volt: Q130 -- re-land ten blobs a spark sweep reverted, pin the Q126 cells, run the guard as gate leg ten

This commit is contained in:
volt committed 2026-10-30 20:45:00 +00:00
1 parent afb231d10b
commit e9991dc7dc
14 files changed
+763 -167

No files matched your search

+2
View File
@@ -13671,3 +13671,5 @@ CHECK: bash wizard-work/volt/CHECK-q129
**Q129 — DONE at `590c7d9` (the rename) and the commit this line lands in (the instrument, this record, the re-rendered index); opened and repaired in the same tick, so no opening record was ever written and this line is the only statement the claim census can anchor to the id.**
**Q129 addendum — one more file than the record above lists: `tools/path_claim_baseline.tsv` gains one `RENAMED` row, and the reason is the record's own prose.** Measured on the landing tree before the row existed: `path_claim_check.py --repo . --rev WORKTREE` read `9637 distinct paths, 261 missing, 1 new against the baseline`, and the one row was `4x checkins/2026-10-30-timmy-tm-formidable.md first WORK-QUEUE.md:13655 [NEW -- not in the baseline]` — the record naming the name it renamed away, which the scanner is right to grade as a claim on a path that is not in the tree. The fix is the ledger's stated mechanism and its own precedent, not a reworded record: `checkins/2026-10-19-zephyrus-sovereignty.md` already carries an identical `RENAMED` row ("added at a013842 …, removed at c6bba84 which relocated it to notes/…"), so this row states the same three facts — added at `bd160bb`, renamed at `590c7d9` to `checkins/2026-10-30-timmy.md`, blob `1f3e51c1f9dfd26b7afaae2045f5c604f3c46172` on both sides. After it: `261 entries, 261 matched, 0 new, 0 cleared, 0 dead row(s)`. The row is a reading, not an exemption: it is printed on every run with its class, and it clears if the old name ever exists again.
**Q130 — OPENED and REPAIRED in the same tick (volt, 2026-10-30, at the commit this record lands in; spark lane by authorship, unclaimed and taken). The third sweep of the day put ten repaired blobs back — three Q109/Q120 artifacts and all seven Q126 test cells — in a commit that advertises one new script, and the guard built to catch exactly that was run by nothing.** Measured at HEAD `afb231d` ("spark: add disk usage analyzer script and report", authored `spark`, 2026-10-30T20:17:47Z, 24 files, `568+/743−`) before any edit in this tick: the seven files Q126 rebound are byte-identical to the blobs they replaced — `git rev-parse afb231d:<p>` equals `git rev-parse b14d73a^:<p>` for all seven (`test_array_intersection.py` `11063512`, `test_counter_util.py` `fb5d3113`, `test_email_validator.py` `236c91d8`, `test_hermes.py` `546ee1c9`, `test_prime_factorizer.py` `22d90a1a`, `test_url_title_extractor.py` `d1f7e631`, `test_validator.py` `4bd8fe3c`) — and three artifacts are back to the pre-repair blobs Q109/Q120 closed: `python-env-check.py` sha256 `51e7a52334a2` (the `c166bf1` blob), `snippet.py` `dc4dbeeee3a2` and `verify_hermes_setup.sh` `0af7884e8192` (the `3857b60` blobs). `wizard-work/spark/CHECK` also lost the six lines `1cb001a` added to invoke the guard, so the guard Q127 built to catch this exact revert (`wizard-work/spark/closed_artifact_guard.py`, 8 cells) read **7 legs, 2 passed, 5 faults, rc 1** at HEAD and nothing ran it: `tools/verify.py` had no leg for it, and the one leg that did see part of the damage was `import-resolve`, at **7 faults in 7 files** (`OUTSIDE_REPO`, all spark), which is where this tick's opening reading came from (`verify.py --fast`: 9 legs, 7 pass, 1 fault, 1 skipped, rc 1). **The repair, in four parts.** (1) The ten blobs are restored from the revs that certified them — the seven tests from `b14d73a` (Q126), `python-env-check.py`, `snippet.py` and `verify_hermes_setup.sh` from `1cb001a` (Q127) — and each restored file's sha256 matches the pin the guard already carried (`747dcc3afdcc`, `3eb85613fce7`, `b58861a11d38`), so the restore is a digest reading, not a `git checkout` that looked right. (2) The guard gains seven `Q126` pins (`repaired` the digest restored, `pre_repair` the `afb231d` digest, so a fourth sweep is named as a revert) and a `run:` leg that executes each of the seven cells from `wizard-work/spark` — a pin alone would stay green if `test_schema.json`, `valid_test_data.json` or `invalid_test_data.json` vanished, since none of the three is pinned individually. (3) `tools/verify.py` runs the guard as a tenth leg, `closed-artifacts`, through a new `path` key in `LEGS` and a matching branch in `leg_argv` (`--root` the graded tree); its selftest fixtures stub a slash-keyed tool outside `tools/`, and cells 1, 1b, 7 and 12b move 9→10. (4) The guard's fault lines became `FAIL: <leg> -- <detail>`: measured on the mutant below, the gate's row read FAULT beside a **PASS** contract line, because `verify.py`'s STRONG hint is `FAIL:` and the guard printed `FAIL pin:…`, while the passing contract line carries the word "errors" — the detail-contradicts-verdict class `verify.py` cell 13 exists for. **Evidence, all measured this tick:** guard `rc 0`, `q127: 21 leg(s), 21 passed, 0 fault(s)`; guard `--selftest` `8 cells, 8 passed` (control, four original mutants, two new Q126 mutants — one dropped cell, one drifted byte — and the six-case classifier); `tools/verify.py` `10 legs, 10 pass, 0 fault, 0 ungradable, 0 skipped`, `gate: OK`, rc 0; `tools/verify.py --selftest` `59 cells, 59 passed`; and the gate-level mutant — `git show afb231d:wizard-work/spark/snippet.py` written back over the restored file — reads `closed-artifacts FAULT rc=1` with the row `FAIL: pin:snippet.py -- sha256=dc4dbeeee3a2 is the 3857b60 pre-repair blob -- Q109's repair at ebba640 was overwritten`, `gate: FAULT`, **rc 1**, and reads 10/10 green again after `git checkout 1cb001a --`. **Not claimed:** that the sweep was deliberate (a stale working copy produces identical bytes and intent is Testimony only); that spark's other host-pinned files are repaired — `wizard-work/spark/CHECK` still grades absolute `/root` paths and was deliberately left alone, that is Q109's open class and this record neither closes nor extends it; that the seven cells' data files are pinned (the `run:` leg is what notices their absence, and a third sweep could rotate them without a pin reddening); that the guard's pins may not be re-taken — an intended change to a pinned artifact re-pins by editing `PINS` with the new digest and the commit that took it, which is a deliberate act with a record, not an override flag; that Q126 or Q127 needed reopening — both repairs are re-landed at their certified digests and both records stand. Files edited this tick: the ten restored blobs, `wizard-work/spark/closed_artifact_guard.py`, `tools/verify.py`, `WORK-QUEUE.md` (this record, appended), and `docs/queue-inLine truncated
+56 -19
View File
@@ -4,7 +4,7 @@
**What this is.** One row per item of `WORK-QUEUE.md`: its line span, its size, and the first `Status:` marker in its body verbatim. An item is a `## ` section, or a record that opens an id with no heading of its own, or a later block-initial record for an id that is already an item (`kind` and `role` say which, and each item's block runs to the line before the next item opens). Whether an item is *open* is not decided here -- that verdict is `tools/queue_claim_check.py`'s, and this document does not overrule it.
**What it exposes.** The queue is two-tier. 97 sections carry 86 ids because 11 ids own a second, continuation section; 40 further ids are opened by a record and own no heading at all, and they have rows and extents here -- 105 record blocks in all, 65 of them a later closure or correction for an id already opened. The same **40 ids** are listed again in the table below, which carries what a bounds row cannot -- the form that opened each one and how often the file names it: grepping `^## Q41` finds nothing while Q41 is a live item the file names 30 times.
**What it exposes.** The queue is two-tier. 97 sections carry 86 ids because 11 ids own a second, continuation section; 41 further ids are opened by a record and own no heading at all, and they have rows and extents here -- 106 record blocks in all, 65 of them a later closure or correction for an id already opened. The same **41 ids** are listed again in the table below, which carries what a bounds row cannot -- the form that opened each one and how often the file names it: grepping `^## Q41` finds nothing while Q41 is a live item the file names 30 times.
| # | id | kind | lines | bytes | role | title | first `Status:` |
|---|---|---|---|---|---|---|---|
@@ -209,7 +209,8 @@
| 199 | Q116 | record | 13617-13634 | 6412 | cont: DONE | **Q116 — DONE (volt, 2026-10-30, at the commit this record lands in; pipe lane by authorship, unclaimed and taken). The grep half of the amended done-means now has an instrument that can fail, it … | -- |
| 200 | Q128 | record | 13635-13652 | 6007 | record: BOLD-LEAD | **Q128 — OPENED and REPAIRED in the same tick (volt, 2026-10-30, at the commit this record lands in; beam lane by authorship, unclaimed and taken). `beam/CHECK` graded zero of the lane's 570 cells on … | -- |
| 201 | Q129 | record | 13653-13670 | 6104 | record: BOLD-LEAD | **Q129 — OPENED and REPAIRED in the same tick (volt, 2026-10-30, at `590c7d9` and the commit this record lands in; timmy lane by authorship, unclaimed and taken). The newest check-in in the tree … | -- |
| 202 | Q129 | record | 13671-13673 | 1514 | cont: DONE | **Q129 — DONE at `590c7d9` (the rename) and the commit this line lands in (the instrument, this record, the re-rendered index); opened and repaired in the same tick, so no opening record was ever … | -- |
| 202 | Q129 | record | 13671-13674 | 1515 | cont: DONE | **Q129 — DONE at `590c7d9` (the rename) and the commit this line lands in (the instrument, this record, the re-rendered index); opened and repaired in the same tick, so no opening record was ever … | -- |
| 203 | Q130 | record | 13675-13675 | 5142 | record: BOLD-LEAD | **Q130 — OPENED and REPAIRED in the same tick (volt, 2026-10-30, at the commit this record lands in; spark lane by authorship, unclaimed and taken). The third sweep of the day put ten repaired blobs … | -- |
## Ids with no `## ` heading
@@ -237,7 +238,7 @@ Opened as records inside another section. `line` is the record's own opening lin
| Q106 | 13171 | 13171 | 14 | Q105 | BULLET+BOLD | BOLD-LEAD |
| Q107 | 13189 | 13189 | 18 | Q106 | BULLET+BOLD | BOLD-LEAD |
| Q108 | 13267 | 13263 | 3 | Q106 | BULLET+BOLD | BOLD-LEAD |
| Q109 | 13270 | 13270 | 23 | Q104 | BULLET+BOLD | BOLD-LEAD |
| Q109 | 13270 | 13270 | 27 | Q104 | BULLET+BOLD | BOLD-LEAD |
| Q110 | 13272 | 13272 | 6 | Q109 | BULLET+BOLD | BOLD-LEAD |
| Q111 | 13274 | 13274 | 8 | Q110 | BULLET+BOLD | BOLD-LEAD |
| Q112 | 13276 | 13276 | 6 | Q111 | BULLET+BOLD | BOLD-LEAD |
@@ -248,15 +249,16 @@ Opened as records inside another section. `line` is the record's own opening lin
| Q117 | 13290 | 13288 | 6 | Q96 | BULLET+BOLD | BOLD-LEAD |
| Q118 | 13300 | 13300 | 2 | Q111 | BULLET+BOLD | BOLD-LEAD |
| Q119 | 13305 | 13305 | 5 | Q118 | BULLET+BOLD | BOLD-LEAD |
| Q120 | 13308 | 13308 | 19 | Q119 | BULLET+BOLD | BOLD-LEAD |
| Q120 | 13308 | 13308 | 21 | Q119 | BULLET+BOLD | BOLD-LEAD |
| Q121 | 13342 | 13342 | 4 | Q107 | BULLET+BOLD | BOLD-LEAD |
| Q122 | 13344 | 13344 | 6 | Q121 | BULLET+BOLD | BOLD-LEAD |
| Q123 | 13346 | 13346 | 4 | Q122 | BULLET+BOLD | BOLD-LEAD |
| Q124 | 13350 | 13350 | 4 | Q95 | BULLET+BOLD | BOLD-LEAD |
| Q126 | 13545 | 13545 | 2 | Q113 | BULLET+BOLD | BOLD-LEAD |
| Q127 | 13577 | 13577 | 1 | Q110 | BULLET+BOLD | BOLD-LEAD |
| Q126 | 13545 | 13545 | 8 | Q113 | BULLET+BOLD | BOLD-LEAD |
| Q127 | 13577 | 13577 | 4 | Q110 | BULLET+BOLD | BOLD-LEAD |
| Q128 | 13635 | 13635 | 1 | Q116 | BULLET+BOLD | BOLD-LEAD |
| Q129 | 13653 | 13653 | 3 | Q128 | BULLET+BOLD | BOLD-LEAD |
| Q130 | 13675 | 13675 | 1 | Q129 | BULLET+BOLD | BOLD-LEAD |
**Not in this document.** Ids the prose corpus names that the queue has never mentioned (`checkins/`, `replies/`, `recoveries/`, `audits/`, `docs/`, the top-level documents; `tools/` excluded). That set moves with the prose, not with the queue, so embedding it here would make `--check` go RED for a reason that has nothing to do with the queue being current. It is reported on demand: `python3 tools/queue_index.py --outside`.
@@ -1621,17 +1623,30 @@ The block below is the parse this document renders, byte for byte. `--check` com
"title": "**Q129 \u2014 OPENED and REPAIRED in the same tick (volt, 2026-10-30, at `590c7d9` and the commit this record lands in; timmy lane by authorship, unclaimed and taken). The newest check-in in the tree \u2026"
},
{
"bytes": 1514,
"end_line": 13673,
"bytes": 1515,
"end_line": 13674,
"id": "Q129",
"kind": "record",
"line": 13671,
"lines": 3,
"lines": 4,
"role": "continuation",
"role_detail": "DONE",
"status": null,
"status_line": null,
"title": "**Q129 \u2014 DONE at `590c7d9` (the rename) and the commit this line lands in (the instrument, this record, the re-rendered index); opened and repaired in the same tick, so no opening record was ever \u2026"
},
{
"bytes": 5142,
"end_line": 13675,
"id": "Q130",
"kind": "record",
"line": 13675,
"lines": 1,
"role": "record",
"role_detail": "BOLD-LEAD",
"status": null,
"status_line": null,
"title": "**Q130 \u2014 OPENED and REPAIRED in the same tick (volt, 2026-10-30, at the commit this record lands in; spark lane by authorship, unclaimed and taken). The third sweep of the day put ten repaired blobs \u2026"
}
],
"ids_multi_section": {
@@ -4383,17 +4398,30 @@ The block below is the parse this document renders, byte for byte. `--check` com
"title": "**Q129 \u2014 OPENED and REPAIRED in the same tick (volt, 2026-10-30, at `590c7d9` and the commit this record lands in; timmy lane by authorship, unclaimed and taken). The newest check-in in the tree \u2026"
},
{
"bytes": 1514,
"end_line": 13673,
"bytes": 1515,
"end_line": 13674,
"id": "Q129",
"kind": "record",
"line": 13671,
"lines": 3,
"lines": 4,
"role": "continuation",
"role_detail": "DONE",
"status": null,
"status_line": null,
"title": "**Q129 \u2014 DONE at `590c7d9` (the rename) and the commit this line lands in (the instrument, this record, the re-rendered index); opened and repaired in the same tick, so no opening record was ever \u2026"
},
{
"bytes": 5142,
"end_line": 13675,
"id": "Q130",
"kind": "record",
"line": 13675,
"lines": 1,
"role": "record",
"role_detail": "BOLD-LEAD",
"status": null,
"status_line": null,
"title": "**Q130 \u2014 OPENED and REPAIRED in the same tick (volt, 2026-10-30, at the commit this record lands in; spark lane by authorship, unclaimed and taken). The third sweep of the day put ten repaired blobs \u2026"
}
],
"nested": [
@@ -4582,7 +4610,7 @@ The block below is the parse this document renders, byte for byte. `--check` com
"form": "BOLD-LEAD",
"id": "Q109",
"line": 13270,
"refs": 23,
"refs": 27,
"section": "Q104",
"shape": "BULLET+BOLD"
},
@@ -4681,7 +4709,7 @@ The block below is the parse this document renders, byte for byte. `--check` com
"form": "BOLD-LEAD",
"id": "Q120",
"line": 13308,
"refs": 19,
"refs": 21,
"section": "Q119",
"shape": "BULLET+BOLD"
},
@@ -4726,7 +4754,7 @@ The block below is the parse this document renders, byte for byte. `--check` com
"form": "BOLD-LEAD",
"id": "Q126",
"line": 13545,
"refs": 2,
"refs": 8,
"section": "Q113",
"shape": "BULLET+BOLD"
},
@@ -4735,7 +4763,7 @@ The block below is the parse this document renders, byte for byte. `--check` com
"form": "BOLD-LEAD",
"id": "Q127",
"line": 13577,
"refs": 1,
"refs": 4,
"section": "Q110",
"shape": "BULLET+BOLD"
},
@@ -4756,6 +4784,15 @@ The block below is the parse this document renders, byte for byte. `--check` com
"refs": 3,
"section": "Q128",
"shape": "BULLET+BOLD"
},
{
"first_mention": 13675,
"form": "BOLD-LEAD",
"id": "Q130",
"line": 13675,
"refs": 1,
"section": "Q129",
"shape": "BULLET+BOLD"
}
],
"preamble": {
@@ -4763,9 +4800,9 @@ The block below is the parse this document renders, byte for byte. `--check` com
"end_line": 8,
"line": 1
},
"queue_bytes": 2194789,
"queue_lines": 13673,
"queue_sha256": "95d4c744bdbe7b56b388568999e95f09b6660c3defae8647cc2a810314255797",
"queue_bytes": 2199932,
"queue_lines": 13675,
"queue_sha256": "3472c4f6d9b70859dfae17b0be0ea3cf7720662e70dc85f75de3f4c7cce431f3",
"sections": [
{
"bytes": 3139,
+46 -9
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""verify.py -- the HOME the index gate did not have: one command, nine legs,
"""verify.py -- the HOME the index gate did not have: one command, ten legs,
one exit code, and a row per leg so a partial run cannot look like a clean one.
WHY THIS EXISTS
@@ -83,6 +83,19 @@ WHAT IT RUNS -- one row per leg, cheapest first, never in parallel
flag other than `--json` (`--write`, `--save-vocab`) on the
same rule. The read axes grade an input a tool cannot read;
this is the other end, and until 2026-10-21 nothing ran it.
import-resolve tools/import_resolve_check.py --rev WORKTREE
every committed test file binds the module it imports to the
REPO, not to a host path the writer happened to have -- the
Q61(b)/Q100 class, graded by `ast` rather than `grep` so a
docstring that records the fix is not a fault
closed-artifacts wizard-work/spark/closed_artifact_guard.py --root .
every repair a CLOSED item landed is still the bytes that
landed: sha256 pins for Q109/Q120's three artifacts and
Q126's seven rebound cells, each cell run from its own
directory. The instrument lives in the lane that owns those
files; the leg is here because a guard nothing runs is not a
guard -- `afb231d` reverted all ten blobs and no gate leg saw
the three artifacts.
The legs are ordered by cost, and a leg is never skipped because an earlier
one failed: a stale index and a broken selftest are different defects and the
@@ -205,6 +218,11 @@ LEGS = (
"what": "every committed test file binds the module it imports to the "
"repo, not to a host path (static sys.path scan plus "
"--import-probe)"},
{"key": "closed-artifacts", "tool": "closed_artifact_guard.py", "slow": False,
"path": os.path.join("wizard-work", "spark", "closed_artifact_guard.py"),
"what": "every repair a closed item landed is still the bytes that landed "
"(sha256 pins for Q109/Q120 and the seven Q126 cells) and its "
"cell still runs green"},
)
# A detail line is a courtesy, not the verdict -- but a verdict with no line
@@ -410,6 +428,13 @@ def leg_argv(key, repo, queue, index, checkin_day=None):
return [sys.executable, os.path.join(repo, "tools",
"import_resolve_check.py"),
"--repo", repo, "--rev", "WORKTREE", "--quiet"]
if key == "closed-artifacts":
# --root, so the tree graded is the tree named on the command line and
# never the working directory this leg happened to inherit.
return [sys.executable,
os.path.join(repo, "wizard-work", "spark",
"closed_artifact_guard.py"),
"--root", repo]
raise KeyError(key)
@@ -425,7 +450,10 @@ def _elapsed_ms(started):
def run_leg(leg, repo, queue, index, timeout, checkin_day=None):
"""One leg -> one row. rc 0/1/2 are graded; anything else is ungradable."""
tool = os.path.join(repo, "tools", leg["tool"])
# A leg names its tool inside `tools/` by default; `path` lets a leg grade an
# instrument a lane owns in its own directory (the smoke rule is that the
# gate runs it, not that it lives here).
tool = os.path.join(repo, leg.get("path") or os.path.join("tools", leg["tool"]))
started = time.time()
row = {"key": leg["key"], "tool": leg["tool"], "verdict": None,
"rc": None, "detail": "", "what": leg["what"]}
@@ -644,7 +672,14 @@ def _fixture(base, rcs, outs=None, sleep=0.0, markers=None, argv_markers=None,
for tool, rc in rcs.items():
if rc is None: # tool absent on purpose
continue
_stub(os.path.join(repo, "tools", tool), rc,
# A key with a separator is a leg whose instrument lives outside tools/;
# it is stubbed where the leg will look for it.
if os.sep in tool:
dest = os.path.join(repo, tool)
os.makedirs(os.path.dirname(dest), exist_ok=True)
else:
dest = os.path.join(repo, "tools", tool)
_stub(dest, rc,
out=(outs or {}).get(tool, ""), sleep=sleep,
marker=(markers or {}).get(tool),
argv_marker=(argv_markers or {}).get(tool))
@@ -663,7 +698,9 @@ GREEN_ALL = {"checkin_check.py": 0, "path_claim_check.py": 0,
"reply_check.py": 0, "queue_index.py": 0,
"queue_append_audit.py": 0, "doc_command_check.py": 0,
"selftest_sweep.py": 0, "write_probe.py": 0,
"import_resolve_check.py": 0}
"import_resolve_check.py": 0,
os.path.join("wizard-work", "spark",
"closed_artifact_guard.py"): 0}
def selftest():
@@ -680,11 +717,11 @@ def selftest():
failures.append("%s\n got: %r\n want: %r" % (label, got, want))
with tempfile.TemporaryDirectory(prefix="verify-selftest-") as td:
# 1. eight green stubs -> rc 0
# 1. ten green stubs -> rc 0
repo = _fixture(os.path.join(td, "c1"), GREEN_ALL)
rc, out = _verify("--repo", repo)
check("1 all nine legs green -> rc 0", rc, 0)
check("1b summary counts the nine legs", "verify: 9 legs, 9 pass, 0 fault, "
check("1 all ten legs green -> rc 0", rc, 0)
check("1b summary counts the ten legs", "verify: 10 legs, 10 pass, 0 fault, "
"0 ungradable, 0 skipped" in out, True)
# 2. a stale index is a FAULT, and its own line reaches the row
@@ -741,7 +778,7 @@ def selftest():
check("7 --json writes the rows", [r["key"] for r in obj.get("rows", [])],
["checkin", "path-claims", "reply-headings", "index-current",
"append-audit", "doc-commands", "selftest-sweep", "write-axis",
"import-resolve"])
"import-resolve", "closed-artifacts"])
check("7b --json carries the same rc the process returned",
obj.get("rc"), rc)
@@ -785,7 +822,7 @@ def selftest():
check("12 skipped leg is its own count in --json",
obj.get("counts", {}).get("SKIP"), 1)
check("12b and the skipped leg is not counted as a pass",
obj.get("counts", {}).get(PASS), 8)
obj.get("counts", {}).get(PASS), 9)
# 13. the detail line must not contradict the verdict: a row graded
# FAULT beside "0 fatal" is the SILENT failure this gate exists to catch.
+145 -6
View File
@@ -24,9 +24,21 @@ diagnosis can say "reverted" instead of "changed".
Legs (each prints PASS/FAIL/INFO and names its subject):
pin:<path> sha256 equals the blob the repair commit landed
cell:<name> the item's own cell still runs green
run:<path> a rebound Q126 test file runs rc 0 from its own directory
contract:<name> the repaired artifact's documented behaviour, by subprocess
env:<name> INFO only -- host packages the Q120 cell is coupled to
selftest six fixtures: one control, five one-edit mutants
selftest eight cells: one control, six one-edit mutants, one
classifier cell
2026-10-30, second sweep: `afb231d` ("spark: add disk usage analyzer script and
report") put all three of the pinned artifact blobs back AND reverted the seven
spark test files Q126 had rebound, byte for byte, in one commit that advertises
one new script. Nothing ran this guard -- `1cb001a` wired it into
`wizard-work/spark/CHECK` and that commit put the six-line wiring back too -- so
the reverts stood until the repo gate's `import-resolve` leg named the test
files. The Q126 files are pinned and run here now, and `tools/verify.py` runs
this guard as the `closed-artifacts` leg, so the next sweep reds the primary
gate instead of waiting for a reader to notice.
Exit codes follow the repo's tools/ contract:
0 = every pinned blob and every cell is as the closed records left it
@@ -73,8 +85,76 @@ PINS = {
"landed": "ebba640",
"pre_rev": "3857b60",
},
# Q126 rebound nine host-bound test files to the module beside them. The
# next spark sweep (`afb231d`, 2026-10-30T20:17:47Z) put all seven of the
# spark ones back to the pre-repair bytes, byte for byte. They are pinned
# here for the same reason as the artifacts above: a repair that can be
# overwritten without a name is not a repair, it is a coincidence.
"test_array_intersection.py": {
"repaired": "d31a5ec9bc9b00a41239f6bac930a46156b234c21e3dfac68e9888a8f252cf18",
"pre_repair": "6f29649d52b50b166ce16fafc3a6021d1068301c8093d3a95cbc939afd9ca673",
"item": "Q126",
"landed": "b14d73a",
"pre_rev": "afb231d",
},
"test_counter_util.py": {
"repaired": "8648a28152a557b2a7b2466d8232955f5f3fdfcc7445894285fd1c52882898a8",
"pre_repair": "d837f183440640416cdd8c3020ee6035288d099a704e441d2726810408645411",
"item": "Q126",
"landed": "b14d73a",
"pre_rev": "afb231d",
},
"test_email_validator.py": {
"repaired": "b698838fc4055c1bc3a30f054855b2cf19b79023b27636b53a7dffc5b9278c37",
"pre_repair": "95d8d77c1ee8e75f1438d3915ffc2e8f07d397a8b3d67f38d5e25e81cb84540f",
"item": "Q126",
"landed": "b14d73a",
"pre_rev": "afb231d",
},
"test_hermes.py": {
"repaired": "b8dad112c1f3e170e3b82e8cfb074e1f30e2648f9c62b38e30aaf837dc83feb0",
"pre_repair": "87f552a14a9945ce9cfdaf29bc959a394fcc779eda519b824ece80d27e99ab91",
"item": "Q126",
"landed": "b14d73a",
"pre_rev": "afb231d",
},
"test_prime_factorizer.py": {
"repaired": "3d409c79cbeb4d068d73f0cb8dd7e539f837e5afdc9c8770584d1e7db263a9e1",
"pre_repair": "4be81385dfd01a00363977ef6ff787741d1a0ecc9892f030aaefd3b0386509e1",
"item": "Q126",
"landed": "b14d73a",
"pre_rev": "afb231d",
},
"test_url_title_extractor.py": {
"repaired": "411d781a8d380497def3d4811900f313247d951aee7cef965675dccbdc9b311a",
"pre_repair": "b6ea8fc299a7caa24cacd2ad7aaaed302533ecc7b82f0866265612f92c1696ca",
"item": "Q126",
"landed": "b14d73a",
"pre_rev": "afb231d",
},
"test_validator.py": {
"repaired": "0dd63e37b805090b9e290f5a1707daae45e4f6275a4ffbca1d9c3416f6a84165",
"pre_repair": "665e21645a6cd0d49c55b269e546617917741ac3f592473251c29f59515edc03",
"item": "Q126",
"landed": "b14d73a",
"pre_rev": "afb231d",
},
}
# A pin proves the bytes; this proves those bytes still measure something. The
# seven Q126 files read the module beside them AND data files that are not
# pinned one by one (test_schema.json and the two data files), so a deleted
# fixture would leave every pin green and the suite dead.
RUNS = [
"test_array_intersection.py",
"test_counter_util.py",
"test_email_validator.py",
"test_hermes.py",
"test_prime_factorizer.py",
"test_url_title_extractor.py",
"test_validator.py",
]
# The cell that grades each artifact must still be here for the cell leg to run.
CELL_FILES = ["test_env_verifiers.py", "test_python_env_check.py"]
@@ -123,7 +203,14 @@ class Report:
def bad(self, leg, detail):
self.failed.append(leg)
print("FAIL %s: %s" % (leg, detail))
# `FAIL:` -- with the colon -- is the token `tools/verify.py` treats as a
# STRONG hint, so the gate's row carries THIS line rather than the first
# line that happens to contain the word "error" (a passing contract
# line prints "two usage errors rc 2"). Measured on the 2026-10-30
# mutant: the row read FAULT beside a PASS line until the colon was
# added, which is the detail-contradicts-verdict class verify.py cell 13
# exists to catch.
print("FAIL: %s -- %s" % (leg, detail))
def note(self, leg, detail):
self.info.append(leg)
@@ -273,6 +360,31 @@ def leg_env(rep):
"environment, not for the code")
def leg_runs(root, rep):
"""Q126: the rebound test files run green from their own directory.
Their sys.path is this directory, so rc 0 here means the run graded the
committed module -- and that the data files the file reads are still there.
"""
spark = os.path.join(root, SPARK)
for name in RUNS:
if not os.path.exists(os.path.join(spark, name)):
rep.bad("run:%s" % name, "absent from %s (Q126's cell is gone)" % SPARK)
continue
try:
proc = run([sys.executable, name], cwd=spark, timeout=180)
except subprocess.TimeoutExpired:
rep.bad("run:%s" % name, "timeout after 180s")
continue
if proc.returncode == 0:
rep.ok("run:%s" % name,
"rc 0 from %s -- the run graded the module beside it" % SPARK)
else:
lines = (proc.stdout + proc.stderr).strip().splitlines() or [""]
rep.bad("run:%s" % name, "rc %d -- %s"
% (proc.returncode, lines[-1][:160]))
def guard(root, run_cells=True):
rep = Report()
if not os.path.isdir(os.path.join(root, SPARK)):
@@ -282,6 +394,7 @@ def guard(root, run_cells=True):
leg_cell_files(root, rep)
if run_cells:
leg_env_verifiers(root, rep)
leg_runs(root, rep)
leg_pyenv_contract(root, rep)
leg_env(rep)
@@ -300,12 +413,25 @@ def guard(root, run_cells=True):
# ---------------------------------------------------------------- selftest --
# The modules and data files the pinned Q126 cells import. Copied into every
# selftest tree so the control is a tree in which a correct repair reads green:
# without them the cells are right and the fixture is empty, which is a red
# control and looks exactly like a broken repair.
RUN_DEPS = [
"array_intersection.py", "counter_util.py", "email_validator.py",
"json_schema_cli.py", "prime_factorizer.py", "url_title_extractor.py",
"test_schema.json", "valid_test_data.json", "invalid_test_data.json",
]
def build_fixture(dest, repo_root, repairs):
"""Copy the guarded files into `dest`, then apply `repairs` edits."""
spark = os.path.join(dest, SPARK)
os.makedirs(spark)
for rel in list(PINS) + CELL_FILES + ["CHECK-env-verifiers"]:
shutil.copy2(os.path.join(repo_root, SPARK, rel), os.path.join(spark, rel))
for rel in list(PINS) + CELL_FILES + ["CHECK-env-verifiers"] + RUN_DEPS:
src = os.path.join(repo_root, SPARK, rel)
if os.path.exists(src):
shutil.copy2(src, os.path.join(spark, rel))
repairs(dest)
return dest
@@ -328,6 +454,12 @@ def _drift(dest):
fh.write("\n# a sweep appended this line\n")
def _drift_test_file(dest):
"""One byte appended to a Q126 cell: the pin must name it."""
with open(os.path.join(dest, SPARK, "test_counter_util.py"), "a") as fh:
fh.write("\n# a sweep appended this line\n")
def _drop(path_rel):
def _do(dest):
os.remove(os.path.join(dest, SPARK, path_rel))
@@ -458,14 +590,21 @@ def selftest(repo_root):
cell("mutant-revert-pyenv", _revert_pyenv, 1, "is the c166bf1 pre-repair blob")
cell("mutant-drifted-bytes", _drift, 1, "neither the pinned repair")
cell("mutant-artifact-absent", _drop("verify_hermes_setup.sh"), 1, "absent")
cell("mutant-test-file-absent", _drop("test_counter_util.py"), 1,
"Q126's cell is gone")
cell("mutant-test-file-drifted", _drift_test_file, 1, "neither the pinned repair")
# The sixth cell is not a tree: it grades the classifier on the digests the
# real reverts produced, so the "reverted" diagnosis cannot rot into prose.
# The classifier cells grade the diagnosis the real reverts produced, so the
# "reverted" naming cannot rot into prose. `afb231d` reproduced every one of
# these pre_repair digests in the tree, which is what the digests record.
cases = [
("snippet.py", PINS["snippet.py"]["pre_repair"], "reverted"),
("python-env-check.py", PINS["python-env-check.py"]["pre_repair"], "reverted"),
("snippet.py", PINS["snippet.py"]["repaired"], "repaired"),
("verify_hermes_setup.sh", "0" * 64, "drifted"),
("test_prime_factorizer.py",
PINS["test_prime_factorizer.py"]["pre_repair"], "reverted"),
("test_hermes.py", PINS["test_hermes.py"]["repaired"], "repaired"),
]
bad = [c for c in cases if classify(c[0], c[1]) != c[2]]
cells.append(("classify-revert-digests", 0 if not bad else 1, not bad))
+125 -38
View File
@@ -1,53 +1,140 @@
#!/usr/bin/env python3
"""Python environment health check - verifies key packages are available."""
"""Python environment health check for the spark lane.
import sys
The dependency list is the lane's own third-party imports, measured from
`wizard-work/spark/*.py` (`grep -rhoE '^(import|from) +[A-Za-z0-9_.]+'`):
PyYAML -> yaml debug_yaml.py:2
numpy -> numpy qc_analyze.py:7
Pillow -> PIL ascii_tool.py:12, qc_analyze.py:6
jsonschema -> jsonschema validate_json_schema.py:14
`email_validator` is deliberately NOT declared: the lane's
`wizard-work/spark/email_validator.py` is a local, stdlib-only module, so
declaring the PyPI distribution would test a package the lane never uses.
The check can fail. A dependency that will not import is reported as
`"status": "missing"` and the process exits 1; a package that imports but
raises something other than ImportError is reported as `"status": "broken"`.
`--require` appends a dependency for one run, which is how the committed
cell in `test_python_env_check.py` proves the red path with a package that
is genuinely absent on the host.
Exit codes (tools/ contract):
0 every declared dependency imports and reports
1 at least one declared or --require'd dependency is missing/broken
2 usage error (argparse)
"""
import argparse
import importlib
import importlib.metadata
import json
import sys
from datetime import datetime, timezone
def check_module(name):
try:
__import__(name)
return {"status": "ok", "available": True}
except ImportError as e:
return {"status": "missing", "available": False, "error": str(e)}
# (distribution name, import name) for the lane's own third-party imports
DEPENDENCIES = (
("PyYAML", "yaml"),
("numpy", "numpy"),
("Pillow", "PIL"),
("jsonschema", "jsonschema"),
)
def check_package(name, import_name=None):
import_name = import_name or name
try:
mod = __import__(import_name)
version = getattr(mod, '__version__', 'unknown')
return {"status": "ok", "version": version, "available": True}
except ImportError as e:
return {"status": "missing", "available": False, "error": str(e)}
def main():
results = {
def check_package(distribution, import_name=None):
"""Import one dependency by name and report its version.
The version is read from the distribution metadata first; `__version__`
is the fallback for packages that ship no metadata record.
"""
import_name = import_name or distribution
try:
mod = importlib.import_module(import_name)
except ImportError as exc:
return {
"status": "missing",
"available": False,
"import_name": import_name,
"error": "%s: %s" % (type(exc).__name__, exc),
}
except Exception as exc: # a package that raises on import is not healthy
return {
"status": "broken",
"available": False,
"import_name": import_name,
"error": "%s: %s" % (type(exc).__name__, exc),
}
try:
version = importlib.metadata.version(distribution)
except importlib.metadata.PackageNotFoundError:
version = getattr(mod, "__version__", "unknown")
return {
"status": "ok",
"available": True,
"import_name": import_name,
"version": version,
}
def parse_requirement(text):
"""`NAME` or `DISTRIBUTION:IMPORT` -> (distribution, import name)."""
if ":" in text:
distribution, import_name = text.split(":", 1)
if not distribution or not import_name:
raise argparse.ArgumentTypeError(
"--require needs NAME or DISTRIBUTION:IMPORT, got %r" % text
)
return distribution, import_name
return text, text
def build_parser():
parser = argparse.ArgumentParser(
prog="python-env-check.py",
description="Verify the spark lane's third-party dependencies import, "
"and report their versions.",
)
parser.add_argument(
"--require",
metavar="NAME[:IMPORT]",
action="append",
default=[],
type=parse_requirement,
help="also require this package for this run (repeatable); "
"useful for proving the red path",
)
return parser
def main(argv=None):
args = build_parser().parse_args(argv)
declared = list(DEPENDENCIES) + list(args.require)
packages = {}
for distribution, import_name in declared:
result = check_package(distribution, import_name)
result["distribution"] = distribution
packages[import_name] = result
missing = sorted(
name for name, result in packages.items() if result["status"] != "ok"
)
output = {
"all_ok": not missing,
"timestamp": datetime.now(timezone.utc).isoformat(),
"dependencies_declared": len(declared),
"missing": missing,
"components": {
"python": {
"status": "ok",
"version": sys.version,
"executable": sys.executable
"executable": sys.executable,
},
"packages": packages,
},
"packages": {
"json": check_module("json"),
"subprocess": check_module("subprocess"),
"datetime": check_module("datetime"),
}
}
print(json.dumps(output, indent=2, sort_keys=True))
return 0 if not missing else 1
all_ok = all(
p.get("status") == "ok" or p.get("available", False)
for p in results["packages"].values()
)
output = {
"all_ok": all_ok,
"timestamp": datetime.now(timezone.utc).isoformat(),
"components": results
}
print(json.dumps(output, indent=2))
sys.exit(0 if all_ok else 1)
if __name__ == "__main__":
main()
sys.exit(main())
+147 -28
View File
@@ -1,37 +1,156 @@
#!/usr/bin/env python3
"""Verify the Hermes Agent environment this tree ships into.
Exit 0: every required directory and every required tool is present.
Exit 1: at least one of them is genuinely absent; the line naming it is printed.
Exit 2: usage error.
What "available" means. A tool is available only if `hermes_tools` imports and
exposes a *callable* of that name. Nothing else counts: a name in a list is not
a measurement, which is what this file used to print (`available[tool] = True`
in a loop over a hard-coded list, so run under a bare python3 with no Hermes
session it reported four tools and "verified successfully", rc 0 -- Q109).
Resolution. The profile home is --home, else $HERMES_HOME (which in this
install names the active profile's home, e.g. ~/.hermes/profiles/<profile>),
else ~/.hermes/profiles/$HERMES_PROFILE (default profile: spark). The Hermes
root is --root, else the parent of the profile home's `profiles` directory,
else the profile home's own parent. Every path graded is derived from those two,
so the check can be pointed at a synthetic tree and be seen to fail. The
env-first pattern is the lane's own: hermes_agent_verify.py:27.
Graded by test_env_verifiers.py (black box -- runs this file as a subprocess and
imports nothing from it) and by CHECK-env-verifiers.
Run: python3 snippet.py [--home DIR] [--root DIR] [--profile NAME] [--require TOOL]
"""
Snippet: Verify Hermes Agent environment and available tools.
"""
import sys
import argparse
import importlib
import os
import sys
from pathlib import Path
def check_environment():
"""Verify critical Hermes directories exist."""
dirs = [
"/root/.hermes",
"/root/.hermes/profiles",
"/root/.hermes/skills",
"/root/.hermes/plugins",
TOOLS = ["web_search", "web_extract", "read_file", "write_file"]
def profile_name(override=None):
"""The profile named in the reading: --profile, else $HERMES_PROFILE, else spark."""
if override:
return override
return os.environ.get("HERMES_PROFILE") or "spark"
def profile_home(override=None, profile="spark"):
"""The profile home this check grades."""
if override:
return Path(override).expanduser()
env = os.environ.get("HERMES_HOME", "")
if env:
return Path(env).expanduser()
return Path.home() / ".hermes" / "profiles" / profile
def hermes_root(profile_home_dir, override=None):
"""The Hermes root: --root, else the parent of `profiles`, else the parent."""
if override:
return Path(override).expanduser()
profile_home_dir = Path(profile_home_dir)
if profile_home_dir.parent.name == "profiles":
return profile_home_dir.parent.parent
return profile_home_dir.parent
def required_dirs(profile_home_dir, root):
"""Directories whose absence makes the reading fail."""
profile_home_dir = Path(profile_home_dir)
return [
Path(root),
profile_home_dir,
profile_home_dir / "cache" / "scratch",
]
missing = [d for d in dirs if not os.path.isdir(d)]
return missing
def check_tools():
"""Verify core tools are available."""
tools = ["web_search", "web_extract", "read_file", "write_file"]
def optional_dirs(root):
"""Directories this lane does not depend on; a miss is noted, not failed."""
root = Path(root)
return [root / "skills", root / "plugins"]
def check_environment(profile_home_dir, root):
"""Return (missing_required, present_optional, missing_optional) as strings."""
missing = [str(p) for p in required_dirs(profile_home_dir, root) if not p.is_dir()]
present_opt = [str(p) for p in optional_dirs(root) if p.is_dir()]
missing_opt = [str(p) for p in optional_dirs(root) if not p.is_dir()]
return missing, present_opt, missing_opt
def check_tools(names=None):
"""Import hermes_tools and ask it for each named tool.
Returns (available: dict[str, bool], error: str). `error` is the import
failure when the package cannot be imported at all, "" otherwise. A name
that resolves to a non-callable is unavailable, not available-but-odd.
"""
names = list(names or TOOLS)
try:
module = importlib.import_module("hermes_tools")
except Exception as exc: # ImportError, or a module that raises on import
return {name: False for name in names}, "%s: %s" % (type(exc).__name__, exc)
available = {}
for tool in tools:
# These are Hermes tools available via hermes_tools
available[tool] = True
return available
for name in names:
try:
candidate = getattr(module, name, None)
except Exception:
candidate = None
available[name] = callable(candidate)
return available, ""
def build_parser():
parser = argparse.ArgumentParser(
description="Verify Hermes directories and tools; 0 pass, 1 fail, 2 usage.",
)
parser.add_argument("--home", default=None,
help="profile home to grade (default: $HERMES_HOME or ~/.hermes/profiles/<profile>)")
parser.add_argument("--root", default=None,
help="Hermes root to grade (default: derived from the profile home)")
parser.add_argument("--profile", default=None,
help="profile name (default: $HERMES_PROFILE or spark)")
parser.add_argument("--require", action="append", default=[], metavar="TOOL",
help="also require this tool (repeatable); the negative-control hook")
return parser
def main(argv=None):
args = build_parser().parse_args(argv) # exits 2 on a usage error
profile = profile_name(args.profile)
home = profile_home(args.home, profile)
root = hermes_root(home, args.root)
names = list(TOOLS) + [n for n in args.require if n not in TOOLS]
missing_dirs, present_opt, missing_opt = check_environment(home, root)
available, error = check_tools(names)
unavailable = [n for n in names if not available.get(n)]
print("HERMES_ROOT: %s" % root)
print("HERMES_HOME: %s" % home)
print("HERMES_PROFILE: %s" % profile)
for path in [str(p) for p in required_dirs(home, root)]:
print("DIR %s: %s" % ("MISSING" if path in missing_dirs else "OK", path))
for path in present_opt:
print("DIR OPTIONAL-OK: %s" % path)
for path in missing_opt:
print("DIR OPTIONAL-MISSING: %s" % path)
if error:
print("TOOLS_IMPORT_FAILED: %s" % error)
print("TOOLS_AVAILABLE: %s" % sorted(n for n in names if available.get(n)))
print("TOOLS_MISSING: %s" % unavailable)
ok = not missing_dirs and not unavailable
print("VERDICT: %s" % ("PASS" if ok else "FAIL"))
return 0 if ok else 1
if __name__ == "__main__":
missing_dirs = check_environment()
if missing_dirs:
print(f"MISSING_DIRS: {missing_dirs}")
sys.exit(1)
tools = check_tools()
print(f"TOOLS_AVAILABLE: {list(tools.keys())}")
print("Hermes environment verified successfully")
sys.exit(0)
sys.exit(main())
+3 -1
View File
@@ -1,7 +1,9 @@
#!/usr/bin/env python3
"""Test file for array_intersection.py"""
import sys
sys.path.insert(0, '/root/.hermes/wizard-outputs/spark')
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from array_intersection import array_intersection
# Test cases
+3 -1
View File
@@ -2,7 +2,9 @@
"""Verification tests for counter_util.py"""
import sys
sys.path.insert(0, '/root/.hermes/wizard-outputs/spark')
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from counter_util import count_words, count_items, most_common, counter_to_dict, extract_counter_description
from collections import Counter
+3 -1
View File
@@ -2,7 +2,9 @@
"""Comprehensive test suite for email_validator.py"""
import sys
sys.path.insert(0, '/root/.hermes/wizard-outputs/spark')
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from email_validator import is_valid_email, validate_email
# Test cases: (email, expected_valid)
+90 -20
View File
@@ -1,27 +1,97 @@
#!/usr/bin/env python3
"""Verify hermes-agent skill is available and functional."""
"""Verify the hermes-agent skill is present and readable on this host.
This is a HOST probe, not a repo test: its subject -- the live Hermes skill
catalog -- lives outside this tree, so it binds no module from it. The skills
root is taken from HERMES_SKILLS_DIR, else HERMES_HOME, else ~/.hermes/skills;
it is never a pinned /root path, so a copy of this file grades the host it is
run on.
Exit codes, because "not run" must never read as "verified":
0 the skill's SKILL.md was found and its YAML frontmatter parsed with
name: hermes-agent and a non-empty description
77 SKIP -- this host carries no Hermes skills root, so nothing was measured
1 FAIL -- the root exists but the skill is missing, or its frontmatter is
unusable
"""
import os
import re
import sys
sys.path.insert(0, '/root/.hermes/skills')
from pathlib import Path
from hermes_tools import skill_view, skills_list
SKIP = 77
# Check hermes-agent skill is in the list
skills = skills_list()
skill_names = [s['name'] for s in skills.get('skills', [])]
if 'hermes-agent' in skill_names:
print("FOUND: hermes-agent skill in catalog")
else:
print("ERROR: hermes-agent skill not found")
print("Available skills:", skill_names[:10])
sys.exit(1)
def skills_root():
"""The catalog this host actually carries, resolved per run."""
explicit = os.environ.get("HERMES_SKILLS_DIR")
if explicit:
return Path(explicit).expanduser()
home = os.environ.get("HERMES_HOME")
return (Path(home).expanduser() if home else Path.home() / ".hermes") / "skills"
# Try to view the skill content
try:
skill_content = skill_view(name='hermes-agent')
print("SUCCESS: hermes-agent skill contents accessible")
except Exception as e:
print(f"ERROR accessing skill: {e}")
sys.exit(1)
print("\nAll checks passed - hermes-agent skill is working correctly")
def find_skill_md(root, name):
"""Every SKILL.md under a directory called `name`, nearest the root first."""
direct = root / name / "SKILL.md"
hits = [direct] if direct.is_file() else []
hits.extend(sorted(root.glob("**/%s/SKILL.md" % name)))
return [h for h in hits if h.is_file()]
def frontmatter(text):
"""The fenced `---` block's `key: value` pairs, or None when there is none."""
m = re.match(r"---\r?\n(.*?)\r?\n---\r?\n", text, re.S)
if not m:
return None
out = {}
for line in m.group(1).splitlines():
if ":" not in line or line.lstrip().startswith("#"):
continue
key, val = line.split(":", 1)
out[key.strip()] = val.strip().strip('"').strip("'")
return out
def main():
root = skills_root()
print("skills root: %s" % root)
if not root.is_dir():
print("SKIP: no Hermes skills root at %s -- nothing was measured" % root)
return SKIP
hits = find_skill_md(root, "hermes-agent")
if not hits:
print("FAIL: the root exists but carries no hermes-agent/SKILL.md")
many = sorted(p.name for p in root.iterdir() if p.is_dir())
print("directories under the root: %s" % (many[:10] or "none"))
return 1
for path in hits:
try:
text = path.read_text(encoding="utf-8")
except (OSError, UnicodeDecodeError) as exc:
print("FAIL: cannot read %s: %s" % (path, exc))
return 1
fm = frontmatter(text)
if fm is None:
print("FAIL: %s has no fenced frontmatter block" % path)
return 1
if fm.get("name") != "hermes-agent":
print("FAIL: %s declares name %r, want 'hermes-agent'"
% (path, fm.get("name")))
return 1
if not fm.get("description"):
print("FAIL: %s declares no description" % path)
return 1
print("PASS: %s" % path)
print(" name=%s version=%s" % (fm.get("name"), fm.get("version", "?")))
print(" description=%s" % fm["description"][:70])
print("\nAll checks passed - hermes-agent skill is present and readable (%d file(s))"
% len(hits))
return 0
if __name__ == "__main__":
sys.exit(main())
+3 -1
View File
@@ -1,7 +1,9 @@
#!/usr/bin/env python3
"""Verification test for prime_factorizer.py"""
import sys
sys.path.insert(0, '/root/.hermes/wizard-outputs/spark')
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from prime_factorizer import prime_factors
# Test cases with expected results
@@ -2,7 +2,9 @@
"""Test the url_title_extractor module."""
import sys
sys.path.insert(0, "/root/.hermes/wizard-outputs/spark")
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from url_title_extractor import extract_title, TitleParser
import json
@@ -22,7 +24,7 @@ def test_extract_title():
def test_cache_file_creation():
"""Test that cache file location is writable."""
cache_dir = Path("/root/.hermes/wizard-outputs/spark")
cache_dir = Path(__file__).resolve().parent
cache_dir.mkdir(parents=True, exist_ok=True)
return cache_dir.exists() and cache_dir.is_dir()
+6 -4
View File
@@ -1,7 +1,9 @@
#!/usr/bin/env python3
"""Test runner for json_schema_cli.py"""
import sys
sys.path.insert(0, '/root/.hermes/wizard-outputs/spark')
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from json_schema_cli import validate_data, load_json
print("=" * 50)
@@ -9,11 +11,11 @@ print("JSON Schema Validator Tests")
print("=" * 50)
# Load test schema
schema = load_json('/root/.hermes/wizard-outputs/spark/test_schema.json')
schema = load_json(str(Path(__file__).resolve().parent / 'test_schema.json'))
# Test 1: Valid data
print("\nTest 1: Valid data")
valid_data = load_json('/root/.hermes/wizard-outputs/spark/valid_test_data.json')
valid_data = load_json(str(Path(__file__).resolve().parent / 'valid_test_data.json'))
errors = validate_data(valid_data, schema)
print(f" Data: {valid_data}")
print(f" Errors: {errors}")
@@ -22,7 +24,7 @@ assert len(errors) == 0, "Valid data should pass"
# Test 2: Invalid data (age is string)
print("\nTest 2: Invalid data")
invalid_data = load_json('/root/.hermes/wizard-outputs/spark/invalid_test_data.json')
invalid_data = load_json(str(Path(__file__).resolve().parent / 'invalid_test_data.json'))
errors = validate_data(invalid_data, schema)
print(f" Data: {invalid_data}")
print(f" Errors: {errors}")
+130 -37
View File
@@ -1,48 +1,141 @@
#!/bin/bash
# Verification script for Hermes Agent profile setup
set -e
# Verify the directories a Hermes Agent profile ships into -- on the tree the
# script is run from, not on the host it was written on.
#
# Q109. This script used to test three absolute paths under the root user's
# home, so it printed its three ticks and "All critical paths verified" rc 0 on
# any tree, and it read nothing relative to itself. It now resolves
# - its own directory from $0 (so the sibling snippet.py it names is the one
# that shipped beside it, and a lone copy has no opinion to report), and
# - the profile home from --home, else $HERMES_HOME, else
# ~/.hermes/profiles/$HERMES_PROFILE (default profile: spark), with the
# Hermes root from --root, else the parent of the profile home's `profiles`
# directory, else the profile home's own parent,
# so the same invocation fails on a tree that is missing the paths it names.
#
# Exit codes follow the repo's tools/ contract:
# 0 = every required path exists
# 1 = a required path is missing (each is printed on its own MISSING: line)
# 2 = usage error, or the script's own directory could not be resolved
set -u
HERE=$(cd -- "$(dirname -- "$(readlink -f -- "$0")")" && pwd) || {
echo "could not resolve the script's own directory" >&2
exit 2
}
PROFILE=${HERMES_PROFILE:-spark}
HOME_ARG=""
ROOT_ARG=""
usage() {
cat <<'USAGE'
usage: verify_hermes_setup.sh [--home DIR] [--root DIR] [--profile NAME]
Grades one Hermes installation:
ROOT the Hermes root
HOME the profile home (ROOT/profiles/NAME, usually)
HOME/cache/scratch the profile's scratch directory
<script dir>/snippet.py the snippet that ships beside this script
Defaults: --home $HERMES_HOME or ~/.hermes/profiles/$HERMES_PROFILE;
--root derived from the profile home; --profile $HERMES_PROFILE or spark.
Exit 0 all present, 1 a required path is missing, 2 usage error.
USAGE
}
while [ "$#" -gt 0 ]; do
case "$1" in
--home)
if [ "$#" -lt 2 ]; then echo "--home needs a value" >&2; exit 2; fi
HOME_ARG=$2
shift 2
;;
--root)
if [ "$#" -lt 2 ]; then echo "--root needs a value" >&2; exit 2; fi
ROOT_ARG=$2
shift 2
;;
--profile)
if [ "$#" -lt 2 ]; then echo "--profile needs a value" >&2; exit 2; fi
PROFILE=$2
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
echo "unknown argument: $1" >&2
usage >&2
exit 2
;;
esac
done
if [ -z "$HOME_ARG" ]; then
if [ -n "${HERMES_HOME:-}" ]; then
HOME_ARG=$HERMES_HOME
else
HOME_ARG=$HOME/.hermes/profiles/$PROFILE
fi
fi
if [ -z "$ROOT_ARG" ]; then
if [ -n "${HERMES_ROOT:-}" ]; then
ROOT_ARG=$HERMES_ROOT
else
parent=$(dirname -- "$HOME_ARG")
if [ "$(basename -- "$parent")" = "profiles" ]; then
ROOT_ARG=$(dirname -- "$parent")
else
ROOT_ARG=$parent
fi
fi
fi
echo "=== Hermes Agent Profile Verification ==="
echo "script dir: $HERE"
echo "hermes root: $ROOT_ARG"
echo "profile home: $HOME_ARG"
echo "profile: $PROFILE"
# Check profile directory
if [ -d "/root/.hermes/profiles/spark" ]; then
echo "✓ Profile directory exists: /root/.hermes/profiles/spark"
missing=0
for path in "$ROOT_ARG" "$HOME_ARG" "$HOME_ARG/cache/scratch" "$HERE/snippet.py"; do
if [ -e "$path" ]; then
echo "ok: $path"
else
echo "MISSING: $path"
missing=$((missing + 1))
fi
done
# Advisory: this lane does not depend on these, so a miss is a note.
for path in "$ROOT_ARG/skills" "$ROOT_ARG/plugins"; do
if [ -d "$path" ]; then
echo "ok (optional): $path"
else
echo "note: optional path absent: $path"
fi
done
if command -v hermes >/dev/null 2>&1; then
echo "note: hermes CLI on PATH: $(command -v hermes)"
else
echo "✗ Profile directory missing"
exit 1
echo "note: hermes CLI not on PATH"
fi
# Check skills directory
if [ -d "/root/.hermes/skills" ]; then
echo "✓ Skills directory exists: /root/.hermes/skills"
if command -v python3 >/dev/null 2>&1; then
echo "note: python3: $(python3 --version 2>&1)"
else
echo "Note: Skills directory not found (may be normal for this session)"
fi
# Check scratch directory
if [ -d "/root/.hermes/profiles/spark/cache/scratch" ]; then
echo "✓ Scratch directory exists"
else
echo "✗ Scratch directory missing"
exit 1
fi
# Check current working directory
CWD=$(pwd)
echo "Current working directory: $CWD"
# Check for Hermes tools
if command -v hermes &> /dev/null; then
echo "✓ hermes CLI available"
else
echo "Note: hermes CLI not in PATH"
fi
# Verify key environment variables
if [ -n "$HermesProfile" ]; then
echo "HermesProfile: $HermesProfile"
echo "MISSING: python3 (snippet.py cannot be run)"
missing=$((missing + 1))
fi
echo ""
echo "=== Verification Complete ==="
echo "All critical paths verified."
if [ "$missing" -gt 0 ]; then
echo "VERDICT: FAIL ($missing required path(s) missing)"
exit 1
fi
echo "VERDICT: PASS"
exit 0