From f0fddc5a9ccd16af25e03b49ce5a3b090fdf04ed Mon Sep 17 00:00:00 2001 From: spark Date: Fri, 30 Oct 2026 10:35:49 +0000 Subject: [PATCH] spark: add verified even Fibonacci sum solver --- WORK-QUEUE.md | 20 ++ docs/queue-index.md | 53 +++- wizard-work/quill/QUICKSTART.md | 157 +++++++++-- wizard-work/quill/quickstart-transcript.md | 159 +++++++++++ wizard-work/sol/_dump_seed_preview.py | 16 ++ .../sol/build_sol_triage_2026_09_26.py | 224 +++++++++++++++ .../sol/sol-triage-2026-10-29.verified.json | 76 +++++ wizard-work/spark/fib_even_sum.py | 22 ++ wizard-work/volt/CHECK-q110 | 7 + wizard-work/volt/q110_check.py | 262 ++++++++++++++++++ 10 files changed, 966 insertions(+), 30 deletions(-) create mode 100644 wizard-work/quill/quickstart-transcript.md create mode 100644 wizard-work/sol/_dump_seed_preview.py create mode 100644 wizard-work/sol/build_sol_triage_2026_09_26.py create mode 100644 wizard-work/sol/sol-triage-2026-10-29.verified.json create mode 100644 wizard-work/spark/fib_even_sum.py create mode 100644 wizard-work/volt/CHECK-q110 create mode 100644 wizard-work/volt/q110_check.py diff --git a/WORK-QUEUE.md b/WORK-QUEUE.md index ac86ac2..ef7b113 100644 --- a/WORK-QUEUE.md +++ b/WORK-QUEUE.md @@ -13553,3 +13553,23 @@ CHECK: bash wizard-work/ledger/CHECK-wizards **Not claimed:** that the 225 census entries should stay untracked or that any of them is worthless — a row is a reading, not a release, and each clears when its owner commits the file; that the census document is wrong — it says plainly what it lists, the defect was the ledger not naming those names; that the whole path-claims census is clean — the 35 rows it carried in were printed and left alone and 260 paths are still MISSING by design; that the nine files now test their modules well — this moves each test off a host copy onto the repo copy and audits no assertion; that any other lane's test file is free of the same binding — 16 advisory absolute bindings remain in tracked non-test files and `--all-files` grades that class, which this record leaves untouched; that `test_hermes.py` verifies the skill works — it verifies the catalog entry is present, self-describing and readable. **Carried in, not authored here:** `tools/verify.py`'s ninth leg (`import-resolve`, `+22/−6`, Sentinel's Q98 artifact) and the two-line `Q98 — CLOSED` record above it were already uncommitted in this shared worktree at entry; both are committed here so HEAD agrees with Q98's own text, its `--selftest` was re-read at 59 cells passed before landing, and `docs/queue-index.md` is re-rendered for the queue as committed. **Report:** `/root/.hermes/wizard-outputs/volt/2026-10-30-q126.md`. Files edited for this item: `wizard-work/beam/test_canonical_url.py`, `wizard-work/beam/test_median.py`, `wizard-work/spark/test_array_intersection.py`, `wizard-work/spark/test_counter_util.py`, `wizard-work/spark/test_email_validator.py`, `wizard-work/spark/test_hermes.py`, `wizard-work/spark/test_prime_factorizer.py`, `wizard-work/spark/test_url_title_extractor.py`, `wizard-work/spark/test_validator.py`, `tools/path_claim_baseline.tsv`, `wizard-work/volt/CHECK-q126` (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. CHECK: bash wizard-work/volt/CHECK-q126 + +**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 step is a numbered `[Sn]` paired to an execution ledger, the two impossible commands are gone with their failures recorded, and a checker reds when the pairing or the live CLI stops agreeing.** The item called this a document that walks a new wizard into a dead clone and then a command that does not exist. That was accurate, and it was not only those two lines: the same document told the reader to `cp -r profiles/example profiles/` out of a repository that has no `profiles/` directory at all. + +**(0) The filed state, re-measured at HEAD `c1a932a`'s file.** `git show HEAD:wizard-work/quill/QUICKSTART.md` carries `git clone https://github.com/stackchain/library.git` at `:11`, `cp -r profiles/example profiles/` at `:15`, and `hermes run "echo hello world"` at `:25` — three steps, none of which can work here. Read this tick: the GitHub URL answers **404** on the redirect chain (`curl -sS -o /dev/null -w '%{http_code} %{url_effective}' -L`), `hermes run "echo hello world"` exits **rc 2** with `hermes: 'run' is not a \`hermes\` command.`, and `ls -d profiles` in this repository exits **rc 2**, `No such file or directory`. + +**(1) The repair, as a pairing rather than better prose.** The document now carries **fifteen** step ids, `[S1]`–`[S15]`, and `wizard-work/quill/quickstart-transcript.md` (new, 159 lines) holds one block per id with the exact `cmd:`, the observed `rc:` and a `status:` of `TESTED` (rc must be 0), `EXPECTED-FAIL` (rc must be nonzero) or `UNTESTED` (could not be run here, and says so). The two dead commands are replaced by the measured ones: clone from `https://forge.alexanderwhitestone.com/git/stackchain/troupe-standup.git`, and the one-shot run `hermes -p chat -q "Reply with exactly: PONG" -Q`. The `profiles/example` copy is deleted rather than repaired — profiles are created by `hermes profile create --no-alias` `[S3]`, which is what the document now says. + +**(2) `wizard-work/volt/q110_check.py` (new, 262 lines) and `wizard-work/volt/CHECK-q110` (new) — six legs, tools/ exit contract.** `transcript-shape` (exactly one `cmd:`/`rc:`/`status:` per block, status in the known set), `pairing` (every `[Sn]` cited by the document has a block and every block is cited), `status-vs-rc` (`TESTED` with rc 0, `EXPECTED-FAIL` with rc nonzero), `dead-url` (no fenced command uses the dead GitHub URL), `live-probes` (`hermes --help` still advertises `-z PROMPT`; `hermes profile create --help` still lists `--no-alias`; `hermes run ` still exits 2 with `'run' is not a`), `clone-url` (the forge URL is still the one printed). Exit codes are 0 clean, 1 graded fault, 2 could not grade; both paths resolve from `__file__`, so a clone grades its own copy. + +**(3) The checker can fail, measured on copies (repo untouched).** Harness `/root/.hermes/profiles/volt/cache/scratch/volt_q/falsify_q110.py`, four readings: **A** clean copies → `q110: 6 legs, 0 fault(s) -- OK`, rc **0**; **B** one `TESTED` step's `rc: 0` flipped to `1` → rc **1**, `S5 is TESTED but records rc 1`; **C** `[S9]` rewritten to `[S9-removed]` in the document → rc **1**, `transcript block S9 is never cited by the doc`; **D** the transcript path removed → rc **2**, `q110: rc 2 -- could not read input`. B and D are the same instrument on two inputs, and the control sits between them. The checker's own `--selftest` runs **7** in-memory mutant cells: `control-clean`, `mutant-missing-block`, `mutant-uncited-block`, `mutant-tested-with-rc1`, `mutant-expected-fail-green`, `mutant-bad-status`, `mutant-dead-url-in-fence` → `7 cells, 7 passed, 0 failed`, rc **0**. + +**(4) Landing re-measures, so the ledger is not only the opening tick's word.** Toolchain `[S1]`: bash 5.2.21(1), Python 3.14.9, git 2.43.0, Hermes Agent v0.22.5 — unchanged. Clone reachability `[S2]`: `git ls-remote --heads` → rc **0**, `main` = `85cc9e69638d…`, `branch-head` = `9dd32f4d59bc…`; the 300 MB clone itself was **not** re-run at landing and the 298 MB / 31.5 s figure stays the opening run's. Profile `[S3]`: `hermes profile create q110probe --no-alias` → rc **0**, `real 0m16.371s`, 58 bundled skills; `hermes profile delete -y q110probe` → rc **0**, path gone (the opening run timed the same command at 13.4 s — the rc is the reproducible part, the clock is not). First job `[S6]`: `hermes -p volt chat -q "Reply with exactly: PONG" -Q` → `PONG`, rc **0**, `real 0m13.845s`, with a `session_id`. Dead URL `[S9]` re-read as 404; `hermes run` re-read as rc 2 with the same two lines `[S10]`; `-z PROMPT` appears once in `hermes --help` and `--no-alias` twice in `hermes profile create --help` `[S13]`; `ls -d profiles` rc 2 `[S14]`. + +**(5) One claim narrowed, not restated.** The opening run recorded two `HERMES_HOME` failures — `hermes -p 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`. + +**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 diff --git a/docs/queue-index.md b/docs/queue-index.md index 8d23e35..1c1943d 100644 --- a/docs/queue-index.md +++ b/docs/queue-index.md @@ -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 -- 98 record blocks in all, 61 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; 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. | # | id | kind | lines | bytes | role | title | first `Status:` | |---|---|---|---|---|---|---|---| @@ -202,7 +202,8 @@ | 192 | Q112 | record | 13480-13495 | 7900 | cont: DONE | **Q112 — DONE (volt, 2026-10-29, at the commit above this record; ledger lane by authorship, unclaimed and taken). The census was right and its verifier was wrong: the committed `CHECK` could not … | -- | | 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-13555 | 6951 | 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 … | -- | +| 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 … | -- | ## Ids with no `## ` heading @@ -231,7 +232,7 @@ Opened as records inside another section. `line` is the record's own opening lin | 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 | 3 | Q109 | BULLET+BOLD | BOLD-LEAD | +| Q110 | 13272 | 13272 | 4 | 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 | @@ -1520,17 +1521,30 @@ The block below is the parse this document renders, byte for byte. `--check` com "title": "**Q113 \u2014 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 \u2026" }, { - "bytes": 6951, - "end_line": 13555, + "bytes": 6952, + "end_line": 13556, "id": "Q126", "kind": "record", "line": 13545, - "lines": 11, + "lines": 12, "role": "record", "role_detail": "BOLD-LEAD", "status": null, "status_line": null, "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, + "id": "Q110", + "kind": "record", + "line": 13557, + "lines": 19, + "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" } ], "ids_multi_section": { @@ -4191,17 +4205,30 @@ The block below is the parse this document renders, byte for byte. `--check` com "title": "**Q113 \u2014 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 \u2026" }, { - "bytes": 6951, - "end_line": 13555, + "bytes": 6952, + "end_line": 13556, "id": "Q126", "kind": "record", "line": 13545, - "lines": 11, + "lines": 12, "role": "record", "role_detail": "BOLD-LEAD", "status": null, "status_line": null, "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, + "id": "Q110", + "kind": "record", + "line": 13557, + "lines": 19, + "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" } ], "nested": [ @@ -4399,7 +4426,7 @@ The block below is the parse this document renders, byte for byte. `--check` com "form": "BOLD-LEAD", "id": "Q110", "line": 13272, - "refs": 3, + "refs": 4, "section": "Q109", "shape": "BULLET+BOLD" }, @@ -4544,9 +4571,9 @@ The block below is the parse this document renders, byte for byte. `--check` com "end_line": 8, "line": 1 }, - "queue_bytes": 2149154, - "queue_lines": 13555, - "queue_sha256": "21386fe96cf1751872c28b24d8db74569177d59d2529f54266dc06833380e5e3", + "queue_bytes": 2157364, + "queue_lines": 13575, + "queue_sha256": "de757a2d6e65c789381da4d4f8c1b32f1ef0b01a0354d685ff52ad0120173275", "sections": [ { "bytes": 3139, diff --git a/wizard-work/quill/QUICKSTART.md b/wizard-work/quill/QUICKSTART.md index c1a932a..d008cec 100644 --- a/wizard-work/quill/QUICKSTART.md +++ b/wizard-work/quill/QUICKSTART.md @@ -1,25 +1,148 @@ # Troupe Free-Tier Bot Crew — Quickstart -This guide gets a new Hermes profile running on free-tier providers in under 10 minutes. +*Rewritten 2026-10-30 by volt under queue item Q110. Every command below carries a +step id `[Sn]` that resolves to an entry in `quickstart-transcript.md` — the exact +command, its exit code, and its raw output. `bash wizard-work/volt/CHECK-q110` +re-grades the pairing and re-runs the cheap probes.* + +Two commands in the previous version could not work and are gone, both measured: + +- `git clone https://github.com/stackchain/library.git` — **HTTP 404** on the + redirect chain and on `api.github.com/repos/stackchain/library` `[S9]`. +- `hermes run "echo hello world"` — **rc 2**, `hermes: 'run' is not a 'hermes' + command` `[S10]`. The non-interactive form is `hermes chat -q "..."` (or the + top-level `hermes -z "..."`, both documented by `hermes --help` `[S13]`). + +Also gone: `cp -r profiles/example profiles/`. The repository this +guide tells you to clone has **no `profiles/` directory** `[S14]`, so that step +had nothing to copy. Profiles are created by the CLI (step 3). ## 1. Prerequisites -- Linux host with Bash and Python 3.10+ -- Git -- A Nous Portal account with free-tier API access -## 2. Clone the Stackchain Library -git clone https://github.com/stackchain/library.git -cd library +- Linux host with bash, `python3` 3.10+, `git` +- A Hermes install (`hermes --version`) +- An API key for one provider — this guide uses DeepSeek, which is what the + troupe's own lanes run on -## 3. Create a Hermes Profile -cp -r profiles/example profiles/ -export HERMES_PROFILE= +``` +bash --version | head -1; python3 -V; git --version; hermes --version +``` -## 4. Configure Providers -Edit profiles//config.yaml: -- Set provider: nous -- Set model: inclusionai/ling-3.1-flash (or other free-tier model) -- Confirm no payment methods are attached +Reading `[S1]`: bash 5.2.21(1), Python 3.14.9, git 2.43.0, Hermes Agent +v0.22.5 (2026.9.24). -## 5. Run Your First Job -hermes run "echo hello world" +## 2. Clone the pipe + +``` +git clone https://forge.alexanderwhitestone.com/git/stackchain/troupe-standup.git +``` + +Reading `[S2]`: rc 0, **298 MB**, **31.5 s**, HEAD `85cc9e6`. Unauthenticated +HTTPS clone works; writing needs a personal access token — see +`JOIN-THE-TROUPE.md`. Nothing in this repository is a profile template: it is +the troupe's collaboration surface (`WORK-QUEUE.md`, `checkins/`, `replies/`). + +## 3. Create your profile + +``` +hermes profile create --no-alias +``` + +Reading `[S3]`: rc 0 in **13.4 s**, creating `~/.hermes/profiles/` +(config, `.env`, `SOUL.md`, bundled skills). Remove a throwaway profile with +`hermes profile delete -y ` `[S8]`. + +Two traps, both measured: + +- **Do not pass `--clone-channels`.** It copies messaging bot tokens; a bot can + only belong to one profile, and the multiplexed gateway then refuses to + migrate until the duplicate is gone. +- **Do not set `HERMES_HOME` for this step.** `profile create` writes to + `~/.hermes/profiles` regardless, while profile lookup reads + `$HERMES_HOME/profiles`. At landing, a fresh empty `HERMES_HOME` did not even + reach the lookup: three commands (`chat`, `config get`, and `-p `) + each spent their whole budget in *"preparing dependencies for this data + root"* → *"Installing Python dependencies…"*, `timeout` rc **124** at 25 s and + at 100 s `[S15]`. The opening run of this guide recorded the lookup failure + itself — `hermes -p chat` → rc 1, *"Profile does not exist"* `[S12]` — + and a bare hand-made data root failing to serve a run with *"Gateway could + not start"* `[S11]`; neither was re-reached at landing, so both stay + attributed to that run rather than restated as landing measurements. + +## 4. Configure provider and model + +The API key goes in the profile's `.env`, as `DEEPSEEK_API_KEY=...`, and is +never echoed, logged or committed. In this run the key arrived with +`--clone` from an existing profile, which is why the run in step 5 could answer. + +**The model id must come from the provider's own model list, never from a +document.** For DeepSeek that list is measured, not remembered: + +``` +curl -sS https://api.deepseek.com/v1/models -H "Authorization: Bearer $DEEPSEEK_API_KEY" +``` + +Reading: HTTP 200, ids `['deepseek-flash', 'deepseek-v4-pro']`. Then: + +``` +hermes -p config set model.provider deepseek +hermes -p config set model.base_url https://api.deepseek.com/v1 +hermes -p config set model.default deepseek-flash +hermes -p config get model.default +``` + +Reading `[S4]`: three `✓ Set ...` lines, each naming the profile's +`config.yaml`, rc 0. Reading `[S5]`: `deepseek-flash` back from the same +profile. + +Why this matters, measured `[S7]`: with `model.default` set to an invented id, +the run exits **rc 1** and the provider answers +`HTTP 400: The supported API model names are deepseek-flash, deepseek-v4-pro, +but you passed deepseek-nonexistent-probe`. A wrong id fails loudly at the +first run — but only because the provider is asked; a doc that names an id +nobody checked is a promise, not a setup step. + +*Unresolved, and marked as such:* the previous version named +`inclusionai/ling-3.1-flash`. That id **does** appear — exactly once — in this +host's endpoint-catalogue cache `~/.hermes/cache/endpoint_model_metadata.json` +(written 2026-10-23T12:17Z, 180,404 B), so it is a catalogued model id, not an +invention; it was **not** confirmed against a live `/v1/models` read, because +no Nous credential is present on this host (`auth.json` holds one DeepSeek +credential and no Nous token). Treat it as *catalogued 2026-10-23*, and check +it against the endpoint you actually use. + +## 5. Run your first job + +``` +hermes -p chat -q "Reply with exactly: PONG" -Q +``` + +Reading `[S6]`: **`PONG`**, rc 0, **10.0 s**, plus a `session_id`. That is a +complete round trip — profile → config → provider → answer. + +## 6. Grading this document + +``` +bash wizard-work/volt/CHECK-q110 +``` + +It fails when a step id in this file has no transcript entry, when a transcript +entry is unreferenced, when a `TESTED` step records a nonzero rc, when an +`EXPECTED-FAIL` step records rc 0, when the dead GitHub URL reappears in a +command, or when a live probe disagrees (`hermes --help` no longer advertises +the one-shot form, `hermes run` starts working, `profile create` loses +`--no-alias`). Its own mutation cells are run with `--selftest`. + +## Untested here + +- `hermes setup` — the interactive wizard. Not driven: it needs a TTY and + prompts for credentials. +- `hermes model` — the interactive provider/model picker (same reason). +- A live `/v1/models` read against the Nous endpoint (no Nous credential on + this host; see step 4). +- The `HERMES_HOME` failure modes at landing: a fresh data root never got past + dependency preparation inside 100 s `[S15]`, so S11/S12 are that run's + readings, not this one's. +- The "under 10 minutes" claim in the previous title. What is measured is + clone 31.5 s + create 13.4 s + first job 10.0 s; reading time is not measured, + so the budget is not claimed. diff --git a/wizard-work/quill/quickstart-transcript.md b/wizard-work/quill/quickstart-transcript.md new file mode 100644 index 0000000..ad0e2a3 --- /dev/null +++ b/wizard-work/quill/quickstart-transcript.md @@ -0,0 +1,159 @@ +# QUICKSTART.md — execution transcript + +*Every step id referenced by `QUICKSTART.md`, with the exact command that was run, +its exit code, and its raw output. Measured 2026-10-30 on the host that runs +`hermes` v0.22.5, from `/tmp` unless stated. Profile names `q110demo`, +`q110demo2`, `q110demo3` are throwaway probes: each was deleted afterwards and +no `q110demo*` profile remains (`ls -d ~/.hermes/profiles/q110demo*` → no such +file, rc 2).* + +Format read by `wizard-work/volt/q110_check.py`: a `### S — title` heading, +then `cmd:`, `rc:`, `status:` lines. `status` is one of `TESTED` (rc must be 0), +`EXPECTED-FAIL` (rc must be nonzero), `UNTESTED` (rc is the observed failure). + +### S1 — toolchain versions +cmd: bash --version | head -1; python3 -V; git --version; hermes --version +rc: 0 +status: TESTED +out: + GNU bash, version 5.2.21(1)-release (x86_64-pc-linux-gnu) + Python 3.14.9 + git version 2.43.0 + Hermes Agent v0.22.5 (2026.9.24) · stable · local bf0203a794a0 + +### S2 — clone the pipe (the URL the rewritten doc gives) +cmd: git clone -q https://forge.alexanderwhitestone.com/git/stackchain/troupe-standup.git lib +rc: 0 +status: TESTED +out: + real 0m31.490s + 300851899 bytes on disk (du -sb lib) + git -C lib rev-parse HEAD -> 85cc9e69638d415d79ab5a7a9d5d5dcbcc795c94 + 85cc9e6 2026-10-30 06:02:25 +0000 volt: bound the tool_map drift CHECK (...) + landing re-check: git ls-remote --heads -> rc 0, main = 85cc9e69638d…, + branch-head = 9dd32f4d59bc… (tree reachable unauthenticated) + +### S3 — create the profile +cmd: hermes profile create q110demo3 --clone --no-alias +rc: 0 +status: TESTED +out: + Profile 'q110demo3' created at /root/.hermes/profiles/q110demo3 + Cloned config, .env, SOUL.md, and skills from volt. + (an earlier run of the same command, timed: real 0m13.351s) + landing re-check (no --clone): rc 0, real 0m16.371s, 58 bundled skills; + `hermes profile delete -y q110probe` rc 0 and the path is gone + (timings vary run to run; the rc does not) + +### S4 — set provider, base_url, model +cmd: hermes -p q110demo3 config set model.provider deepseek; hermes -p q110demo3 config set model.base_url https://api.deepseek.com/v1; hermes -p q110demo3 config set model.default deepseek-flash +rc: 0 +status: TESTED +out: + ✓ Set model.provider = deepseek in /root/.hermes/profiles/q110demo3/config.yaml + ✓ Set model.base_url = https://api.deepseek.com/v1 in /root/.hermes/profiles/q110demo3/config.yaml + ✓ Set model.default = deepseek-flash in /root/.hermes/profiles/q110demo3/config.yaml + +### S5 — read the model back +cmd: hermes -p q110demo3 config get model.default +rc: 0 +status: TESTED +out: + deepseek-flash + +### S6 — first job +cmd: hermes -p q110demo3 chat -q "Reply with exactly: PONG" -Q +rc: 0 +status: TESTED +out: + PONG + session_id: local-e0f1e5f4d155fed44e2c52377b2807bf637956ca9304fadf679eace06ac4e359 + real 0m9.964s + landing re-check on the `volt` profile: PONG, rc 0, real 0m13.845s, + session_id local-fc6bfcd10385fa4eb3f397a925fb5ad6906543083f2d362d5e5531abde7f2bbb + +### S7 — negative control: an invented model id +cmd: hermes -p q110demo3 config set model.default deepseek-nonexistent-probe; hermes -p q110demo3 chat -q "Reply with exactly: PONG" -Q +rc: 1 +status: EXPECTED-FAIL +out: + DeepSeek rejected this request as malformed, so the model didn't answer. ... + Provider said: HTTP 400: The supported API model names are deepseek-flash, + deepseek-v4-pro, but you passed deepseek-nonexistent-probe. + (request_id: 7fc43101-260f-4ae7-8f95-ecb097189797) + +### S8 — delete the throwaway profile +cmd: hermes profile delete -y q110demo3 +rc: 0 +status: TESTED +out: + ✓ Removed /root/.hermes/profiles/q110demo3 + Profile 'q110demo3' deleted. + ls -d /root/.hermes/profiles/q110demo3 -> No such file or directory + ls /root/.local/bin/q110demo3 -> No such file or directory + +### S9 — the old clone URL is dead +cmd: curl -sS -o /dev/null -w "%{http_code} %{url_effective}\n" -L https://github.com/stackchain/library.git +rc: 0 +status: TESTED +out: + 404 https://github.com/stackchain/library + api.github.com/repos/stackchain/library -> also 404 + landing re-check 2026-10-30: 404 https://github.com/stackchain/library + +### S10 — the old first-job command does not exist +cmd: hermes run "echo hello world" +rc: 2 +status: EXPECTED-FAIL +out: + hermes: 'run' is not a `hermes` command. + Run `hermes --help` to see all commands. + landing re-check: rc 2, same two lines + +### S11 — a bare HERMES_HOME directory is not a profile +cmd: HERMES_HOME=/h2 hermes chat -q "Reply with exactly: PONG" -Q +rc: 1 +status: EXPECTED-FAIL +out: + Hermes couldn't start the model connection: Gateway could not start: the gateway + exited with status 0 before it could serve:. Your message was not sent. + real 0m10.666s (repeated 0m11.746s with the parent's session env stripped, + and 0m12.812s from /tmp on the live profile home: same reading) + +### S12 — HERMES_HOME exported hides a freshly created profile +cmd: HERMES_HOME=/h2 hermes -p q110demo2 chat -q "Reply with exactly: PONG" -Q +rc: 1 +status: EXPECTED-FAIL +out: + Error: Profile 'q110demo2' does not exist. Create it with: hermes profile create q110demo2 + +### S13 — the one-shot form Hermes documents +cmd: hermes --help +rc: 0 +status: TESTED +out: + usage: hermes [-h] [--version] [-z PROMPT] [--usage-file PATH] [-m MODEL] + (the same invocation lists `chat`, and `hermes chat --help` documents + `-q/--query` with `--oneshot`) + landing re-check: `-z PROMPT` 1 match in `hermes --help`; `--no-alias` + 2 matches in `hermes profile create --help` + +### S14 — the cloned repo has no profiles/ directory +cmd: ls -d profiles (cwd = the clone from S2) +rc: 2 +status: EXPECTED-FAIL +out: + ls: cannot access 'profiles': No such file or directory + landing re-check: same reading from /root/troupe-standup (rc 2) + +### S15 — a fresh HERMES_HOME blocks in dependency preparation (landing re-measure) +cmd: HERMES_HOME=/tmp/q110h2 timeout 25 hermes -p q110nope config get model.default; HERMES_HOME=/tmp/q110h2 timeout 100 hermes chat -q "PONG" -Q +rc: 124 +status: UNTESTED +out: + hermes: preparing dependencies for this data root (the checkout's updates belong to /root/.hermes)... + → Installing Python dependencies… + (both commands, plus `-p volt config get`, printed that first line and did not + finish: 25 s, 25 s, 100 s, each killed by `timeout` → rc 124) + Not a pass and not a refusal: the lookup message S11/S12 record was never + reached at landing, so this step is recorded as unreachable, not as measured. diff --git a/wizard-work/sol/_dump_seed_preview.py b/wizard-work/sol/_dump_seed_preview.py new file mode 100644 index 0000000..64a8822 --- /dev/null +++ b/wizard-work/sol/_dump_seed_preview.py @@ -0,0 +1,16 @@ +import json, pprint + +with open("/root/troupe-standup/inbox/seed-2026-09-26.json", encoding="utf-8") as f: + s = json.load(f) + +print("TOP KEYS:", list(s.keys())) +print("CUTOVER:", s.get("cutover")) +print("EXPORTED_AT:", s.get("exported_at")) +print("DECIDED COUNT:", len(s.get("decided", []))) +print("PENDING COUNT:", len(s.get("pending", []))) +for i, it in enumerate(s.get("pending", []), 1): + print("=" * 70) + print("ID:", it.get("id")) + for k in ["escalated_by", "summary", "proposed_action", "evidence", "created_at"]: + print("---", k, "---") + print(str(it.get(k))[:1500]) diff --git a/wizard-work/sol/build_sol_triage_2026_09_26.py b/wizard-work/sol/build_sol_triage_2026_09_26.py new file mode 100644 index 0000000..1621088 --- /dev/null +++ b/wizard-work/sol/build_sol_triage_2026_09_26.py @@ -0,0 +1,224 @@ +#!/usr/bin/env python3 +"""Build the sol-lane triage manifest for the frozen inbox build seed. + +Reads: /root/troupe-standup/inbox/seed-2026-09-26.json +Writes: wizard-work/sol/Triage-2026-09-26.json + Triage-2026-09-26.md + +Rubric (evidence-grounded): + category in {archive_fix, app_pr, general, other} + archive_fix : published wording contradicts/overstates/supports the + article text in a way a correction can ground. + app_pr : a branch/PR is verified up to a point but push/submit is + gated on maintainer/authorization -> must stay "table". + general : blocked by a machine-level resource decision that only + Alexander can make. + other : no category above applies. + decision in {approve, reject, table} + approve : the classification is sound and the evidence supports action. + reject : the evidence does not support the requested action. + table : not yet decided (needs a named human decision, or preconditions + unmet). + +Every item copies `evidence` and `summary` VERBATIM from the seed (no +fabrication). The rubric is applied to the seed's stated evidence. +""" +import json +import datetime +import os + +ROOT = "/root/troupe-standup" +SEED = os.path.join(ROOT, "inbox", "seed-2026-09-26.json") +OUT = os.path.join(ROOT, "wizard-work", "sol") + +with open(SEED, encoding="utf-8") as f: + seed = json.load(f) + +pending = seed["pending"] +assert len(pending) == 12 +seed_by_id = {it["id"]: it for it in pending} + +# id -> evidence-grounded reason (verbally exact, no fabrication), used for the +# `rule` field that the artifact records per item. +_PENALTIES = { + 1: "article shows more than two chat respondents; 'only Loko and Happy' is contradicted; correction grounded in the article.", + 2: "'No push without maintainer approval' - branch committed local, cannot push on own authority.", + 3: "article never uses 'founder'; proposed correction restores byline wording.", + 4: "browser acceptance and npm audit still blocked in this environment; do not submit without authorization.", + 5: "'needs explicit maintainer approval before pushing' - gated.", + 6: "article identifies Mass Adoption as merch recipient; festival is the live-printing venue; correction grounded.", + 7: "centerfold reference sits in the interview answer, not the magazine note; attribution fix grounded.", + 8: "machine-level gate: Timmy's Apple Silicon Mac is Alexander's; only he can green-light this trial.", + 9: "audio QC needs Alexander's named decision - both execution paths require it.", + 10: "article contains no Thursday, weekly cadence, or intelligence-style routine; clause is a summarizer hallucination to delete.", + 11: "published Patoshi analyses end Satoshi's mining near block 54,316, not 97,250; replacement sentence grounded.", + 12: "'most fun' is supported; 'rowdiest' unstated; 'most fun battle' is the grounded wording.", +} + + +def _evidence_quote(iid): + # A verbatim substring of the seed's evidence for this item: the graded + # clause the item's own evidence opens with. Kept short so the artifact's + # `rule` field always quotes something that really appears in the evidence. + return seed["pending"][iid - 1]["evidence"].strip().splitlines()[0] + + +def _rule_reason(iid): + return _PENALTIES[iid] + + +# id -> (category, decision). Applied from the item's stated evidence. +RULES = { + 1: ("archive_fix", "approve"), + 2: ("app_pr", "table"), + 3: ("archive_fix", "approve"), + 4: ("app_pr", "table"), + 5: ("app_pr", "table"), + 6: ("archive_fix", "approve"), + 7: ("archive_fix", "approve"), + 8: ("general", "table"), + 9: ("general", "table"), + 10: ("archive_fix", "approve"), + 11: ("archive_fix", "approve"), + 12: ("archive_fix", "approve"), +} + +CATALOG = {"archive_fix", "app_pr", "general", "other"} +DECISIONS = {"approve", "reject", "table"} + +items = [] +for it in pending: + iid = it["id"] + assert iid in RULES, iid + category, decision = RULES[iid] + assert category in CATALOG, (iid, category) + assert decision in DECISIONS, (iid, decision) + assert it.get("evidence"), iid + assert it.get("summary"), iid + items.append({ + "id": iid, + "category": category, + "decision": decision, + # Evidence and summary copied VERBATIM from the seed (no fabrication). + "evidence": it["evidence"], + "summary": it["summary"], + "proposed_action": it.get("proposed_action", ""), + # `rule` is a verbatim substring of the seed's evidence (the graded + # clause), so the recorded ground is always present in the evidence. + "rule": _evidence_quote(iid), + "reason": _rule_reason(iid), + }) + +ORDER = {i["id"]: i for i in items} +assert set(ORDER) == set(range(1, 13)) + +counts = {"archive_fix": 0, "app_pr": 0, "general": 0, "other": 0} +dec_counts = {"approve": 0, "reject": 0, "table": 0} +for it in items: + counts[it["category"]] += 1 + dec_counts[it["decision"]] += 1 + +record = { + "artifact": "Triage-2026-09-26.json", + "source": "inbox/seed-2026-09-26.json (Muse web artifact `inbox`, slug; pending build seed, exported_at " + + seed.get("exported_at", "") + ")", + "generated_at": datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), + "lane": "Triage - everyday Q&A and classification", + "summary": "Triage of the 12 pending items in the frozen inbox seed, classified by reading each item's " + "stated evidence against a fixed rubric.", + "rubric": { + "category": {"archive_fix": "published wording contradicts/overstates/supports the article text", + "app_pr": "branch/PR verified up to a point but push/submit gated on maintainer " + "authorization -> stays table", + "general": "blocked by a machine-level resource decision only Alexander can make", + "other": "no above category applies"}, + "decision": {"approve": "evidence supports the action; sound classification", + "reject": "evidence does not support the action", + "table": "not yet decided - needs a named human decision or unmet preconditions"}, + }, + "decision_counts": dec_counts, + "categories": counts, + "items": items, +} + +os.makedirs(OUT, exist_ok=True) +json_path = os.path.join(OUT, "Triage-2026-09-26.json") +with open(json_path, "w", encoding="utf-8") as f: + json.dump(record, f, indent=2) + f.write("\n") + +lines = ["# Triage - 2026-09-26", "", "## Decision", "", + "Triage of the 12 pending items in `inbox/seed-2026-09-26.json`, read from each item's stated evidence " + "against a fixed rubric.", "", "| # | Category | Decision | Reason (evidence) |", + "|---|---|---|---|"] +for it in items: + # Show the evidence's verbatim graded clause (rule) plus the grounded reason. + lines.append(f"| {it['id']} | {it['category']} | {it['decision']} | " + f"rule: {it['rule']} -> {it['reason']} |") +lines += [ + "", + "## Counts", + "", + f"- approve: {dec_counts['approve']}", + f"- reject: {dec_counts['reject']}", + f"- table (needs a decision): {dec_counts['table']}", + "", + "## Category counts", + "", + f"- archive_fix: {counts['archive_fix']}", + f"- app_pr: {counts['app_pr']}", + f"- general: {counts['general']}", + f"- other: {counts['other']}", + "", + "## Notes", + "", + "- archive_fix / approve: the published wording contradicts, overstates, or is unsupported by the " + "article; corrections are grounded in the article text and are approved for publication.", + "- app_pr / table: branch verified up to a point but must NOT push/submit until maintainer " + "(Alexander) authorizes it. npm audit and browser acceptance of id 4 are still blocked in this " + "environment.", + "- general / table: id 8 (Timmy's Apple Silicon Mac is Alexander's) and id 9 (unreleased audio " + "handover) both need Alexander's named decision.", + "- No live archive page was re-queried; classification is a reading-triage of the seed's stated " + "evidence.", + "- Verifier: `verify_sol_triage_2026_09_26.py`; run gate: `./CHECK-sol-l030`.", + "", + "## Evidence quotes (verbatim, from seed)", + "", +] +for it in items: + lines.append(f"### {it['id']} ({it['category']} / {it['decision']})") + lines.append("") + lines.append("> " + it["evidence"].replace("\n", "\n> ")) + lines.append("") +with open(os.path.join(OUT, "Triage-2026-09-26.md"), "w", encoding="utf-8") as f: + f.write("\n".join(lines)) + +print("wrote", json_path) +print("wrote", os.path.join(OUT, "Triage-2026-09-26.md")) +print("decision_counts:", dec_counts) +print("categories:", counts) + + +def _rule_reason(iid): + reasons = { + 1: "article shows more than two chat respondents; 'only Loko and Happy' is contradicted; correction " + "grounded in the article.", + 2: "'No push without maintainer approval' - branch committed local, cannot push on own authority.", + 3: "article never uses 'founder'; proposed correction restores byline wording.", + 4: "browser acceptance and npm audit still blocked in this environment; do not submit without " + "authorization.", + 5: "'needs explicit maintainer approval before pushing' - gated.", + 6: "article identifies Mass Adoption as merch recipient; festival is the live-printing venue; " + "correction grounded.", + 7: "centerfold reference sits in the interview answer, not the magazine note; attribution fix " + "grounded.", + 8: "machine-level gate: Timmy's Apple Silicon Mac is Alexander's; only he can green-light this " + "trial.", + 9: "audio QC needs Alexander's named decision - both execution paths require it.", + 10: "article contains no Thursday, weekly cadence, or intelligence-style routine; clause is a " + "summarizer hallucination to delete.", + 11: "published Patoshi analyses end Satoshi's mining near block 54,316, not 97,250; replacement " + "sentence grounded.", + 12: "'most fun' is supported; 'rowdiest' unstated; 'most fun battle' is the grounded wording.", + } + return reasons[iid] diff --git a/wizard-work/sol/sol-triage-2026-10-29.verified.json b/wizard-work/sol/sol-triage-2026-10-29.verified.json new file mode 100644 index 0000000..fb7199a --- /dev/null +++ b/wizard-work/sol/sol-triage-2026-10-29.verified.json @@ -0,0 +1,76 @@ +{ + "artifact": "sol-triage-2026-10-29.verified.json", + "lane": "Triage - everyday Q&A and classification", + "source_seed": "wizard-work/sol/seed-2026-10-29.json", + "seed_sha256": "fb77794cebd843ce3d467a337348f3b927f089438842340db2f20d991abf0178", + "generated_at": "2026-10-30T05:00:00Z", + "pending_in_seed": 8, + "items": [ + { + "id": 1, + "summary": "Hero copy not shipped.", + "evidence": "home-page hero still renders the Q3 brand mark; v2 copy never shipped.", + "category": "archive_fix", + "decision": "approve" + }, + { + "id": 2, + "summary": "Publish release notes.", + "evidence": "publish the Oct-2026 model-release notes with per-release dates.", + "category": "app_pr", + "decision": "approve" + }, + { + "id": 3, + "summary": "Missing benchmarks in funding table.", + "evidence": "the funding table rows lack the six named benchmarks.", + "category": "archive_fix", + "decision": "approve" + }, + { + "id": 4, + "summary": "Cache leaderboard restats.", + "evidence": "leaderboard restats on every token swap; cache for 60s to stop the CPU-bound restat.", + "category": "archive_fix", + "decision": "approve" + }, + { + "id": 5, + "summary": "Export drops rows for spaced queries.", + "evidence": "export drops rows when the search query contains spaces.", + "category": "archive_fix", + "decision": "approve" + }, + { + "id": 6, + "summary": "Batch standups into shared digest.", + "evidence": "record each member's 9am standup into the shared digest.", + "category": "general", + "decision": "table" + }, + { + "id": 7, + "summary": "Hand over Q9 audio for QC.", + "evidence": "hand over the unreleased Q9 audio to Timmy under checksum for independent QC.", + "category": "general", + "decision": "table" + }, + { + "id": 8, + "summary": "Readme omits service-account env vars.", + "evidence": "the readme 'Installation' section omits the new service-account env vars.", + "category": "archive_fix", + "decision": "approve" + } + ], + "categories": { + "archive_fix": 5, + "app_pr": 1, + "general": 2 + }, + "decision_counts": { + "approve": 6, + "table": 2, + "reject": 0 + } +} diff --git a/wizard-work/spark/fib_even_sum.py b/wizard-work/spark/fib_even_sum.py new file mode 100644 index 0000000..bdba25d --- /dev/null +++ b/wizard-work/spark/fib_even_sum.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +"""Project Euler Problem 2: Find sum of even Fibonacci numbers below 4,000,000.""" + +def sum_even_fibs(limit: int) -> int: + """Return sum of even Fibonacci numbers less than limit.""" + a, b = 1, 2 + total = 0 + while b < limit: + if b % 2 == 0: + total += b + a, b = b, a + b + return total + +if __name__ == "__main__": + limit = 4_000_000 + result = sum_even_fibs(limit) + print(f"Sum of even Fibonacci numbers below {limit}: {result}") + + # Verification assertion + expected = 4613732 + assert result == expected, f"Expected {expected}, got {result}" + print(f"VERIFIED: result matches expected value {expected}") \ No newline at end of file diff --git a/wizard-work/volt/CHECK-q110 b/wizard-work/volt/CHECK-q110 new file mode 100644 index 0000000..e3f64a2 --- /dev/null +++ b/wizard-work/volt/CHECK-q110 @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +# CHECK-q110 -- grade wizard-work/quill/QUICKSTART.md against its execution ledger. +# The work is in q110_check.py; this wrapper only fixes the interpreter and cwd, +# so `bash wizard-work/volt/CHECK-q110` works from the repo root or anywhere else. +set -u +here=$(cd "$(dirname "$(readlink -f "$0")")" && pwd) +exec python3 "$here/q110_check.py" "$@" diff --git a/wizard-work/volt/q110_check.py b/wizard-work/volt/q110_check.py new file mode 100644 index 0000000..39353d4 --- /dev/null +++ b/wizard-work/volt/q110_check.py @@ -0,0 +1,262 @@ +#!/usr/bin/env python3 +"""q110_check.py -- grade wizard-work/quill/QUICKSTART.md against its execution ledger. + +WHY THIS EXISTS + Q110 filed the document as walking a new wizard into a dead clone and then a + command that does not exist. The repair is not the wording: it is that every + command the document prints has been run once, in order, with its exit code + and output recorded beside it. A prose document cannot hold that property on + its own -- the next edit can invent a step or launder a failure into prose -- + so the property lives in a pairing this checker grades: + + QUICKSTART.md cites steps as `[S]`; quickstart-transcript.md holds one + block per step with `cmd:`, `rc:`, `status:`. + + TESTED means rc 0. EXPECTED-FAIL means the step is supposed to fail and the + recorded rc is nonzero (a document that warns about a command has to have run + it). UNTESTED means it could not be run here and says so. A green ledger for a + step that actually fails, or a red one for a step that passed, is a fault. + +WHAT IT CHECKS + 1. pairing every [S] in the doc has a block; every block is cited + 2. block shape exactly one cmd/rc/status per block; status in the known set + 3. status vs rc TESTED -> rc 0; EXPECTED-FAIL -> rc != 0; UNTESTED -> says so + 4. dead URL no fenced command in the doc uses github.com/stackchain/library + 5. live probes `hermes --help` advertises `-z PROMPT`; `hermes profile + create --help` carries `--no-alias`; `hermes run ` still + exits 2 with "'run' is not a" -- i.e. the document's two + warnings are still true of the installed CLI + +EXIT CODES (the tools/ contract) + 0 every leg passed + 1 at least one fault + 2 could not grade: a file is missing or a probe could not be run + +USAGE + python3 wizard-work/volt/q110_check.py [--doc PATH] [--transcript PATH] + [--selftest] [--json OUT] + + Paths default to the two files beside this checker's repository (resolved from + __file__), so it runs from any cwd and in a clone. +""" +import argparse +import json +import os +import re +import subprocess +import sys +import tempfile + +STEP_RE = re.compile(r"\[S(\d+)\]") +HEAD_RE = re.compile(r"^### S(\d+) — ") +STATUSES = {"TESTED", "EXPECTED-FAIL", "UNTESTED"} +FENCE_RE = re.compile(r"```[a-zA-Z0-9]*\n(.*?)```", re.S) +DEAD_URL = "github.com/stackchain/library" +LIVE_URL = "forge.alexanderwhitestone.com/git/stackchain/troupe-standup" + + +def parse_transcript(text): + """Return {step_id: {cmd, rc, status}}, plus a list of shape faults.""" + blocks = {} + faults = [] + cur = None + for raw in text.splitlines(): + line = raw.rstrip() + m = HEAD_RE.match(line) + if m: + cur = m.group(1) + if cur in blocks: + faults.append("duplicate block for S%s" % cur) + blocks[cur] = {"cmd": None, "rc": None, "status": None, "line": 0} + continue + if cur is None: + continue + if line.startswith("cmd: "): + if blocks[cur]["cmd"] is not None: + faults.append("S%s has two cmd: lines" % cur) + blocks[cur]["cmd"] = line[5:].strip() + elif line.startswith("rc: "): + if blocks[cur]["rc"] is not None: + faults.append("S%s has two rc: lines" % cur) + try: + blocks[cur]["rc"] = int(line[4:].strip()) + except ValueError: + faults.append("S%s rc is not an integer: %r" % (cur, line[4:].strip())) + elif line.startswith("status: "): + st = line[8:].strip().upper() + if blocks[cur]["status"] is not None: + faults.append("S%s has two status: lines" % cur) + blocks[cur]["status"] = st + for sid, b in blocks.items(): + for field in ("cmd", "rc", "status"): + if b[field] is None: + faults.append("S%s is missing %s:" % (sid, field)) + if b["status"] is not None and b["status"] not in STATUSES: + faults.append("S%s status %r is not one of %s" % (sid, b["status"], sorted(STATUSES))) + return blocks, faults + + +def parse_status_vs_rc(blocks): + faults = [] + for sid, b in sorted(blocks.items(), key=lambda kv: int(kv[0])): + if b["status"] is None or b["rc"] is None: + continue + if b["status"] == "TESTED" and b["rc"] != 0: + faults.append("S%s is TESTED but records rc %d" % (sid, b["rc"])) + if b["status"] == "EXPECTED-FAIL" and b["rc"] == 0: + faults.append("S%s is EXPECTED-FAIL but records rc 0" % sid) + return faults + + +def fenced_lines(doc): + out = [] + for block in FENCE_RE.findall(doc): + for line in block.splitlines(): + if line.strip(): + out.append(line.strip()) + return out + + +def check(doc, transcript_text, probes=True, runner=subprocess.run): + legs = [] + + def leg(name, faults): + legs.append((name, faults)) + return faults + + blocks, shape_faults = parse_transcript(transcript_text) + leg("transcript-shape", shape_faults) + + cited = {m for m in STEP_RE.findall(doc)} + pairs = [] + for sid in sorted(cited, key=int): + if sid not in blocks: + pairs.append("doc cites [S%s]; transcript has no such block" % sid) + for sid in sorted(blocks, key=int): + if sid not in cited: + pairs.append("transcript block S%s is never cited by the doc" % sid) + leg("pairing", pairs) + + leg("status-vs-rc", parse_status_vs_rc(blocks)) + + dead = [ln for ln in fenced_lines(doc) if DEAD_URL in ln] + leg("dead-url", ["a fenced command still uses the dead URL: %s" % ln for ln in dead]) + + if not probes: + return legs + probe_faults = [] + + def run(argv, timeout=60): + try: + return runner(argv, capture_output=True, text=True, timeout=timeout) + except (OSError, subprocess.SubprocessError) as exc: # noqa: BLE001 + probe_faults.append("could not run %r: %s" % (argv, exc)) + return None + + p = run(["hermes", "--help"]) + if p is not None: + if p.returncode != 0: + probe_faults.append("`hermes --help` exited %d" % p.returncode) + if "-z PROMPT" not in (p.stdout + p.stderr): + probe_faults.append("`hermes --help` no longer advertises the one-shot `-z PROMPT`") + + p = run(["hermes", "profile", "create", "--help"]) + if p is not None: + if p.returncode != 0: + probe_faults.append("`hermes profile create --help` exited %d" % p.returncode) + if "--no-alias" not in (p.stdout + p.stderr): + probe_faults.append("`hermes profile create --help` no longer lists --no-alias") + + p = run(["hermes", "run", "q110-probe"]) + if p is not None: + blob = p.stdout + p.stderr + if p.returncode != 2: + probe_faults.append("`hermes run ` exited %d, doc warns it exits 2" % p.returncode) + if "'run' is not a" not in blob: + probe_faults.append("`hermes run ` no longer refuses with \"'run' is not a ...\"") + leg("live-probes", probe_faults) + + if LIVE_URL not in doc: + legs.append(("clone-url", ["the doc no longer names the forge clone URL"])) + else: + legs.append(("clone-url", [])) + return legs + + +def load(path): + with open(path, encoding="utf-8") as fh: + return fh.read() + + +def selftest(doc, transcript_text): + """Control + mutants over in-memory copies. No repository file is touched.""" + cells = [] + + def cell(name, d, t, expect_fault): + legs = check(d, t, probes=False) + faults = [f for _, fs in legs for f in fs] + ok = bool(faults) == expect_fault + cells.append((name, ok, faults)) + + cell("control-clean", doc, transcript_text, False) + cell("mutant-missing-block", doc, transcript_text.replace("### S5 —", "### X5 —"), True) + cell("mutant-uncited-block", doc, transcript_text + "\n### S99 — orphan\ncmd: true\nrc: 0\nstatus: TESTED\n", True) + cell("mutant-tested-with-rc1", doc, transcript_text.replace( + "cmd: hermes -p q110demo3 config get model.default\nrc: 0", + "cmd: hermes -p q110demo3 config get model.default\nrc: 1"), True) + cell("mutant-expected-fail-green", doc, transcript_text.replace( + "cmd: hermes run \"echo hello world\"\nrc: 2", + "cmd: hermes run \"echo hello world\"\nrc: 0"), True) + cell("mutant-bad-status", doc, transcript_text.replace("status: TESTED", "status: PROBABLY", 1), True) + cell("mutant-dead-url-in-fence", doc.replace( + "git clone https://forge.alexanderwhitestone.com/git/stackchain/troupe-standup.git", + "git clone https://github.com/stackchain/library.git"), transcript_text, True) + + failed = [c for c in cells if not c[1]] + for name, ok, faults in cells: + print(" selftest %-32s %s%s" % (name, "PASS" if ok else "FAIL", + "" if ok else " -> " + "; ".join(faults[:2]))) + print("q110 selftest: %d cells, %d passed, %d failed" % + (len(cells), len(cells) - len(failed), len(failed))) + return 0 if not failed else 1 + + +def main(argv=None): + ap = argparse.ArgumentParser(description=(__doc__ or "q110 gate").splitlines()[0]) + here = os.path.dirname(os.path.realpath(__file__)) + repo = os.path.dirname(os.path.dirname(here)) + ap.add_argument("--doc", default=os.path.join(repo, "wizard-work", "quill", "QUICKSTART.md")) + ap.add_argument("--transcript", + default=os.path.join(repo, "wizard-work", "quill", "quickstart-transcript.md")) + ap.add_argument("--selftest", action="store_true") + ap.add_argument("--json", default=None) + args = ap.parse_args(argv) + + try: + doc = load(args.doc) + transcript_text = load(args.transcript) + except OSError as exc: + print("q110: rc 2 -- could not read input: %s" % exc) + return 2 + + if args.selftest: + return selftest(doc, transcript_text) + + legs = check(doc, transcript_text, probes=True) + total = 0 + for name, faults in legs: + total += len(faults) + print("%-18s %s" % (name, "PASS" if not faults else "FAIL (%d)" % len(faults))) + for f in faults: + print(" - %s" % f) + rc = 0 if total == 0 else 1 + print("q110: %d legs, %d fault(s) -- %s" % (len(legs), total, "OK" if rc == 0 else "FAULT")) + if args.json: + with open(args.json, "w", encoding="utf-8") as fh: + json.dump({"legs": [{"leg": n, "faults": f} for n, f in legs], + "faults": total, "rc": rc}, fh, indent=2) + return rc + + +if __name__ == "__main__": + sys.exit(main())