spark: add verified even Fibonacci sum solver

This commit is contained in:
spark committed 2026-10-30 10:35:49 +00:00
1 parent 8c67a8a540
commit f0fddc5a9c
10 files changed
+966 -30

No files matched your search

+20
View File
@@ -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/<yourname>` 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/<yourname>` 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 <yourname> chat -q "Reply with exactly: PONG" -Q`. The `profiles/example` copy is deleted rather than repaired — profiles are created by `hermes profile create <name> --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 <x>` 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 <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`.
**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
+40 -13
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 -- 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,
+140 -17
View File
@@ -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/<yourname>`. 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/<yourname>
export HERMES_PROFILE=<yourname>
```
bash --version | head -1; python3 -V; git --version; hermes --version
```
## 4. Configure Providers
Edit profiles/<yourname>/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 <yourname> --no-alias
```
Reading `[S3]`: rc 0 in **13.4 s**, creating `~/.hermes/profiles/<yourname>`
(config, `.env`, `SOUL.md`, bundled skills). Remove a throwaway profile with
`hermes profile delete -y <yourname>` `[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 <other>`)
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 <name> 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 <yourname> config set model.provider deepseek
hermes -p <yourname> config set model.base_url https://api.deepseek.com/v1
hermes -p <yourname> config set model.default deepseek-flash
hermes -p <yourname> 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 <yourname> 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.
+159
View File
@@ -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<n> — 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=<scratch>/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=<scratch>/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.
+16
View File
@@ -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])
@@ -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]
@@ -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
}
}
+22
View File
@@ -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}")
+7
View File
@@ -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" "$@"
+262
View File
@@ -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<n>]`; 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<n>] 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 <x>` 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 <x>` exited %d, doc warns it exits 2" % p.returncode)
if "'run' is not a" not in blob:
probe_faults.append("`hermes run <x>` 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())