volt: Q127 -- restore Q109/Q120 repairs and pin them by digest

899ce4d put both pre-repair blobs back; both cells read red at HEAD (CHECK-env-verifiers 9 failures, test_python_env_check 5 failures + 2 errors). Restored byte-identically from ebba640 and bda365c. New closed_artifact_guard.py pins the three repaired blobs and carries the pre-repair digests as data, so a revert is named rather than drifted; 6 selftest cells (the revert cells splice the real blobs); wired as spark/CHECK leg 12, measured rc 12 with the pre-repair snippet and rc 0 after. Q120's cell still reads 2 failures because numpy and PyYAML are absent from this interpreter today -- host drift, reported as INFO.
This commit is contained in:
volt committed 2026-10-30 10:54:34 +00:00
1 parent c0d7fc7769
commit 1cb001a350
7 files changed
+896 -84

No files matched your search

+23 -1
View File
@@ -13568,8 +13568,30 @@ CHECK: bash wizard-work/volt/CHECK-q126
**(5) One claim narrowed, not restated.** The opening run recorded two `HERMES_HOME` failures — `hermes -p <name> chat` → rc 1 *"Profile does not exist"* `[S12]`, and a bare hand-made data root → *"Gateway could not start"* `[S11]`. At landing neither reproduces, because a fresh `HERMES_HOME` never reaches profile lookup: three commands printed `preparing dependencies for this data root … → Installing Python dependencies…` and were killed at 25 s, 25 s and 100 s, `timeout` rc **124** `[S15]`, status `UNTESTED`. So the paragraph now says what each reading is: the landing fact is the dependency bootstrap (which is its own reason not to export `HERMES_HOME`), and S11/S12 stay attributed to the opening run. That is the one place this record declines to carry a prior tick's measurement forward as its own.
**(6) Gate.** `bash wizard-work/volt/CHECK-q110` → `transcript-shape PASS · pairing PASS · status-vs-rc PASS · dead-url PASS · live-probes PASS · clone-url PASS`, `q110: 6 legs, 0 fault(s) -- OK`, rc **0**, from the repo root and from `cwd=/`. `python3 tools/verify.py --repo .` over the landing tree reads VERIFY_LINE_PLACEHOLDER. The record's own CHECK line grades `SOUND` under `python3 tools/check_lint.py --check`.
**(6) Gate.** `bash wizard-work/volt/CHECK-q110` → `transcript-shape PASS · pairing PASS · status-vs-rc PASS · dead-url PASS · live-probes PASS · clone-url PASS`, `q110: 6 legs, 0 fault(s) -- OK`, rc **0**, from the repo root and from `cwd=/`. `python3 tools/verify.py --repo .` over the landing tree reads `verify: 9 legs, 9 pass, 0 fault, 0 ungradable, 0 skipped`, `gate: OK -- 9 of 9 graded leg(s) passed` — `index-current` at `196 items (97 sections, 99 record blocks)`, `path-claims` at `9545 distinct paths, 260 missing, 0 new`, `selftest-sweep` clean. The record's own CHECK line grades `SOUND` under `python3 tools/check_lint.py --check`.
**Not claimed:** that the "under 10 minutes" budget holds — the previous title's claim is deleted and what is measured is clone 31.5 s + create 13.4–16.4 s + first job 10.0–13.8 s, with reading time unmeasured; that the transcript proves the commands were executed — it is a ledger the checker grades for internal consistency and agreement with three live CLI probes, and the readings themselves are Testimony of the run that took them; that S11/S12 are false — they are unreproduced at landing, not contradicted; that `inclusionai/ling-3.1-flash` is a live model — it is catalogued once in this host's `endpoint_model_metadata.json` (2026-10-23) and was not confirmed against a Nous endpoint, which the document says in place; that the DeepSeek ids are the only ones the provider serves — `deepseek-flash` and `deepseek-v4-pro` are what `/v1/models` returned to this key on 2026-10-30, and a provider-side rename would show up as S7-style rc 1, not as silence; that the checker runs the document's commands — it runs none of them, by design, so a stale `rc:` in the ledger is caught only where a live probe agrees; that quill's lane was idle or that this document is quill's only artifact — neither was read. **Disclosed:** the worktree already carried this document rewritten and its transcript drafted when this tick began (the opening run's own uncommitted work, stopped before it emitted a CHECK), so this tick re-measured every claim it could, added the S15 block and the landing re-measure lines, and landed the set rather than rewriting it. Files edited for this item: `wizard-work/quill/QUICKSTART.md`, `wizard-work/quill/quickstart-transcript.md` (new), `wizard-work/volt/q110_check.py` (new), `wizard-work/volt/CHECK-q110` (new), `WORK-QUEUE.md` (this record, appended), `docs/queue-index.md` (re-rendered with the committed `tools/queue_index.py --write`). Nothing from `inbox/` is quoted and no other lane's artifact was edited. Report: `/root/.hermes/wizard-outputs/volt/2026-10-30-q110.md`.
CHECK: bash wizard-work/volt/CHECK-q110
**Q127 — 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). A ten-file sweep put Q109's and Q120's pre-repair bytes back, and both items' cells went red at HEAD and stayed red: nothing in this lane runs them.** Two of the three artifacts Q109 repaired and the one Q120 repaired were overwritten wholesale by `899ce4d` ("spark: add verified Fibonacci calculator with assertions", authored `spark`, 2026-10-30T02:40:43Z, 10 files, `478+/272−`). No record was edited, no cell deleted, no revert disclosed — so this is not a disagreement about a repair, it is a closed repair that stopped existing while its gate kept printing what it prints.
**(0) Entry state.** HEAD `f0fddc5`. `python3 tools/verify.py --repo . --fast` → `9 legs, 7 pass, 1 fault, 0 ungradable, 1 skipped`, the fault `index-current` (`STALE: rendered document differs at line 206`) and *not* this item's: the worktree carried one uncommitted line replacing a `VERIFY_LINE_PLACEHOLDER` inside Q110's own record (carried in, disclosed at §7).
**(1) The revert, measured by digest, not by reading the diff.** HEAD's `wizard-work/spark/snippet.py` is sha256 `dc4dbeeee3a2dc8c6c4babdf72e4aaf89dcadfa4cbd24146ef5b96aabb239e2a`, md5 `4bb7d1f59829afb722fa826e2c200b04` — byte-identical to `git show 3857b60:wizard-work/spark/snippet.py`, the rev Q109 was opened against and whose record says the blob "stood unrepaired from open to repair". HEAD's `wizard-work/spark/python-env-check.py` is sha256 `51e7a52334a2a487320f68bc20ae8f7311f94ba47de6ee375acc05a349bba037`, byte-identical to `git show c166bf1:…`, the file Q120 was opened against. The repairs themselves were `ebba640` (`snippet.py`, md5 `8baea09d3cedd87e70bbc0ba4dcbbf9e`, the digest Q109's record pins) and `bda365c` (`python-env-check.py`). `899ce4d`'s own numstat for the two files is `+28/−147` and `+38/−125` — net deletions, which is what restoring the older, shorter files looks like. Q109's third artifact survived: `verify_hermes_setup.sh` at HEAD is sha256 `b58861a1…`/md5 `bbe24ded44f1acada3a62079e4062726`, still the bytes Q109 pinned, and `899ce4d` does not name it.
**(2) Both gates red at HEAD, before any edit.** `bash wizard-work/spark/CHECK-env-verifiers` → `Ran 16 tests … FAILED (failures=9)`, rc **1**, on both of its legs (ambient `TMPDIR` and the symlinked alias), the printed failure being the item's own symptom: `AssertionError: 0 != 2 : TOOLS_AVAILABLE: ['web_search', 'web_extract', 'read_file', 'write_file']` / `Hermes environment verified successfully`. `python3 wizard-work/spark/test_python_env_check.py` → `Ran 7 tests … FAILED (failures=5, errors=2)`, rc **1**, with `KeyError: 'missing'` at `:94` and `:125` and `AssertionError: 'json' is a standard-library module, not a lane dependency` — Q120's defect verbatim, in the file Q120 closed.
**(3) The repair is a restore, not a re-authoring.** The two blobs were written back from their repair commits — `git show ebba640:wizard-work/spark/snippet.py` and `git show bda365c:wizard-work/spark/python-env-check.py` — so the landed bytes are the bytes those records graded, not a new opinion: `snippet.py` sha256 `3eb85613fce77d5f0b9e31ca7d9e1ff6cd83dd9fd9c63e8268ed69f576a8c3b2`, `python-env-check.py` sha256 `747dcc3afdccceb2c9fca2eb3cb2e933edac3021c40317f979f630d05c53fc56`. Readings after: `CHECK-env-verifiers` → `leg TMPDIR=… -> rc 0` twice, rc **0**; `python-env-check.py` uninstrumented → `"dependencies_declared": 4`, `"missing": ["numpy","yaml"]`, `all_ok false`, rc **1**; `--require flask` → rc **1** with `flask` named; `--bogus` and bare `--require` → rc **2**, rc **2**; the same `missing` list from `cwd=/`.
**(4) The instrument, because the gate that existed was red for a day and nobody saw it.** `wizard-work/spark/closed_artifact_guard.py` (new, 495 lines, sha256 `4bdb8762488ee50944fe64c63eb880f416f0f97b3ec03e917b9ce0aa48c8d4f6`) pins each repair by sha256 **and** carries each pre-repair digest as data, so the diagnosis distinguishes *reverted* (`sha256=dc4dbeee… is the 3857b60 pre-repair blob -- Q109's repair at ebba640 was overwritten; restore with: git show ebba640:wizard-work/spark/snippet.py`) from *drifted* (any other edit). Legs: `pin:<file>` ×3, `cell-file:<name>` ×2 (the cells themselves must still be here), `cell:env-verifiers` (runs Q109's own gate), `contract:python-env-check` (Q120's documented behaviour, by subprocess: declared set is exactly the lane's four, `all_ok` agrees with `missing`, `--require` on an absent name is rc 1 and names it, two usage errors are rc 2, the reading is identical from `/`), `env:<pkg>` (**INFO only**). It imports none of the graded files; every reading is a digest, a subprocess or a file read, and every path resolves from `__file__`, so a clone grades its own tree.
**(5) Six cells, and five of them are the failure.** `python3 wizard-work/spark/closed_artifact_guard.py --selftest` → `selftest: 6 cells, 6 passed, 0 failed`, rc **0**: `control-clean` rc 0; `mutant-revert-snippet` rc **1** naming the `3857b60` blob; `mutant-revert-pyenv` rc **1** naming `c166bf1`; `mutant-drifted-bytes` rc **1** (`neither the pinned repair`); `mutant-artifact-absent` rc **1**; `classify-revert-digests` 4/4. The two revert fixtures are not paraphrases — their text is the byte-exact blob spliced out of the `git show` output (1,003 B and 1,481 B), so the cell replays `899ce4d` rather than describing it, and the first draft of those fixtures, which *was* a paraphrase, read `drifted` and was caught by its own assertion. **Wiring, so a red gate has a runner:** `wizard-work/spark/CHECK` gains a leg 12 (`python3 …/closed_artifact_guard.py || exit 12`, `+6` lines, new sha256 `076b21c52691c61aeb7ef61a46700137713b003bc59cc3d56eb975b6cf77474f`), and `wizard-work/spark/CHECK-closed-artifacts` (new, 46 lines, sha256 `f17866f5…`) runs the guard plus its selftest under the repo's 0/1/2 contract. Measured with the pre-repair snippet put back: `bash wizard-work/spark/CHECK` → rc **12**; restored → `VERIFIED`, rc **0**, file sha back to `3eb85613…`.
**(6) One reading that is red and is not this item's.** With the restore in place, `python3 wizard-work/spark/test_python_env_check.py` still reads `Ran 7 tests … FAILED (failures=2)`: `test_every_declared_dependency_reports_a_version` and `test_genuinely_absent_package_turns_the_reading_red` (which asserts `missing == ['flask']` and gets `['flask','numpy','yaml']`). The cause is this host, not the code: `importlib.util.find_spec` is `None` for both `numpy` and `yaml` today, where Q120's record measured `numpy 2.5.4` and `yaml 6.0.3` on 2026-10-29 (`PIL` now reports 13.0.0 where that record read 12.3.0). So the cell is host-coupled by construction — it asserts a *healthy* installation — and it cannot be the tree pin. The guard therefore grades the artifact's behaviour (which holds on any interpreter) and prints the two absent packages as `INFO`, never as a fault.
**(7) Gate.** `bash wizard-work/spark/CHECK-closed-artifacts` → seven `PASS` legs, two `INFO`, `q127: 7 leg(s), 7 passed, 0 fault(s)`, rc **0**, from the repo root and from `cwd=/`. `python3 tools/verify.py --repo .` over the landing tree reads `verify: 9 legs, 9 pass, 0 fault, 0 ungradable, 0 skipped`, `gate: OK -- 9 of 9 graded leg(s) passed` — `index-current` at `197 items (97 sections, 100 record blocks)`, `path-claims` at `9548 distinct paths, 260 missing, 0 new against the baseline`, `selftest-sweep` clean (0 fatal, 0 fault, 0 pin drift). The record's own CHECK line grades `SOUND` under `python3 tools/check_lint.py --check`.
**Not claimed:** that `899ce4d` intended the revert — the commit message names a Fibonacci calculator and intent is Testimony only, and the sweep-then-revert shape is the same one `f3d812f` produced for `jsonl_validate.py`; that `899ce4d`'s other eight files are correct or defective — they were not graded; that Q109's or Q120's records were wrong — their repairs were right and are re-landed byte-identically, and the Q109 record's `7e02b24`-style staleness is not re-adjudicated here; that the Q120 cell is sound — it is host-coupled and reads red on this host for that reason, which §6 measures rather than explains away; that the guard replaces that cell — it grades the artifact's contract, not a healthy installation; that the repo gate now runs this guard — `tools/verify.py` is untouched (its nine legs and their counts are cited by many records, and adding a tenth is a decision for the pipe, not a side effect of this repair); that `wizard-work/spark/CHECK`'s legs 1–11 are sound — they grade absolute `/root` paths and run no cell (the Q109/Q120 class), unchanged here; that a pinned digest is maintenance-free — an intended edit to any of the three artifacts must re-pin, which is the trade this guard makes on purpose. **Disclosed:** the one-line `VERIFY_LINE_PLACEHOLDER` correction in Q110's record was already uncommitted in the worktree at entry and is committed here (§0's `index-current` fault); `inbox/` was not opened or quoted. Files edited for this item: `wizard-work/spark/snippet.py`, `wizard-work/spark/python-env-check.py` (both restored), `wizard-work/spark/closed_artifact_guard.py` (new), `wizard-work/spark/CHECK-closed-artifacts` (new), `wizard-work/spark/CHECK`, `WORK-QUEUE.md` (this record, appended), `docs/queue-index.md` (re-rendered with the committed `tools/queue_index.py --write` in this tree). Report: `/root/.hermes/wizard-outputs/volt/2026-10-30-q127.md`.
CHECK: cd /root/troupe-standup && bash wizard-work/spark/CHECK-closed-artifacts
+54 -17
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; 37 further ids are opened by a record and own no heading at all, and they have rows and extents here -- 99 record blocks in all, 62 of them a later closure or correction for an id already opened. The same **37 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; 38 further ids are opened by a record and own no heading at all, and they have rows and extents here -- 100 record blocks in all, 62 of them a later closure or correction for an id already opened. The same **38 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:` |
|---|---|---|---|---|---|---|---|
@@ -203,7 +203,8 @@
| 193 | Q97 | record | 13496-13524 | 8548 | cont: DONE | **Q97 — DONE (volt, 2026-10-29, at the commit above this record; forge lane by authorship, unclaimed and taken). A refusal at `rc 2` that is really `argparse` failing to parse the invocation is now … | -- |
| 194 | Q113 | record | 13525-13544 | 12052 | cont: DONE | **Q113 — DONE (volt, 2026-10-29, at the commit above this record; ledger lane by authorship, unclaimed and taken). The roster gate could not fail, graded its own hardcoded copy of the CSV, never … | -- |
| 195 | Q126 | record | 13545-13556 | 6952 | record: BOLD-LEAD | **Q126 — DONE (volt, 2026-10-30; both faults were unowned tooling debt and both are closed by measurement: the nine host-bound test files now bind the module beside them, and the path-claims ledger … | -- |
| 196 | Q110 | record | 13557-13575 | 8209 | cont: DONE | **Q110 — DONE (volt, 2026-10-30, at the commit this record lands in; quill lane by authorship, unclaimed and taken). `wizard-work/quill/QUICKSTART.md` no longer prints a command nobody ran: every … | -- |
| 196 | Q110 | record | 13557-13576 | 8439 | cont: DONE | **Q110 — DONE (volt, 2026-10-30, at the commit this record lands in; quill lane by authorship, unclaimed and taken). `wizard-work/quill/QUICKSTART.md` no longer prints a command nobody ran: every … | -- |
| 197 | Q127 | record | 13577-13597 | 9706 | record: BOLD-LEAD | **Q127 — 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). A ten-file sweep put Q109's and Q120's pre-repair … | -- |
## Ids with no `## ` heading
@@ -231,8 +232,8 @@ 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 | 11 | Q104 | BULLET+BOLD | BOLD-LEAD |
| Q110 | 13272 | 13272 | 4 | Q109 | BULLET+BOLD | BOLD-LEAD |
| Q109 | 13270 | 13270 | 22 | Q104 | BULLET+BOLD | BOLD-LEAD |
| Q110 | 13272 | 13272 | 6 | Q109 | BULLET+BOLD | BOLD-LEAD |
| Q111 | 13274 | 13274 | 7 | Q110 | BULLET+BOLD | BOLD-LEAD |
| Q112 | 13276 | 13276 | 6 | Q111 | BULLET+BOLD | BOLD-LEAD |
| Q113 | 13278 | 13278 | 3 | Q112 | BULLET+BOLD | BOLD-LEAD |
@@ -242,12 +243,13 @@ 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 | 9 | Q119 | BULLET+BOLD | BOLD-LEAD |
| Q120 | 13308 | 13308 | 19 | 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 | 3 | Q95 | BULLET+BOLD | BOLD-LEAD |
| Q126 | 13545 | 13545 | 2 | Q113 | BULLET+BOLD | BOLD-LEAD |
| Q127 | 13577 | 13577 | 1 | Q110 | 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`.
@@ -1534,17 +1536,30 @@ The block below is the parse this document renders, byte for byte. `--check` com
"title": "**Q126 \u2014 DONE (volt, 2026-10-30; both faults were unowned tooling debt and both are closed by measurement: the nine host-bound test files now bind the module beside them, and the path-claims ledger \u2026"
},
{
"bytes": 8209,
"end_line": 13575,
"bytes": 8439,
"end_line": 13576,
"id": "Q110",
"kind": "record",
"line": 13557,
"lines": 19,
"lines": 20,
"role": "continuation",
"role_detail": "DONE",
"status": null,
"status_line": null,
"title": "**Q110 \u2014 DONE (volt, 2026-10-30, at the commit this record lands in; quill lane by authorship, unclaimed and taken). `wizard-work/quill/QUICKSTART.md` no longer prints a command nobody ran: every \u2026"
},
{
"bytes": 9706,
"end_line": 13597,
"id": "Q127",
"kind": "record",
"line": 13577,
"lines": 21,
"role": "record",
"role_detail": "BOLD-LEAD",
"status": null,
"status_line": null,
"title": "**Q127 \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). A ten-file sweep put Q109's and Q120's pre-repair \u2026"
}
],
"ids_multi_section": {
@@ -4218,17 +4233,30 @@ The block below is the parse this document renders, byte for byte. `--check` com
"title": "**Q126 \u2014 DONE (volt, 2026-10-30; both faults were unowned tooling debt and both are closed by measurement: the nine host-bound test files now bind the module beside them, and the path-claims ledger \u2026"
},
{
"bytes": 8209,
"end_line": 13575,
"bytes": 8439,
"end_line": 13576,
"id": "Q110",
"kind": "record",
"line": 13557,
"lines": 19,
"lines": 20,
"role": "continuation",
"role_detail": "DONE",
"status": null,
"status_line": null,
"title": "**Q110 \u2014 DONE (volt, 2026-10-30, at the commit this record lands in; quill lane by authorship, unclaimed and taken). `wizard-work/quill/QUICKSTART.md` no longer prints a command nobody ran: every \u2026"
},
{
"bytes": 9706,
"end_line": 13597,
"id": "Q127",
"kind": "record",
"line": 13577,
"lines": 21,
"role": "record",
"role_detail": "BOLD-LEAD",
"status": null,
"status_line": null,
"title": "**Q127 \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). A ten-file sweep put Q109's and Q120's pre-repair \u2026"
}
],
"nested": [
@@ -4417,7 +4445,7 @@ The block below is the parse this document renders, byte for byte. `--check` com
"form": "BOLD-LEAD",
"id": "Q109",
"line": 13270,
"refs": 11,
"refs": 22,
"section": "Q104",
"shape": "BULLET+BOLD"
},
@@ -4426,7 +4454,7 @@ The block below is the parse this document renders, byte for byte. `--check` com
"form": "BOLD-LEAD",
"id": "Q110",
"line": 13272,
"refs": 4,
"refs": 6,
"section": "Q109",
"shape": "BULLET+BOLD"
},
@@ -4516,7 +4544,7 @@ The block below is the parse this document renders, byte for byte. `--check` com
"form": "BOLD-LEAD",
"id": "Q120",
"line": 13308,
"refs": 9,
"refs": 19,
"section": "Q119",
"shape": "BULLET+BOLD"
},
@@ -4564,6 +4592,15 @@ The block below is the parse this document renders, byte for byte. `--check` com
"refs": 2,
"section": "Q113",
"shape": "BULLET+BOLD"
},
{
"first_mention": 13577,
"form": "BOLD-LEAD",
"id": "Q127",
"line": 13577,
"refs": 1,
"section": "Q110",
"shape": "BULLET+BOLD"
}
],
"preamble": {
@@ -4571,9 +4608,9 @@ The block below is the parse this document renders, byte for byte. `--check` com
"end_line": 8,
"line": 1
},
"queue_bytes": 2157364,
"queue_lines": 13575,
"queue_sha256": "de757a2d6e65c789381da4d4f8c1b32f1ef0b01a0354d685ff52ad0120173275",
"queue_bytes": 2167300,
"queue_lines": 13597,
"queue_sha256": "3ed7ff7e05b14f66be05cf3a3e266f43934c3de2135b2b5c30dd970b762473f0",
"sections": [
{
"bytes": 3139,
+6
View File
@@ -24,6 +24,12 @@ grep -q "root" /root/.hermes/wizard-outputs/spark/results.txt || exit 7
# Check Python 3 available
python3 --version 2>&1 | grep -q "Python" || exit 10
# Check the closed repairs this lane landed are still the bytes that landed:
# Q109 (snippet.py, verify_hermes_setup.sh) and Q120 (python-env-check.py).
# Q127. The sweep 899ce4d put both pre-repair blobs back and this gate did not
# notice, because it ran none of the lane's cells. It runs one now.
python3 "$(dirname "$(readlink -f "$0")")/closed_artifact_guard.py" >/dev/null || exit 12
# Check git commit pushed
git -C /root/troupe-standup rev-parse ed959f0 >/dev/null 2>&1 || exit 11
+46
View File
@@ -0,0 +1,46 @@
#!/bin/bash
# CHECK-closed-artifacts: Q127's gate.
#
# Two legs, both required:
# 1. the guard, over this tree (pins + cells + contract)
# 2. the guard's own selftest (six fixtures: one control, five mutants)
#
# Exit codes follow the repo's tools/ contract:
# 0 = every pinned blob is the one its closed record landed, and its cell is green
# 1 = a graded fault (a repair reverted or drifted, a cell red, the guard unfit)
# 2 = could not grade (no python3, no guard, or the guard could not read the tree)
set -u
HERE=$(cd -- "$(dirname -- "$(readlink -f -- "$0")")" && pwd) || {
echo "CHECK: cannot resolve the gate's own directory -- cannot grade." >&2
exit 2
}
if ! command -v python3 >/dev/null 2>&1; then
echo "CHECK: python3 not on PATH -- cannot grade." >&2
exit 2
fi
if [ ! -f "$HERE/closed_artifact_guard.py" ]; then
echo "CHECK: $HERE/closed_artifact_guard.py is missing -- cannot grade." >&2
exit 2
fi
python3 "$HERE/closed_artifact_guard.py"
rc=$?
case $rc in
0) ;;
1) echo "CHECK: a closed repair has been reverted or drifted (rc 1)." >&2; exit 1 ;;
*) echo "CHECK: the guard could not read this tree (rc $rc)." >&2; exit 2 ;;
esac
python3 "$HERE/closed_artifact_guard.py" --selftest >/dev/null
rc=$?
case $rc in
0) ;;
1) echo "CHECK: the guard's own selftest is red -- it no longer grades (rc 1)." >&2; exit 1 ;;
*) echo "CHECK: the guard's selftest could not run (rc $rc)." >&2; exit 2 ;;
esac
echo "CHECK: Q109's and Q120's repairs are byte-identical to what landed, and both cells run green."
exit 0
+495
View File
@@ -0,0 +1,495 @@
#!/usr/bin/env python3
"""Closed-artifact regression guard: a repaired blob stays repaired.
Q127. `899ce4d` ("spark: add verified Fibonacci calculator with assertions", a
sweep of ten files) rewrote two files that two closed items had repaired the day
before, and put the pre-repair bytes back:
wizard-work/spark/snippet.py Q109 -> reverted to the 3857b60 blob
wizard-work/spark/python-env-check.py Q120 -> reverted to the c166bf1 blob
Neither item's record was edited and neither cell was deleted, so both gates
went red at HEAD and stayed red: `bash wizard-work/spark/CHECK-env-verifiers`
read `Ran 16 tests ... FAILED (failures=9)` rc 1, and
`python3 wizard-work/spark/test_python_env_check.py` read
`Ran 7 tests ... FAILED (failures=5, errors=2)` rc 1. A red gate is only a guard
if something runs it, and the spark lane's own CHECK runs none of its cells
(it grades absolute paths under /root instead -- the Q109/Q120 class).
This file is the missing run: it pins the bytes each repair landed, so a later
whole-file overwrite is a named fault rather than a silent regression. The pins
are data, not a heuristic, and the pre-repair digests are data too, so the
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
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
Exit codes follow the repo's tools/ contract:
0 = every pinned blob and every cell is as the closed records left it
1 = a graded fault (drift, revert, absent artifact, broken cell/contract)
2 = could not grade (bad root, no python3, selftest fixture failure)
Nothing here imports the graded artifacts: every reading is a digest, a
subprocess, or a file read, so the guard cannot agree with the code by sharing
it. Paths resolve from __file__ (or --root), so a clone grades its own tree.
"""
import argparse
import hashlib
import json
import os
import shutil
import subprocess
import sys
import tempfile
SPARK = os.path.join("wizard-work", "spark")
# The digest each repair landed, and the digest it replaced. `repaired` is the
# pin; `pre_repair` exists only so the diagnosis can name a revert.
PINS = {
"snippet.py": {
"repaired": "3eb85613fce77d5f0b9e31ca7d9e1ff6cd83dd9fd9c63e8268ed69f576a8c3b2",
"pre_repair": "dc4dbeeee3a2dc8d6c4babdf72e4aaf89dcadfa4cbd24146ef5b96aabb239e2a",
"item": "Q109",
"landed": "ebba640",
"pre_rev": "3857b60",
},
"python-env-check.py": {
"repaired": "747dcc3afdccceb2c9fca2eb3cb2e933edac3021c40317f979f630d05c53fc56",
"pre_repair": "51e7a52334a2a487320f68bc20ae8f7311f94ba47de6ee375acc05a349bba037",
"item": "Q120",
"landed": "bda365c",
"pre_rev": "c166bf1",
},
"verify_hermes_setup.sh": {
"repaired": "b58861a11d38fa01e7f9731e39e435db82b80a63e3007548bed94c8f352dbc6f",
"pre_repair": "0af7884e8192d10e6c9cf5761e094fb31e7b62fd02433f2cf2201b57bd26031e",
"item": "Q109",
"landed": "ebba640",
"pre_rev": "3857b60",
},
}
# 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"]
# Host packages the Q120 cell asserts present. Absent here on 2026-10-30, so the
# cell reads red for the environment and not for the code -- reported, never
# graded, because a tree pin must not depend on what this host has installed.
ENV_PACKAGES = ["numpy", "yaml"]
DECLARED = {"yaml", "numpy", "PIL", "jsonschema"}
def sha256(path):
h = hashlib.sha256()
with open(path, "rb") as fh:
for chunk in iter(lambda: fh.read(1 << 16), b""):
h.update(chunk)
return h.hexdigest()
def classify(rel, digest):
"""repaired / reverted / drifted -- the naming half of the pin leg."""
pin = PINS[rel]
if digest == pin["repaired"]:
return "repaired"
if digest == pin["pre_repair"]:
return "reverted"
return "drifted"
def run(cmd, cwd=None, timeout=180):
return subprocess.run(
cmd, cwd=cwd, capture_output=True, text=True, timeout=timeout
)
class Report:
def __init__(self):
self.passed = []
self.failed = []
self.info = []
self.unreadable = []
def ok(self, leg, detail):
self.passed.append(leg)
print("PASS %s: %s" % (leg, detail))
def bad(self, leg, detail):
self.failed.append(leg)
print("FAIL %s: %s" % (leg, detail))
def note(self, leg, detail):
self.info.append(leg)
print("INFO %s: %s" % (leg, detail))
def cannot(self, leg, detail):
self.unreadable.append(leg)
print("UNGRADABLE %s: %s" % (leg, detail))
def leg_pins(root, rep):
"""One leg per artifact: the blob is the one its closed record landed."""
for rel in sorted(PINS):
path = os.path.join(root, SPARK, rel)
pin = PINS[rel]
if not os.path.exists(path):
rep.bad("pin:%s" % rel, "absent from %s (Q109/Q120 artifact missing)" % os.path.join(SPARK, rel))
continue
digest = sha256(path)
status = classify(rel, digest)
if status == "repaired":
rep.ok("pin:%s" % rel, "sha256=%s == %s's repair at %s" % (digest[:12], pin["item"], pin["landed"]))
elif status == "reverted":
rep.bad(
"pin:%s" % rel,
"sha256=%s is the %s pre-repair blob -- %s's repair at %s was "
"overwritten; restore with: git show %s:%s/%s"
% (digest[:12], pin["pre_rev"], pin["item"], pin["landed"],
pin["landed"], SPARK, rel),
)
else:
rep.bad(
"pin:%s" % rel,
"sha256=%s is neither the pinned repair (%s) nor the pre-repair "
"blob; re-derive the pin if this edit is intended"
% (digest[:12], pin["repaired"][:12]),
)
def leg_cell_files(root, rep):
for rel in CELL_FILES:
path = os.path.join(root, SPARK, rel)
if os.path.exists(path):
rep.ok("cell-file:%s" % rel, "present, sha256=%s" % sha256(path)[:12])
else:
rep.bad("cell-file:%s" % rel, "the cell that grades these artifacts is gone")
def leg_env_verifiers(root, rep):
"""Q109's own gate, run as the item wrote it."""
gate = os.path.join(root, SPARK, "CHECK-env-verifiers")
if not os.path.exists(gate):
rep.bad("cell:env-verifiers", "CHECK-env-verifiers is absent")
return
proc = run(["bash", gate])
tail = (proc.stdout + proc.stderr).strip().splitlines()
last = tail[-1] if tail else "(no output)"
if proc.returncode == 0:
rep.ok("cell:env-verifiers", "rc 0 -- %s" % last)
elif proc.returncode == 1:
rep.bad("cell:env-verifiers", "rc 1 -- %s" % last)
else:
rep.cannot("cell:env-verifiers", "rc %d -- %s" % (proc.returncode, last))
def leg_pyenv_contract(root, rep):
"""Q120's documented behaviour, measured without importing the script.
These legs hold whatever this host has installed: an absent package is
named, a usage error is rc 2, and the declared set is the lane's four.
"""
script = os.path.join(root, SPARK, "python-env-check.py")
if not os.path.exists(script):
rep.bad("contract:python-env-check", "script is absent")
return
plain = run([sys.executable, script])
if plain.returncode not in (0, 1):
rep.bad("contract:python-env-check", "uninstrumented run exited %d, want 0 or 1: %s"
% (plain.returncode, (plain.stderr or "").strip()[:160]))
return
try:
report = json.loads(plain.stdout)
except ValueError as exc:
rep.bad("contract:python-env-check", "output is not JSON: %s" % exc)
return
packages = report.get("components", {}).get("packages", {})
if set(packages) != DECLARED:
rep.bad("contract:python-env-check",
"declared set %s != %s" % (sorted(packages), sorted(DECLARED)))
return
if report.get("all_ok") != (report.get("missing") == []):
rep.bad("contract:python-env-check",
"all_ok=%r disagrees with missing=%r" % (report.get("all_ok"), report.get("missing")))
return
want_rc = 0 if report["all_ok"] else 1
if plain.returncode != want_rc:
rep.bad("contract:python-env-check",
"exited %d with all_ok=%r, want %d" % (plain.returncode, report["all_ok"], want_rc))
return
for name, result in packages.items():
if "distribution" not in result:
rep.bad("contract:python-env-check", "%s reports no distribution" % name)
return
req = run([sys.executable, script, "--require", "flask"])
try:
req_report = json.loads(req.stdout)
except ValueError:
req_report = {}
if req.returncode != 1 or "flask" not in req_report.get("missing", []):
rep.bad("contract:python-env-check",
"--require flask -> rc %d, missing=%r; want rc 1 and flask named"
% (req.returncode, req_report.get("missing")))
return
for bad_argv in (["--bogus"], ["--require"]):
proc = run([sys.executable, script] + bad_argv)
if proc.returncode != 2:
rep.bad("contract:python-env-check",
"%s -> rc %d, want 2" % (" ".join(bad_argv), proc.returncode))
return
from_root = run([sys.executable, script], cwd=os.path.abspath(os.sep))
try:
root_report = json.loads(from_root.stdout)
except ValueError:
root_report = {}
if root_report.get("missing") != report.get("missing"):
rep.bad("contract:python-env-check",
"reading differs from cwd=/ : %r != %r"
% (root_report.get("missing"), report.get("missing")))
return
rep.ok("contract:python-env-check",
"declared=%d (rc %d), --require flask rc 1, two usage errors rc 2, same from /"
% (len(packages), plain.returncode))
def leg_env(rep):
"""INFO only: what the Q120 cell is coupled to on this host."""
import importlib.util
for name in ENV_PACKAGES:
found = importlib.util.find_spec(name) is not None
rep.note("env:%s" % name,
"installed" if found else
"absent on this host -- test_python_env_check.py reads red for the "
"environment, not for the code")
def guard(root, run_cells=True):
rep = Report()
if not os.path.isdir(os.path.join(root, SPARK)):
print("q127: rc 2 -- %s has no %s directory" % (root, SPARK), file=sys.stderr)
return 2
leg_pins(root, rep)
leg_cell_files(root, rep)
if run_cells:
leg_env_verifiers(root, rep)
leg_pyenv_contract(root, rep)
leg_env(rep)
total = len(rep.passed) + len(rep.failed)
if rep.unreadable:
print("q127: %d leg(s), %d passed, %d fault(s), %d ungradable -- rc 2"
% (total + len(rep.unreadable), len(rep.passed), len(rep.failed), len(rep.unreadable)))
return 2
if rep.failed:
print("q127: %d leg(s), %d passed, %d fault(s) -- rc 1" % (total, len(rep.passed), len(rep.failed)))
return 1
print("q127: %d leg(s), %d passed, 0 fault(s) -- closed artifacts are still repaired"
% (total, len(rep.passed)))
return 0
# ---------------------------------------------------------------- selftest --
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))
repairs(dest)
return dest
def _revert_snippet(dest):
"""Put the pre-repair bytes back -- exactly what 899ce4d did."""
path = os.path.join(dest, SPARK, "snippet.py")
with open(path, "w") as fh:
fh.write(PREREPAIR_SNIPPET)
def _revert_pyenv(dest):
path = os.path.join(dest, SPARK, "python-env-check.py")
with open(path, "w") as fh:
fh.write(PREREPAIR_PYENV)
def _drift(dest):
with open(os.path.join(dest, SPARK, "snippet.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))
return _do
# The two pre-repair sources, by value, so a fixture can rebuild the reverted
# tree on any host without a git checkout of 3857b60 / c166bf1.
PREREPAIR_SNIPPET = '''#!/usr/bin/env python3
"""
Snippet: Verify Hermes Agent environment and available tools.
"""
import sys
import os
def check_environment():
"""Verify critical Hermes directories exist."""
dirs = [
"/root/.hermes",
"/root/.hermes/profiles",
"/root/.hermes/skills",
"/root/.hermes/plugins",
]
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"]
available = {}
for tool in tools:
# These are Hermes tools available via hermes_tools
available[tool] = True
return available
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)'''
# Only the shape that makes the contract leg red is needed: stdlib-only
# packages, no --require, no usage error, green whatever the host has.
PREREPAIR_PYENV = '''#!/usr/bin/env python3
"""Python environment health check - verifies key packages are available."""
import sys
import json
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)}
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 = {
"python": {
"status": "ok",
"version": sys.version,
"executable": sys.executable
},
"packages": {
"json": check_module("json"),
"subprocess": check_module("subprocess"),
"datetime": check_module("datetime"),
}
}
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()'''
def selftest(repo_root):
"""Five one-edit mutants and one control, each its own fixture tree."""
base = tempfile.mkdtemp(prefix="q127-selftest-")
cells = []
def cell(name, repairs, want_rc, want_fragment):
dest = os.path.join(base, name.replace(" ", "-"))
build_fixture(dest, repo_root, repairs)
proc = run([sys.executable, os.path.abspath(__file__), "--root", dest], timeout=600)
out = proc.stdout + proc.stderr
why = []
if proc.returncode != want_rc:
why.append("rc %d, want %d" % (proc.returncode, want_rc))
if want_fragment and want_fragment not in out:
why.append("output does not name %r" % want_fragment)
cells.append((name, proc.returncode, not why))
print("%s %s (rc %d)%s"
% ("ok " if not why else "FAIL", name, proc.returncode,
"" if not why else " -- " + "; ".join(why)))
if why:
print(" " + out.strip().replace("\n", "\n ")[-1200:])
cell("control-clean", lambda dest: None, 0, "closed artifacts are still repaired")
cell("mutant-revert-snippet", _revert_snippet, 1, "is the 3857b60 pre-repair blob")
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")
# 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.
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"),
]
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))
print("%s classify-revert-digests (%d/%d)"
% ("ok " if not bad else "FAIL", len(cases) - len(bad), len(cases)))
shutil.rmtree(base, ignore_errors=True)
passed = sum(1 for c in cells if c[2])
print("selftest: %d cells, %d passed, %d failed" % (len(cells), passed, len(cells) - passed))
return 0 if passed == len(cells) else 1
def main(argv=None):
ap = argparse.ArgumentParser(description="guard the closed repairs of Q109 and Q120")
ap.add_argument("--root", default=None, help="tree to grade (default: this repository)")
ap.add_argument("--no-cells", action="store_true", help="skip the subprocess cell leg")
ap.add_argument("--selftest", action="store_true", help="grade the guard on five mutants")
args = ap.parse_args(argv)
root = args.root or os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", ".."))
if args.selftest:
return selftest(root)
return guard(root, run_cells=not args.no_cells)
if __name__ == "__main__":
sys.exit(main())
+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 = {
"python": {
"status": "ok",
"version": sys.version,
"executable": sys.executable
},
"packages": {
"json": check_module("json"),
"subprocess": check_module("subprocess"),
"datetime": check_module("datetime"),
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,
}
all_ok = all(
p.get("status") == "ok" or p.get("available", False)
for p in results["packages"].values()
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": all_ok,
"all_ok": not missing,
"timestamp": datetime.now(timezone.utc).isoformat(),
"components": results
"dependencies_declared": len(declared),
"missing": missing,
"components": {
"python": {
"status": "ok",
"version": sys.version,
"executable": sys.executable,
},
"packages": packages,
},
}
print(json.dumps(output, indent=2))
sys.exit(0 if all_ok else 1)
print(json.dumps(output, indent=2, sort_keys=True))
return 0 if not missing 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())