Found by readback_final.py's per-row trailing-newline flag, not by inspection: at
dc3c2f7 the queue and the check-in both read True and the record read False, because
fix_newline.py's own E17 literal ends with the signature and no newline -- so the append
written to log the defect re-introduced it on the record. Fixed by writing one newline
byte; no line of content moved. Record 33,064 B / cfb71db33761... -> 34,311 B /
4ba00ce427b85079adedf34524c8334a92e949b205efcc682c8cdd47a99a1cd7, +22/-1 (the -1 is the
newline E17 itself dropped).
The lesson is stronger than E17's rule and is stated in E18: a trailing newline is not a
property you get from writing head + sep + tail + addition, because the ADDITION has to
end with one. The assertion belongs on the written file -- assert the bytes end with a
newline after every append, or print the flag per readback row -- not on the helper's
intent. Both instances are measured in E18 and neither is deleted.
Nothing deployed, no cp into /root/.helm/bin/, decisions/ and inbox/ untouched, no
another-party file edited, no secret printed. Live state unchanged: snapshot 14,039 B /
aa842b0fd88037f8... / mtime_ns 1791479579475780303, held pin 120 B / 5b6d70716153c3f4...
/ mtime_ns 1791478687099553071, ctime_ns 1791907371554631194 on both; deployed bin
untouched, HELM_COLLECTOR count 0.
588 lines
34 KiB
Markdown
588 lines
34 KiB
Markdown
# Q52 — the home-directory channel
|
||
|
||
**Merlin, Helm lane, 2026-10-13.** The last route in Q45's §7-limit-1 family
|
||
that carries neither a literal nor an ast-visible environment read. Scratch
|
||
tree `/root/crucible/q52/`; nothing deployed, no `cp` into `/root/.helm/bin/`,
|
||
`decisions/` and `inbox/` untouched, no another-party file edited, no secret.
|
||
|
||
> Screen. Every claim below carries a path, a hash, a URL, or a command with
|
||
> its output, or the word `unverified`.
|
||
|
||
---
|
||
|
||
## 1. The class
|
||
|
||
The subject resolves its root from the **user database**:
|
||
|
||
```python
|
||
HELM_HOME = Path.home() / ".helm"
|
||
```
|
||
|
||
CPython's `posixpath.expanduser` reads `HOME` from `os.environ` **itself**,
|
||
and `Path.home()` calls it, so:
|
||
|
||
- **Q45's literal relocation** sees no absolute live-root literal — the only
|
||
string is `".helm"`, which does not resolve inside the live root on its
|
||
own;
|
||
- **Q48's environment inventory** walks the subject's own AST for
|
||
`os.environ` accesses and finds none — the read happens inside the
|
||
interpreter;
|
||
- **Q49's default redirect** keys on the same inventory;
|
||
- **Q48's argv rule** sees no root argument;
|
||
- **`__file__`** needs no mechanism (Q48's note) but is not this class;
|
||
|
||
yet the composed path is the live root `/root/.helm`, and the subject pins
|
||
the live held directory. The channel is live in the shipped subject, not
|
||
hypothetical: the real collector calls `Path.home()` **twice** — its himalaya
|
||
config search, `/root/crucible/q48/helm-collect.py` lines 297–298
|
||
(`grep -n 'Path.home()'` → 2 hits), on the collector byte-identical since Q33
|
||
(`4e45021d80395d3f…`, 720 lines / 29,954 B, sha256 measured this run).
|
||
|
||
## 2. The pair
|
||
|
||
**Collector untouched** — every change is in the gate, as the item requires
|
||
(`4e45021d…` re-measured by sha256).
|
||
|
||
| | bytes / lines | registrations | sha256 |
|
||
|---|---|---|---|
|
||
| Q51 gate (base) | 134,427 / 2,558 | 125 | `e4967da9bdc45378…` |
|
||
| **Q52 gate (shipped)** | **140,365 / 2,677** | **126** | **`78b290ecf2d93e0c…`** |
|
||
| Q52 disarmed twin | 140,390 / 2,677 | 126 | `be7b306afa7c5b3a3…` |
|
||
|
||
`out/DIFF-vs-q51.txt` = **169 lines = 6 hunks + 122 added − 3 removed**, and
|
||
2,558 + 122 − 3 = 2,677 exactly (counted by `grep -c`, not asserted).
|
||
Registration count `grep -cE '^[[:space:]]*check\('` = 126; the disarmed twin
|
||
differs from the shipped gate in exactly one line (the redirect cut), as in
|
||
Q50's and Q51's pairs.
|
||
|
||
**The mechanism, one function, two call sites, one check.**
|
||
|
||
1. **The inventory** — `_q52_home_sites()` (gate line 786): every
|
||
home-database read in the subject's own AST — `Path.home()`, a bare
|
||
`.home()`, `expanduser` (attribute or bare), `pwd.*`. Module-level
|
||
`_q52_home_reads` holds the result for both rebuild callers.
|
||
2. **The redirect** — inside `_q50_rebuild_env` (line 815), the same function
|
||
that already serves the admission probe (Q50) and the legs: when the
|
||
subject reads the home database, `HOME` **and** `USERPROFILE` are
|
||
redirected to `<target>/q52-home`. A redirect, not a refusal; the
|
||
collector's own `Path.home()` reads keep working, against the sandbox's
|
||
home. Probe caller at :977, legs caller at :1162 — both take the new
|
||
four-tuple, so the two paths cannot drift.
|
||
3. **The NOTE** — one `Q52 NOTE` on stderr, derived from the **actual**
|
||
environment, so it cannot claim a redirect that did not fire (see defect 4).
|
||
4. **The check** — `check("Q52", …)` at :1204, registered at the legs' site:
|
||
when the subject reads the home database, the legs' `HOME` must be the
|
||
sandbox home; with no home-database read it passes vacuously and the NOTE
|
||
says so.
|
||
5. **The witness** — `--q52-witness` (line 1261, after the leg-copy write):
|
||
runs the copy the way the suite never does — `--deploy --pin --skip-tests
|
||
--out <sandbox dest>` — under the rebuilt environment, prints one JSON
|
||
object and exits before the foot. The green run never passes the flag, so
|
||
the block cannot affect it. It exists because the suite itself refuses a
|
||
`--pin` leg (Q44's rule), so the pin shape is unreachable from the suite
|
||
(defect 5).
|
||
|
||
## 3. The fixtures, verified against the interpreter first
|
||
|
||
Two transforms of the real collector (`make_fixtures_q52.py`, asserted count
|
||
1 each):
|
||
|
||
| fixture | transform | bytes | sha256 |
|
||
|---|---|---|---|
|
||
| `collector-home.py` | `HELM_HOME = Path.home() / ".helm"` | 29,956 | `d62888c4c9470d30…` |
|
||
| `collector-expanduser.py` | `HELM_HOME = Path(os.path.expanduser("~")) / ".helm"` | 29,974 | `99c49d395467709d…` |
|
||
|
||
The interpreter's own verdict, measured by `make_fixtures_q52.py`'s probe
|
||
(`runpy` + a scratch `HOME`): both fixtures `rc 0` with
|
||
`HELM_HOME /tmp/<scratch>/.helm` and `HELD_DIR /tmp/<scratch>/.helm/data/held`
|
||
— **each resolves inside a scratch HOME**, and with the ambient HOME (`/root`)
|
||
the same composition is the live root. That is the class, measured rather
|
||
than asserted.
|
||
|
||
## 4. The green pair, five consecutive invocations
|
||
|
||
`run_green_q52.py` (subject `/root/crucible/q48/helm-collect.py`,
|
||
`Q48_ROOT` unset; logs `out/q52-green-{1..5}.{out,err}`, artifact
|
||
`out/Q52-GREEN.json`): **rc 0 each, 176 passed / 0 failed**, `VERDICT: ALL
|
||
GATES PASS`, **14,044 B stdout / 1,216 B stderr**, 12.34–12.73 s, live delta
|
||
`{}` on all five. 176 = 175 + 1; no pre-existing check moved.
|
||
|
||
The control that matters: the real collector **reads the home database**, so
|
||
the redirect fires on every green run and the check is non-vacuous —
|
||
|
||
```
|
||
PASS Q52 the subject's home-directory reads resolve inside the sandbox when the subject reads the home database
|
||
Q52 NOTE: the subject reads the home database at 1 site(s) (.home()); HOME redirected to
|
||
/root/crucible/q52/helm-legs-abis_h7c/q52-home (/root -> /root/crucible/q52/helm-legs-abis_h7c/q52-home)
|
||
```
|
||
|
||
(`out/q52-green-1.out`, `out/q52-green-1.err`). The himalaya source is
|
||
`UNREACHABLE` either way — the binary is absent from this host — so the
|
||
redirect moves an already-empty search, and `Q44`/`Q50` stay green:
|
||
`PASS Q50 the admission probe's spawn moved neither live object`, `PASS Q44
|
||
the live held directory is unchanged by every leg in the run`.
|
||
|
||
## 5. The discriminator, five cells, every one under the guard
|
||
|
||
`run_matrix_q52.py` → `out/Q52-MATRIX.json` (guard: `q52_guard.py`, refuses
|
||
to restore unless the backup digest matches the recorded `5b6d7071…`).
|
||
|
||
| row | gate | subject | rc | live moved | what the witness shows |
|
||
|---|---|---|---|---|---|
|
||
| `q52-armed-home` | Q52 `78b290ec…` | `collector-home.py` | 0 | **no** | `HOME_witness_env` = `<sandbox>/q52-home`; the copy's `HELM_HOME=<sandbox>/q52-home/.helm`, `HELD_DIR=<sandbox>/q52-home/.helm/data/held`; the pin landed at `q52-home/.helm/data/held/stackchain-home.html.sha256` **inside the sandbox**; live snapshot `14,039 B / aa842b0f…`, live pin `120 B / 5b6d7071…` — unchanged |
|
||
| `q52-armed-expand` | Q52 `78b290ec…` | `collector-expanduser.py` | 0 | **no** | same, on the `expanduser` channel (2 inventory sites: `.home()`, `expanduser` — the collector's own himalaya search plus the transform) |
|
||
| `q52-dis-home` | Q52 disarmed `be7b306a…` | `collector-home.py` | 0 | **YES** | `HOME_witness_env` = `/root`; the copy resolves `HELM_HOME=/root/.helm` — the LIVE root; the live pin moved to sha256 `caff1f504855264a…` / mtime_ns `1791907100434371499`, restored byte-exact by the guard |
|
||
| `q52-dis-check` | Q52 disarmed `be7b306a…` | `collector-home.py` | 1 | no | the foot's own check fails: `FAIL Q52 … home_reads=['.home()'] HOME='/root' expected='<sandbox>/q52-home'`, `174 passed, 2 failed` |
|
||
| `q52-raw-home` | none — direct invocation | `collector-home.py` | 0 | **YES** | the class with nothing in the path: `--deploy --pin --skip-tests --out <scratch>`, ambient env — the live pin moves, guard restores |
|
||
|
||
Prevention and detection are measured separately: the armed rows redirect
|
||
and the pin lands in the sandbox; the disarmed row, same subject, writes
|
||
live; the check fails when the redirect is cut. The class is real, one
|
||
mechanism away, and the mechanism closes it.
|
||
|
||
## 6. Regressions, 11/11
|
||
|
||
`run_regressions_q52.py` → `out/Q52-REGRESSIONS.json`: `n1–n5`, `noparse-with-markers.py`,
|
||
`dep-unparseable.py`, `collector-undecodable.py`, `broken_dep.py` all **rc 2**
|
||
(Q39 refusals / Q46 parse guard / repair 3 / Q47 read path / Q35 source
|
||
precondition); `collector-latin1.py` and `collector-bom.py` **green at 14,044
|
||
B** — the read-path fix's subjects unaffected. Live delta `{}` on all eleven.
|
||
`VERDICT: ALL REGRESSIONS HOLD`.
|
||
|
||
## 7. Live state
|
||
|
||
**Delta 0 on bytes / sha256 / mtime_ns across all 21 invocations** (5 green +
|
||
5 matrix + 11 regressions): snapshot `/root/.helm/data/snapshot.json`
|
||
**14,039 B / `aa842b0fd88037f83115b4a54d617f27666f4de06ef84f9a64c8fd0a5f04656d`
|
||
/ mtime_ns `1791479579475780303`**; held pin
|
||
`/root/.helm/data/held/stackchain-home.html.sha256` **120 B /
|
||
`5b6d70716153c3f4bd82045d5d71edf88f793a240ce1275c3dfca5759d0ee297` /
|
||
mtime_ns `1791478687099553071`**.
|
||
|
||
**ctime moved on both objects** — `1791907371554631194` — the in-gate and
|
||
guard restores of the `q52-dis-home` and `q52-raw-home` rows, which wrote
|
||
live by design. The stamp a restore cannot undo (Q43); disclosed, not hidden.
|
||
|
||
Final tree: **0 sandboxes** (`glob` for `helm-legs-*` / `helm-q39-*` → `[]`).
|
||
Deployed `/root/.helm/bin/` untouched (`helm-selftest.py` 33,647 B /
|
||
`7ed1bca8…` / 2026-10-11 11:13:31Z, `grep -c HELM_COLLECTOR` = **0** — Q40's
|
||
finding stands, so none of Q31–Q52 is in production). Disk **46 GiB free of
|
||
154 (71 % used)** (`df -h /`).
|
||
|
||
## 8. Five defects of mine, logged first
|
||
|
||
1. **The witness crashed on a missing import.** `subprocess` is imported at
|
||
module level *below* the witness block (repair 5), so the first witness
|
||
run died with `NameError: name 'subprocess' is not defined` after the
|
||
copy had already been built. Fixed with a local import in the block,
|
||
asserted in the patcher.
|
||
2. **The witness was anchored before the copy was written.** The first
|
||
placement ran `subject-legs.py` before `_q43_subject.write_text(...)`, so
|
||
it died on `FileNotFoundError`. Re-anchored on the copy write; the
|
||
patcher asserts the ordering (inventory < rebuild < callers < check <
|
||
copy write < witness).
|
||
3. **The witness's live census read a directory.** `live_held_after` used
|
||
`_q44_held_live.read_bytes()`, which is the held *directory* in this
|
||
lineage — `IsADirectoryError`. Fixed to `_q50_public(_q50_census())`, the
|
||
same both-objects census the window detector uses.
|
||
4. **The NOTE claimed a redirect that had not fired.** The first version
|
||
printed "HOME redirected to …" whenever the subject read the home
|
||
database — and the disarmed twin's own `FAIL Q52` line contradicted it.
|
||
The NOTE is now derived from the actual environment, so the disarmed twin
|
||
prints `HOME NOT redirected -- HOME in the legs' environment is '/root'`.
|
||
The same defect class as Q50's disarmed twin NOTE; caught here before the
|
||
pipe could.
|
||
5. **The first matrix measured nothing.** The real collector pins only under
|
||
`--pin`, and the suite refuses a `--pin` leg (Q44's rule), so the first
|
||
four rows — subjects run without the pin shape — moved nothing and
|
||
proved nothing. The `--q52-witness` flag exists because of this: the
|
||
class's write path is structurally unreachable from the green suite, the
|
||
same shape Q48's witness was built for.
|
||
|
||
## 9. Limits, stated as the item's first limits
|
||
|
||
- **The original-subject legs still inherit the ambient environment**
|
||
(Q43's resolution leg, G8's dry-run leg, G8's in-process deploy leg): a
|
||
subject whose root resolves at import time in the gate's own process
|
||
writes live from a leg the redirect does not cover. Q48's residual,
|
||
unchanged by this item. The witness covers the copy's environment only.
|
||
- **`pwd` reads bypass `HOME`.** `posixpath.expanduser` prefers `HOME` when
|
||
set (so the redirect covers it), but a subject calling
|
||
`pwd.getpwuid(os.getuid()).pw_dir` directly reads the user database and
|
||
the redirect cannot see it. The inventory *names* such a site; the
|
||
redirect does not relocate it. Stated, not fixed.
|
||
- **The inventory is static.** Home reads behind indirection — `getattr`,
|
||
aliasing, a helper module — are outside every static inventory's reach
|
||
(carried from Q46).
|
||
- **`USERPROFILE` is redirected alongside `HOME`** on this host although it
|
||
is a Windows variable and unset here; the redirect is a no-op for it, and
|
||
the pair keeps the mechanism portable rather than POSIX-specific.
|
||
|
||
## 10. Ask
|
||
|
||
Alexander's, unchanged: (a) the **§4 adoption `cp`** — collector
|
||
`4e45021d…` + gate **`78b290ec…`**, the pair that makes
|
||
`python3 helm-selftest.py` green with no environment and puts Q31–Q52's
|
||
protection into production (the deployed gate today has no
|
||
subject-selection path at all); (b) the **`manifest_match` pin** decision
|
||
(stale since 2026-10-08); (c) the **Q13 publication call** (item 13's
|
||
decided note — escalate, do not edit the live page — still controls over its
|
||
`proposed_action`). This run added no new ask, and wrote and restored the
|
||
live held directory twice, by design, under the guard (disclosed in §7).
|
||
|
||
— Merlin
|
||
|
||
---
|
||
|
||
# Q45§9 — Run 19: corrections to this record (2026-10-13, ~18:2xZ)
|
||
|
||
**The item's label is corrected here: `Q45§9`, not `Q52`.** `WORK-QUEUE.md:7167`
|
||
defines a different item as Q52 (Mercury's live-writer attribution and the two
|
||
skipped midnights), and the pipe surfaced the collision in
|
||
`replies/2026-10-13-to-merlin-37.md` §2.3 without executing a rename. The queue's own
|
||
section for this unit is *Q45 §9 follow-on — the home-directory channel*, and the
|
||
`c559d33` commit subject already reads *the Q45 s9 follow-on*, so **`Q45§9`** is the
|
||
label from here on. The gate's internal `check("Q52", …)` registration and the
|
||
`_q52_*` helper names are **deliberately not renamed**: renaming them changes
|
||
`helm-selftest-q52.py`'s digest and would invalidate the byte-level verification the
|
||
pipe performed in reply 37 against the digest as committed. Internal check names are
|
||
identifiers inside a verified artifact, not queue item ids.
|
||
|
||
Nothing above this line was edited. This ledger's rule is append, not rewrite: every
|
||
figure above stands as written, and each correction below carries its measurement.
|
||
**Screen:** every claim below carries a path, a hash, a command with its output, or the
|
||
word `unverified`.
|
||
|
||
## E1 — the gate digest in full, and the commit subject's 63-character cite
|
||
|
||
`sha256sum /root/crucible/q52/helm-selftest-q52.py` →
|
||
`78b290ecf2d93e0cc3087e5ede8baef5c10ca52986706d5da69a13a619dbdd9f` — **64** hex
|
||
characters, 140,365 B / 2,677 lines / **126** `check(` registrations
|
||
(`grep -cE '^[[:space:]]*check\('`).
|
||
|
||
The `c559d33` commit subject carries the same digest as
|
||
`78b290ecf2d93e0c3087e5ede8baef5c10ca52986706d5da69a13a619dbdd9f` — **63** characters,
|
||
one `c` dropped at index 16 (`…d93e0c|3…` against the measured `…d93e0c|c3…`).
|
||
Measured: `git -C /root/troupe-standup log -1 --format=%s c559d33`, lengths printed by
|
||
`len()`. A commit message cannot be corrected by edit, so the correction lives here and
|
||
in the queue's block, not in the commit. The truncated `78b290ecf2d93e0c…` /
|
||
`78b290ec…` forms used in §2, §5 and §10 match the measured value by prefix and are
|
||
unaffected. **The §4 adoption cite is the 64-character value above.**
|
||
|
||
## E2 — §5's disarmed-row pin figure is `unverified`
|
||
|
||
§5's `q52-dis-home` row cites the moved live pin as sha256 `caff1f504855264a…` /
|
||
mtime_ns `1791907100434371499`. Neither string is in any measurement artifact under the
|
||
tree:
|
||
|
||
- `grep -rn 'caff1f504855264a' /root/crucible/q52/` → **3 hits, all my own prose** —
|
||
`q52-commit-msg.txt:5`, `q52-queue-block.md:39`, `checkin-run18.md:50` — and **0**
|
||
in `out/`.
|
||
- `grep -rln '1791907100434371499' /root/crucible/q52/` → the same three files, nothing
|
||
else.
|
||
|
||
The figure is therefore **unverified** (most likely the first matrix run, overwritten by
|
||
the retained one). What the retained artifact records — `out/Q52-MATRIX.json`,
|
||
12,308 B / sha256 `75bd624ef25c5667b81f7a77f8d0fed358305f4a41fbb7fc05a185eaf4cf6a8a`,
|
||
`generated_at 2026-10-13T16:02:51+0000`:
|
||
|
||
- `q52-dis-home` — `guard_live_delta` **before**
|
||
`[120, 5b6d70716153c3f4…, 1791478687099553071, 1791907262852704423]` → **after**
|
||
`[120, 10d12a7d20c5f0ac066a370440944b4a65b0a7669b553a8517d82bb076c0f027,
|
||
1791907358370548372, 1791907358379549112]`, `guard_restored: true`;
|
||
- `q52-raw-home` — `guard_live_delta` **after**
|
||
`[120, 576a47723957844e90d73bb81301ad313bc16c3783000935024718d116c0d310,
|
||
1791907371509627497, 1791907371527628976]`, `guard_restored: true`.
|
||
|
||
These are **not** corrections of the same event — they are different rows' readings,
|
||
recorded so the cell has an artifact behind it. The **direction** is Corroborated (both
|
||
rows' `guard_live_delta` non-empty with `guard_restored: true`; the same file's
|
||
`final_live` reads `5b6d7071…` and `final_live_delta` is `{}`); the **figure** §5
|
||
printed is unverified.
|
||
|
||
## E3 — §5's `q52-dis-check` row names one failure; the artifact names two
|
||
|
||
`out/q52-dis-check.out` reads `helm-selftest: 174 passed, 2 failed`, and the two failing
|
||
lines are at **:186** and **:187**: the `Q52` home-redirect check, and the `Q45`
|
||
module-constant check failing with `HELM_HOME: /root/.helm` — the expected consequence
|
||
of the redirect being cut, not a surprise. §5 names the first; the second is the
|
||
artifact's. Reported by the pipe in reply 37 §1; verified here from the saved stdout.
|
||
|
||
## E4 — four line citations in §2 are off
|
||
|
||
Read at the line in `helm-selftest-q52.py` by `grep -n`:
|
||
|
||
| §2 cites | as | actual |
|
||
|---|---|---|
|
||
| `_q52_home_sites()` | gate line 786 | **:758** (`def _q52_home_sites(_tree):`) |
|
||
| `_q50_rebuild_env` | line 815 | :815 — correct |
|
||
| probe caller | :977 | :977 — correct (its spawn carrying `env=` is at **:993**) |
|
||
| legs caller | :1162 | **:1160** (`… = _q50_rebuild_env(_q43_dir)`) |
|
||
| `check("Q52", …)` | :1204 | **:1205** |
|
||
| `--q52-witness` | line 1261 | section bar at :1261, the flag test at **:1270** |
|
||
|
||
The same class as reply 35's E1 (`q49:1117` → `q49:1203`): the mechanism is where the
|
||
record says it is, the line numbers are not. No claim moves.
|
||
|
||
## E5 — the readback artifact's revision pairing
|
||
|
||
`out/Q52-READBACK.json` (675 B / sha256
|
||
`b0f74587967126e95195a3d407cd446daf85b3ddb39895523c17081047d41066`) carries
|
||
`read_at 2026-10-13T16:09:11Z`, `"revision": "6954cc6"`, and its check-in row is
|
||
**67,347 B / `912985e33be7bd7f…`**. The **65,145 B / `d89b2189…`** figure §7's landing
|
||
table cites is the same file at `c559d33`. Both are true; the pairing was missing.
|
||
**Rule: read a readback artifact's `revision` field before comparing one of its rows
|
||
against a table row.**
|
||
|
||
## E6 — "delta 0 across all 21 invocations" is a net end-state
|
||
|
||
The honest phrasing is **net zero, with two disclosed transient movements**. Measured
|
||
from `out/Q52-MATRIX.json`: of the five rows, exactly two carry a non-empty
|
||
`guard_live_delta` and `guard_restored: true` — `q52-dis-home` and `q52-raw-home` — and
|
||
`final_live_delta` is `{}`. §7's sentence is not false; it is a net reading, and it now
|
||
says so.
|
||
|
||
## E7 — live state, re-measured this run (2026-10-13T18:18:06Z)
|
||
|
||
`/root/.helm/data/snapshot.json` **14,039 B /
|
||
`aa842b0fd88037f83115b4a54d617f27666f4de06ef84f9a64c8fd0a5f04656d` / mtime_ns
|
||
`1791479579475780303` / ctime_ns `1791907371554631194`**; held pin
|
||
`/root/.helm/data/held/stackchain-home.html.sha256` **120 B /
|
||
`5b6d70716153c3f4bd82045d5d71edf88f793a240ce1275c3dfca5759d0ee297` / mtime_ns
|
||
`1791478687099553071` / ctime_ns `1791907371554631194`** — bytes, sha256 and mtime_ns
|
||
identical to Run 18's closing reading; ctime is the restore trace and has not moved
|
||
since `1791907371554631194`. Deployed `/root/.helm/bin/` untouched:
|
||
`helm-collect.py` 27,907 B / `ee0b36a82f23…`, `helm-render.py` 21,766 B /
|
||
`5c67473d8928…`, `helm-selftest.py` 33,647 B / `7ed1bca8f9da…`
|
||
(2026-10-11 11:13:31Z), `helm-validate.py` 3,556 B / `12570786ba70…`,
|
||
`summarize.py` 1,463 B / `738699986778…`; `HELM_COLLECTOR` count **0** — Q40 stands,
|
||
so none of Q31 through Q45§9's mechanisms is in production. `df -h /` → **46 G avail of
|
||
154 G, 71 % used**. Sandbox census both ways (`glob` and `rglob` for `helm-legs-*` /
|
||
`helm-q39-*`) → `[]` / `[]`.
|
||
|
||
## E8 — carried, not corrected: the Q51 record's disk figure
|
||
|
||
`recoveries/helm-runwide-census-hoist.md` §138 reads *Host disk **45 GiB free of** …*;
|
||
this host reads **46 free of 154** today — a dated reading, not a property, and no count
|
||
in that record depends on it. Its other open errata (the `:2476` foot-check citation) is
|
||
settled: both readings are right about their own gate — `:2476` in the Q50 gate,
|
||
`:2482` in the Q51 gate — as the pipe's 14:03Z block records.
|
||
|
||
## E9 — what this run did not do
|
||
|
||
No gate invocation, no live write, no fixture run, no `cp` into `/root/.helm/bin/`, no
|
||
edit to another party's file, no `decisions/` or `inbox/` touch, no secret printed. The
|
||
corrections above are read-only measurements over Run 18's retained artifacts plus three
|
||
appends: this section, a block in the queue, and Run 19 in today's check-in.
|
||
|
||
— Merlin
|
||
|
||
---
|
||
|
||
## E10 — E4's legs-caller row is corrected: **:1162**, not :1160
|
||
|
||
Returned by the pipe in reply 38 §1, confirmed in reply 39. E4's table row reads
|
||
*legs caller | :1162 | **:1160** (`… = _q50_rebuild_env(_q43_dir)`)*. The
|
||
`actual` cell is wrong, and the row contradicts this record's own §2, which says
|
||
*legs caller at :1162*.
|
||
|
||
Measured this run, `grep -n '_q50_rebuild_env(' /root/crucible/q52/helm-selftest-q52.py`
|
||
(gate `78b290ec…`, 140,365 B / 2,677 lines):
|
||
|
||
| line | content |
|
||
|---|---|
|
||
| :815 | `def _q50_rebuild_env(_target):` |
|
||
| :977 | `_q50_probe_env, _q50_probe_redirects, _q50_probe_default_redirects, _q52_probe_home = _q50_rebuild_env(_q39_dir)` — the probe caller |
|
||
| **:1162** | `_q48_env, _q48_redirects, _q48_default_redirects, _q52_home_redirect = _q50_rebuild_env(_q43_dir)` — the legs caller |
|
||
|
||
`:1160` and `:1161` are the two comment lines immediately above it
|
||
(`# call is what Q49's residual needed: before Q50 the probe spawn inherited the` /
|
||
`# gate's ambient environment, and the rebuild was handed to the legs only.`), which
|
||
is where the wrong number came from — a comment read as code. §2's cite stands; the
|
||
erratum's cell is the defect. No mechanism, digest or count moves.
|
||
|
||
## E11 — E2's grep count needs its scope and its clock: **9 lines in 8 files** at 2026-10-13 ~20:5xZ
|
||
|
||
Returned by the pipe in reply 38 §2, restated in reply 39 §0 and accepted in reply 40 §4.
|
||
E2 prints `grep -rn 'caff1f504855264a' /root/crucible/q52/` → **3 hits, all my own
|
||
prose**, with no scope and no clock. Re-run here, same command, same root:
|
||
|
||
| scope | count | files |
|
||
|---|---|---|
|
||
| the command as written, `/root/crucible/q52/` | **9 lines in 8 files** | `checkin-run18.md`, `q52-commit-msg.txt`, `q52-queue-block.md`, `run19-commit-msg.txt`, `run19_append_checkin.py`, `run19_append_queue.py`, `run19_append_record.py` (**2** hits), `run19_probe.py` |
|
||
| `out/` only | **0** (`rc` 1) | — |
|
||
| the three files E2 names, by `grep -c` | **1 / 1 / 1** | `q52-commit-msg.txt:5`, `q52-queue-block.md:39`, `checkin-run18.md:50` |
|
||
| the repository tree at HEAD | **15 lines in 6 files** | queue 3, guard 2, check-in 2, replies 37/38/39 |
|
||
|
||
`1791907100434371499` returns the same 9 lines in the same 8 files. The **3** is a
|
||
Run-18-era reading: those three scratch prose files carry mtime 16:06–16:07Z and were
|
||
the only files under the tree containing the string when the count was taken. Run 19's
|
||
five scripts (`run19_probe.py`, `run19_append_record.py`, `run19_append_checkin.py`,
|
||
`run19_append_queue.py`, `run19-commit-msg.txt`, created 18:13–18:35Z) quote the string
|
||
because they were written to test it, so the count moved under the reading. **The
|
||
finding is unchanged and the direction is Corroborated**: `out/` returns 0 on both
|
||
needles, `out/Q52-MATRIX.json` (12,308 B / `75bd624e…`, `generated_at
|
||
2026-10-13T16:02:51+0000`) records the disarmed row's move as
|
||
`10d12a7d20c5f0ac066a370440944b4a65b0a7669b553a8517d82bb076c0f027` / mtime_ns
|
||
`1791907358370548372` with `guard_restored: true`, and the raw row's as
|
||
`576a47723957844e90d73bb81301ad313bc16c3783000935024718d116c0d310` /
|
||
`1791907371509627497`; **§5's `caff1f50…` / `1791907100434371499` figure stays
|
||
`unverified`** — no artifact carries either string.
|
||
|
||
## E12 — the Run-19 landing block's readback clock is `18:30:31Z`, not `18:31:0xZ`
|
||
|
||
Returned by the pipe in reply 40 §2. The queue's landing block for Run 19 reads back
|
||
*at `2026-10-13T18:31:0xZ`*. The named instrument's own artifact,
|
||
`out/Q45S9-RUN19-READBACK.json` (1,686 B / sha256
|
||
`ccec083ce40f53242ab469f96529d9047eb525047891bedd4b492256fa45b39d`), records
|
||
`"read_at": "2026-10-13T18:30:31.084749+00:00"` and `"revision": "068ac5f"`. Epoch
|
||
**1791916231.084749**. The pipe's supporting measurement — that nothing under the tree
|
||
was written between the read and the block — **reproduces exactly** from a ±360 s band
|
||
census over `/root/crucible/q52`:
|
||
|
||
| Δs from read_at | file |
|
||
|---|---|
|
||
| −170.771 | `run19_append_checkin.py` |
|
||
| −103.364 | `run19-commit-msg.txt` |
|
||
| −26.134 | `run19_readback.py` |
|
||
| +0.013 | `out/Q45S9-RUN19-READBACK.json` — the read itself |
|
||
| **+160.699** | **`run19_append_landing.py`** — the script that wrote the block |
|
||
| +210.365 | `run19-landing-msg.txt` |
|
||
| +279.538 | `run19_readback_final.py` |
|
||
|
||
`+160.699 s` = **18:33:11.78Z**, which is the pipe's own 18:33:11.783898219 to the
|
||
millisecond. So the window is real, the bound is real, and only the printed clock was
|
||
wrong: **`18:30:31Z`** is what the evidence supports. No figure in the block depends on
|
||
the clock — head was `068ac5f` until 18:34:22Z, so every row holds at either reading.
|
||
The queue-side correction is appended beside the block.
|
||
|
||
## E13 — `out/Q45S9-RUN19-READBACK.json` is host-local, not forge-verifiable
|
||
|
||
Returned by the pipe in reply 40 §3, reproduced here. The landing block cites the
|
||
artifact beside three files just shown to be forge-verifiable, but it is not one of
|
||
them:
|
||
|
||
- `git -C /root/troupe-standup ls-tree -r --name-only HEAD` → **244 paths, 0 under
|
||
`out/`** (50 under `recoveries/`); no worktree `out/` directory exists in the repo.
|
||
- `GET https://forge.alexanderwhitestone.com/git/stackchain/troupe-standup/raw/branch/main/out/Q45S9-RUN19-READBACK.json`
|
||
→ **HTTP 404**, 11 bytes.
|
||
|
||
The artifact is real — it is hashed above and its three rows each carry
|
||
`http: 200`, `served_eq_disk: true`, `served_eq_git: true` — but it lives only on this
|
||
host, so **a future auditor must not read that 404 as a missing file**. This is the
|
||
general shape of the Helm lane's evidence: every `out/*.json` in Q31–Q45§9 is
|
||
host-local, and only the `.md` deliverables and the queue are forge-verifiable. It is
|
||
stated here as a property of the lane rather than fixed, because publishing the
|
||
artifacts is a visibility decision, not a lane one.
|
||
|
||
## E14 — the sandbox census is tree-scoped; the whole-crucible census is **26**
|
||
|
||
**Found here, not returned.** E7 prints *Sandbox census both ways (`glob` and `rglob`
|
||
for `helm-legs-*` / `helm-q39-*`) → `[]` / `[]`*. That is true of the `q52` tree —
|
||
both the shallow `glob` and the recursive `rglob` return `[]` — and E7 names the tree,
|
||
so the sentence is not false. The honest census is the recursive one over the whole
|
||
crucible, and it is not empty:
|
||
|
||
| class | count | locations |
|
||
|---|---|---|
|
||
| `helm-legs-*` | **19** | `q43` ×1, `q43/variants` ×2, `q43z/variants` ×1, `q44` ×2, `q45/variants` ×13 |
|
||
| `helm-q39-*` | **7** | `q48` ×7 |
|
||
| **total** | **26** | — |
|
||
|
||
This is the skill's own logged lesson, applied to my own record: *a census globbed at
|
||
the harness root cannot see sandboxes created beside a gate that lives in a
|
||
subdirectory*. The q52 tree is clean; the earlier trees are not. No sandbox is in a
|
||
live path and none is reachable from a gate run, so nothing is broken — but "0
|
||
sandboxes" as a standing line in this lane's record overstates the tree. **The
|
||
sentence should read `0 in the q52 tree; 26 across /root/crucible`.**
|
||
|
||
## E15 — everything else in this record re-derives, measured not accepted
|
||
|
||
Re-measured this run at 2026-10-13 ~20:5xZ, before and after every read:
|
||
|
||
| figure | as recorded | measured |
|
||
|---|---|---|
|
||
| gate `helm-selftest-q52.py` | `78b290ec…`, 140,365 B / 2,677 lines / 126 `check(` | identical — sha256 `78b290ecf2d93e0cc3087e5ede8baef5c10ca52986706d5da69a13a619dbdd9f` |
|
||
| disarmed twin | `be7b306a…`, 140,390 B | identical — `be7b306afa7c5b3a3105fe9d9a073fe22a691ff9b3a0c7fec3dfc23deacf7444` |
|
||
| `_q52_home_sites` | gate line 786 → corrected :758 | **:758** — correct |
|
||
| `_q50_rebuild_env` | line 815 | **:815** — correct |
|
||
| probe caller | :977 | **:977** — correct (its spawn carrying `env=` at :993) |
|
||
| `check("Q52", …)` | :1204 → corrected :1205 | **:1205** — correct |
|
||
| `--q52-witness` | line 1261 / flag test :1270 | **comment at :1262**, flag test **:1270** — the comment is one line lower than E4 says; the flag test is right |
|
||
| `out/Q52-MATRIX.json` | 12,308 B / `75bd624e…`, 16:02:51Z | identical |
|
||
| `out/q52-dis-check.out` (E3) | 191 lines, `174 passed, 2 failed`, FAILs at :186/:187 | identical — :186 is the `Q52` home-redirect check, :187 the `Q45` module-constant check with `HELM_HOME: /root/.helm` and `HELD_DIR: /root/.helm/data/held` escaping the redirect. **E3 re-derives exactly.** |
|
||
| live snapshot | 14,039 B / `aa842b0f…` / mtime_ns `1791479579475780303` / ctime_ns `1791907371554631194` | identical, delta 0 across the run |
|
||
| live held pin | 120 B / `5b6d7071…` / mtime_ns `1791478687099553071` / ctime_ns `1791907371554631194` | identical, delta 0 across the run |
|
||
| deployed `/root/.helm/bin/` | 5 files, `HELM_COLLECTOR` count **0** | identical — `ee0b36a82f23` (27,907), `5c67473d8928` (21,766), `7ed1bca8f9da` (33,647), `12570786ba70` (3,556), `738699986778` (1,463); Q40 stands, so none of Q31–Q45§9 is in production |
|
||
| disk | 46 G free of 154, 71 % | 46 G / 154 G / 71 % |
|
||
|
||
**One new citation of my own, logged with the rest:** E4's `--q52-witness` row puts the
|
||
section comment at :1261; it is at **:1262**. The flag test at :1270 is right. That is
|
||
the sixth line-citation defect in this family and the second one I found by re-reading
|
||
rather than by being told.
|
||
|
||
## E16 — what this run did and did not do
|
||
|
||
Read-only. No gate invocation, no fixture run, no live write, no `cp` into
|
||
`/root/.helm/bin/`, no edit to another party's file, no `decisions/` or `inbox/`
|
||
touch, no secret printed. Live delta **0** on bytes / sha256 / mtime_ns for both live
|
||
objects across every measurement in this run; ctime unmoved at
|
||
`1791907371554631194`, which is still Run 18's restore trace, not a new write.
|
||
|
||
The two corrections that belong to the queue rather than to this file — E12's clock
|
||
and E13's local-artifact label — are appended beside the Run-19 landing block in
|
||
`WORK-QUEUE.md`. Instrument `/root/crucible/q53/measure.py` and `measure2.py`; durable
|
||
artifact `out/Q53-MEASUREMENTS.json`.
|
||
|
||
— Merlin (Run 20)
|
||
|
||
## E17 — my append helper dropped the trailing newline; restored, and logged
|
||
|
||
**Found here, on my own tool, before the landing commit.** `q53/append.py`'s
|
||
`append_last()` wrote `head + sep + addition` and never wrote `tail` back. `tail` was
|
||
the file's single trailing newline, so both the record and the queue lost it — visible
|
||
in `git diff` as the one deleted line in each, `\ No newline at end of file` moving
|
||
between revisions. The three earlier appends in this run (`append.py` for the record and
|
||
the queue, `append_checkin.py` for the check-in) all carried it.
|
||
|
||
Measured: `git diff --numstat` at the Run-20 append read **16 / 0** on the queue and
|
||
**159 / 0** on the record (clean), then **19 / 1** and **28 / 1** once the landing
|
||
appends ran — the `1` in each being the dropped newline, not a content edit. Restored
|
||
by writing one `\n` byte to each file; **no line of content moved**, and the fix is
|
||
byte-countable (`WORK-QUEUE.md` 927,008 → 927,009 B; the check-in 83,853 → 83,854 B).
|
||
|
||
Why it matters here and not only as hygiene: this lane's readback discipline compares
|
||
`served = disk = git cat-file` byte for byte, so a lost trailing newline is a real
|
||
delta an outside reader would have to explain. The rule is **`head + sep + tail +
|
||
addition`, and assert `tail` is whitespace-only before dropping it** — the same
|
||
`count(anchor) == 1` discipline the lane already applies, extended to the tail.
|
||
|
||
**Run 20's landing evidence is its own commit**, filed in
|
||
`checkins/2026-10-13-merlin.md` §*Run 20 landing evidence* and in this queue's Run-20
|
||
landing block, both read back over the anonymous raw path at **HTTP 200, served = disk =
|
||
`git cat-file -p 7673477:<path>`** for all three files. This section does not cite its
|
||
own post-append digest; the record's row is dated at the revision before this append.
|
||
|
||
— Merlin (Run 20)
|
||
|
||
## E18 — the E17 append reproduced the defect it describes; asserted, not intended
|
||
|
||
**The fix failed the first time, and the measurement caught it.** `fix_newline.py`'s own
|
||
`E17` literal ends with `— Merlin (Run 20)` and **no trailing newline**, so appending it
|
||
restored the newline on the queue and the check-in and removed it from the record — the
|
||
same defect, on the very append written to log it. Caught by
|
||
`readback_final.py`, which prints `trailing-newline` per row: **queue `True`, check-in
|
||
`True`, record `False`** at `dc3c2f7`. Fixed by writing one `\n` byte to the record; no
|
||
line of content moved.
|
||
|
||
That is the honest lesson behind E17's rule, and it is stronger than the rule: a
|
||
trailing newline is not a property you get by writing `head + sep + tail + addition`,
|
||
because the **addition itself** has to end with one. The assertion has to be on the
|
||
**written file**, not on the helper: `assert path.read_bytes().endswith(b"\n")` after
|
||
every append, or a readback that prints the flag per row. E17 said "assert `tail` is
|
||
whitespace-only before dropping it"; the assertion that would actually have caught both
|
||
instances is the one on the result. **Both instances are measured above and neither is
|
||
deleted.**
|
||
|
||
— Merlin (Run 20)
|