Files
troupe-standup/checkins/2026-10-13-merlin.md
T
Merlin 26d4020ce2 Q49 landing evidence: the three post-push digests, read back from main
The record's deferred table and the check-in's deferred line are filled from the
anonymous raw read at main, taken after the push (a pre-push readback returns 404
for a new file), so the figures are stable for any outsider. All three HTTP 200
and byte-identical to disk.
2026-10-13 09:07:12 +00:00

37 KiB
Raw Blame History

2026-10-13 — merlin

Screen. Every claim below carries a path, a hash, a URL, or a command with its output, or the word unverified. Nothing secret is printed anywhere in this check-in or in the files it lands.


Run 12 — Q46: the parse guard

Did

Claimed and finished Q46 — the parse guard (Helm lane; the top open item, opened by the pipe on reply 29's C1). Deliverable: recoveries/helm-parse-guard.md (12,284 B / sha256 19fa3ff6d25c85ce4ae5dfbde5af00b832651f76ca17ca42d3eadf17731f3eba, this host). Queue block appended to WORK-QUEUE.md. Scratch tree /root/crucible/q46/; nothing deployed, no cp into /root/.helm/bin/, decisions/ and inbox/ untouched, no another-party file edited, no secret.

The item. A subject carrying the Q35 markers whose source does not parse was admitted by the Q39 admission and then crashed the gate, on every gate from Q39 up. The mechanism, as the pipe named it: the crash detector tests for the literal 'Traceback (most recent call last)' and a top-level SyntaxError prints no such line, so "probe rc≠0, wrote nothing, no traceback" is satisfied by a subject that cannot run at all. What failed was the refusal-path property every gate claims for every subject — and a crashed run left its sandbox behind.

The pair. Collector byte-identical to Q45's (4e45021d80395d3f…, 720 / 29,954) — every change is in the gate. Gate ae69d5dd735b6f3cfe82b3682b02acf4557b2939d104b9f15b81cffaeef016d4 (2,032 lines / 109,643 B) against Q45's 2bbca218… (1,942); diff -u = 113 lines = 2 file + 3 hunk + 90 added + 0 removed + 18 context, and 1,942 + 90 − 0 = 2,032 exactly (out/DIFF-vs-q45.txt, counted by grep -c, not asserted).

Two changes. (1) A parse guard after Q35's source precondition and before the admission probe: compile() the subject's own source; a SyntaxError is refused with exit 2 and a dated message naming the subject, the failure and its position, with the live snapshot read before and after — and the probe directory and the sandbox do not exist yet, so the refused class creates neither. It registers no check(): the green count stays 169 passed / 0 failed. (2) An atexit handler removes the per-invocation sandbox on every exit path — refusal, propagated leg rc, or crash — closing a leak this run measured on the n5 negative (before/after below).

The discriminator, ten invocations, all five gates digest-identical to the revision history (run_negatives.py → out/Q46-NEGATIVES.json, 58,433 B / 028b6b1c68ab48cd…, 21 rows, generated_at 2026-10-13T01:31:42.240730+00:00):

fixture Q39 (9b09f328…) Q43 (c22601ec…) Q44 (18580c10…) Q45 (2bbca218…) Q46 (ae69d5dd…)
noparse-with-markers.py rc 1, 575 B, SyntaxError at the legs' import of the original subject; no sandbox exists yet rc 1, 575 B, sandbox-copy import; legacy/q43/helm-legs-jkij8r1x left rc 1, 0 B, AttributeError: 'NoneType' object has no attribute 'body' at :482 (the Q45 record's unexercised §8(a), now exercised); legacy/q44/helm-legs-8tkclld3 left rc 1, 575 B, sandbox-copy import; legacy/q45/helm-legs-wgn7u3v2 left rc 2, stdout 0 B (e3b0c44298fc1c149…), Q46 REFUSAL: … does not parse - invalid syntax (noparse-with-markers.py, line 13) (line 13, offset 12) - refused at 2026-10-13T01:31:45.419033+00:00, before the admission probe, before any leg, and before any sandbox is created.; no traceback; no leftover
dep-unparseable.py (parses; its dependency does not) rc 2 — Q39 repair 3 rc 2 — repair 3 rc 2 — repair 3 rc 2 — repair 3 rc 2 — repair 3

The class is split, and both halves are measured: the subject's own source is refused by the parse guard; a dependency SyntaxError raises with a traceback and is refused by repair 3 on all five gates. Repair 3 stays load-bearing — the guard is the gap between them, not a second layer.

C2 reproduced mechanically. On the crashed rows the ROOT-anchored leftover() reports [] while the recursive census reports legacy/q4X/helm-legs-…: a gate creates its sandbox beside itself, so a glob anchored at ROOT cannot see it. The harness takes both censuses per row; the shipped gate's atexit handler means no run of it leaves one.

The n5 leak, before/after. The pre-atexit gate (1fd889ca…, the shipped gate minus the handler) run with n5: rc 2, leftover work/helm-legs-3fancddl (out/q46-noatexit-n5.{out,err}, measured live); the shipped gate, same fixture: rc 2, leftover []. The leak is not the item's class (n5 parses) — the criterion generalises to every early exit, and the measurement said the refusal path did not cover a propagated leg rc.

Green pair, five consecutive invocations (run_green.py, logs on disk at out/q46-green-{1..5}.{out,err}): rc 0 each, 169 passed / 0 failed, VERDICT: ALL GATES PASS, 13,312 B stdout, 12.17–12.62 s, live delta 0, held delta 0, no leftover.

Live state, delta 0 across all 27 invocations (21 harness rows + 5 green runs + the noatexit run): snapshot /root/.helm/data/snapshot.json 14,039 B / sha256 aa842b0fd88037f83115b4a54d617f27666f4de06ef84f9a64c8fd0a5f04656d / mtime 2026-10-08 17:12:59.475780303 +0000; held pin /root/.helm/data/held/stackchain-home.html.sha256 120 B / 5b6d70716153c3f4…. Deployed bin untouched (ee0b36a82f23fcf3… / 7ed1bca8f9da624f… / 5c67473d89283c3d…). Final tree: 0 sandboxes (find /root/crucible/q46 -name 'helm-legs-*' -o -name 'helm-q39-*' → 0, after cleanup_final.py). Host disk 46 GB free of 154 GB (71 % used) (df -h /).

Four defects of mine, logged first in the deliverable: the first harness run crashed on a missing fixture path (the C1 fixture was at the tree root, not in fixtures/); the first harness wrote the green logs as empty files (re-measured with run_green.py, real logs on disk); in this tree the Q44 crash carries 0 B of stdout where the pipe measured 4 B on the same fixture — a tree-and-clock difference, recorded not reconciled; and the n5 leak was found by this run's own census and fixed here rather than left as somebody else's finding.

Blocked

Nothing in my lane. Same standing gates, all Alexander's: the §4 adoption cp — now collector 4e45021d… + gate ae69d5dd… (superseding 2bbca218…); Q40's finding stands (the deployed gate 7ed1bca8… reads HELM_COLLECTOR zero times), so none of Q31–Q46's protection is in production until it lands — Q9 (checksum handover), Q13 (wording fixes), Q19 (wizard onboarding).

Next

  1. The pipe's verification of this run, when it lands. Everything is re-runnable from /root/crucible/q46/: run_negatives.py and run_green.py are self-contained, and figures.py / print_figs.py / show_rows.py regenerate the record's figures from the JSONs.
  2. Merlin's Q45 §9 follow-on stays open and unclaimed: resolve every path the subject declares at runtime, rather than rewriting the literals it declares statically. Beside the adoption decision, that is the one Helm-side follow-on I would name.
  3. Q7, measured: the Helm lane's executable set is empty again. The adoption, Q9, Q13/Q19 and Q42 are human- or counterparty-gated. No new item opened — the next Helm unit is a decision, not a task.

Ask

Alexander: nothing blocking. The standing items, consolidated: (a) rule on the §4 adoption cp — collector 4e45021d… + gate ae69d5dd…, the pair that makes python3 helm-selftest.py green with no environment and puts Q31–Q46'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; a static render cannot say so); (c) the Q13 publication call (item 13's decided note — escalate, do not edit the live page — still controls over its proposed_action).


Landing evidence, appended after the commit and read back over the anonymous raw path (https://forge.alexanderwhitestone.com/git/stackchain/troupe-standup/raw/branch/main/<path>), no credential — each HTTP 200 and cmp byte-identical to disk:

file status / bytes sha256
recoveries/helm-parse-guard.md 200 / 12,284 19fa3ff6d25c85ce4ae5dfbde5af00b832651f76ca17ca42d3eadf17731f3eba
WORK-QUEUE.md 200 / 751,695 1c2d9a3937994d068d02c7c3d59d6df46e6e6513525b83547d71c69b3db64f99
checkins/2026-10-13-merlin.md 200 / 9,599 (the committed revision a6b1ae3, which is what the server served at readback time and what git cat-file -p a6b1ae3:checkins/2026-10-13-merlin.md reproduces locally — a file cannot hash itself, so this row cites the revision it was written against, and the fill below lands as its own commit) 36123a348755789cf2d862ccbd6f1387619f73598a5e07f891065a034a6c4d8a

Read-back method: python3 /root/crucible/q46/readback.py (anonymous GET, no credential), exit 0, VERDICT: ALL READBACKS IDENTICAL — all three files HTTP 200 over the anonymous raw path and byte-identical on three sides: the bytes the server served, the bytes on this host's disk, and git cat-file -p a6b1ae3:<path>. The fill block below lands as its own commit; its revision is readable over the same path by the same script.

Re-measured after the push, from this host: the live snapshot /root/.helm/data/snapshot.json is 14,039 B / sha256 aa842b0fd88037f83115b4a54d617f27666f4de06ef84f9a64c8fd0a5f04656d / mtime 2026-10-08 17:12:59.475780303 +0000 — delta 0 across the whole run — and the deployed /root/.helm/bin/ is untouched (ee0b36a82f23fcf3… / 7ed1bca8f9da624f… / 5c67473d89283c3d…). Nothing was deployed, published, rebuilt or re-run beyond the measurements above; decisions/ and inbox/ were not touched; no another-party file was edited; no secret is printed in this check-in or in the files it lands — the long strings are gate check names, refusal messages, file paths and sha256 digests. This run's commits are authored Merlin <merlin@agents.invalid> (git -c), so the monitor sees them as counterparty content per Q36's v1.3 rule.

—

Run 13 — Q47: the read path

Did

Claimed and finished Q47 — the read path (Helm lane; opened by the pipe on reply 30's C1, its new Documented finding). Deliverable: recoveries/helm-read-path-guard.md (12,821 B / sha256 5bf9ba115071a97f07a4d6e09bd4d4add78646680724ff63e32f2d101cdc1516, this host). Queue block appended to WORK-QUEUE.md. Scratch tree /root/crucible/q47/; nothing deployed, no cp into /root/.helm/bin/, decisions/ and inbox/ untouched, no another-party file edited, no secret.

The defect, both directions, one cause. A subject with # -*- coding: latin-1 -*- and one latin-1 byte — bytes-compile OK, python3 <fixture> rc 0, so the interpreter runs it — crashed the gate at COLLECTOR.read_text(): UTF-8 decode raises UnicodeDecodeError, rc 1, a traceback, no dated refusal. The real collector with a UTF-8 BOM — CPython strips a BOM when it reads a source file — was falsely refused by the Q46 guard: the decoded text still carries U+FEFF and compile() calls it an invalid non-printable character. The gate reads a decoded str; the interpreter compiles the file's bytes.

The pair. Collector byte-identical to Q46's (4e45021d80395d3f…, 720 / 29,954, measured by sha256) — every change is in the gate. Gate 76f2292b9a6cad08… (115,182 B / 2,124 lines) against Q46's ae69d5dd… (110,509 / 2,032); out/DIFF-vs-q46.txt = 130 lines = 2 file + 4 hunk + 96 added + 4 removed + 24 context, and 2,032 + 96 − 4 = 2,124 exactly (the lane decomposition rule: added = ^+ minus +++, removed = ^- minus ---).

What changed — one reader, four read sites, one refusal. (1) _q47_subject_text() reads the subject the CPython way: bytes through tokenize.detect_encoding (BOM first, then the PEP 263 cookie, then utf-8), decoding with utf-8-sig when a BOM is present so it is stripped exactly as the interpreter strips it. (2) A dated exit-2 refusal at Q46's guard point — after Q35's source precondition, before the admission probe, before any leg, before any sandbox — with the live snapshot read before and after. (3) All four subject-source reads routed through the one reader (Q35's _q35_src, the admission's _q39_src_text, the G8 source read, the G8 source-guard check). (4) One Q47 NOTE on stderr, never stdout, and only when the subject's encoding is not plain utf-8, so a green pair's stdout shape is unchanged and the positive side is visible. The parse guard, the redirect, every check() and the 169 count are untouched.

The interpreter's own verdict, measured not assumed (make_fixtures.py, fixtures built from the real collector with asserted counts): collector-latin1.py (30,028 B / 6ce8bf90292d83f1…) → python3 <fixture> --skip-tests rc 2, the destination rule refusing — the file runs; collector-bom.py (29,957 B / d14906c868e24997…) → rc 2, runs; collector-undecodable.py (30,006 B / 47fc3580fdfe8308…) → rc 1, SyntaxError: Non-UTF-8 code starting with '\xe9'.

The discriminator, fifteen invocations, five gates (run_negatives_q47.py → out/Q47-NEGATIVES.json, 57,247 B, 21 rows, generated_at 2026-10-13T03:46:55.002706+00:00; legacy gates digest-verified 9b09f328… / c22601ec… / 18580c10… / 2bbca218…):

fixture Q39 Q43 Q44 Q45 Q47 (shipped)
collector-latin1.py rc 1 — UnicodeDecodeError … position 30026 (0.16 s) rc 1 — same (0.13 s) rc 1 — same (0.15 s) rc 1 — same (0.14 s) rc 0, 169 passed / 0 failed, ALL GATES PASS, 13,312 B, 12.44 s, Q47 NOTE … (iso-8859-1), no leftover
collector-bom.py rc 0, 164 passed, ALL GATES PASS, 11,398 B, 10.12 s (see the finding below) rc 1 — SyntaxError: invalid non-printable character U+FEFF at the sandbox copy's import, 575 B, legacy/q43/helm-legs-_sja2yko left (0.61 s) rc 1 — AttributeError: 'NoneType' object has no attribute 'body', 0 B, legacy/q44/helm-legs-sl1v6yx5 left (0.22 s) rc 1 — SyntaxError … U+FEFF, 575 B, legacy/q45/helm-legs-8qmmr11u left (0.66 s) rc 0, 169 passed / 0 failed, ALL GATES PASS, 13,312 B, 12.22 s, Q47 NOTE … (utf-8-sig), no leftover
collector-undecodable.py rc 1 — UnicodeDecodeError … position 30004 (0.14 s) rc 1 — same (0.14 s) rc 1 — same (0.19 s) rc 1 — same (0.14 s) rc 2, stdout 0 B (e3b0c44298fc1c149…), dated Q47 REFUSAL: … cannot be read the way CPython reads a source file - 'utf-8' codec can't decode byte 0xe9 in position 30004: invalid continuation byte - refused at 2026-10-13T03:47:32.685519+00:00, before the admission probe, before any leg, and before any sandbox is created. — no traceback, no leftover (0.13 s)

The item's done-criteria, measured: the latin-1 subject proceeds past the guard and runs the whole suite green; the BOM subject is not refused and runs green; a genuinely undecodable subject is a dated exit-2 refusal on the shipped gate (the four pre-Q46 gates stay frozen controls at rc 1, measured, not patched); green still 169 passed / 0 failed; no sandbox left; live_delta = 0 and live_held_delta = 0 on all 21 rows.

The Q39-bom row is a finding, not noise. The oldest gate runs the BOM subject green with 164 passed while Q43/Q44/Q45 crash on it. Q39's own report says why — parse line -: the subject does not parse: invalid non-printable character U+FEFF — and continues: Q39 never compiles the subject's text; it imports the file (where CPython strips the BOM) and runs the probe copy as a subprocess whose SyntaxError the crash detector cannot see. The read-path class is a property of the gates that compile the subject's text (Q43/Q45's sandbox-copy import, Q44's ast), not of subject handling generally.

Regression, all still refused. q47-q46reg-noparse: rc 2, the dated Q46 REFUSAL unchanged — the read-path fix did not loosen the parse guard. q47-neg-n1…n5: all five Q39 negatives rc 2, live delta 0.

Green pair, five consecutive invocations (run_green_q47.py → out/Q47-GREEN.json, 2,725 B; logs out/q47-green-{1..5}.{out,err}): rc 0 each, 169 passed / 0 failed, ALL GATES PASS, 13,312 B stdout / 425 B stderr, 12.03–12.58 s, live delta 0, no leftover; stdout digests 21920260cade855a… / 580021754ed55d3d… / 49025d65cc3082e5… / c8155f076d214d24… / 1d2c7d69a8531d39…. out/q47-green-1.out vs Q46's green log: 184 lines each, exactly 4 differ, all four randomized names (the /tmp render dest, the deploy temp dir, the two sandbox-path lines) — the read path changed nothing the suite prints.

Live state, delta 0 across all 26 invocations (21 harness rows + 5 green runs), censused on (bytes, sha256, mtime_ns, ctime_ns): snapshot 14,039 B / aa842b0fd88037f8… / mtime_ns 1791479579475780303 / ctime_ns 1791789490146606356; held pin 120 B / 5b6d70716153c3f4… / mtime_ns 1791478687099553071 / ctime_ns 1791846636217687791 (the Q45 restore's trace). Deployed bin untouched (ee0b36a82f23fcf3… / 7ed1bca8f9da624f… / 5c67473d89283c3d…, plus helm-validate.py 12570786ba703787… and summarize.py 7386999867789694…). Final tree: 0 sandboxes.

Four defects of mine, logged first in the deliverable. (1) The harness's verdict block passed fixture names with .py into a row-name builder that strips it, so the verdict raised StopIteration → RuntimeError after the rows JSON was written; the verdict was recomputed from the rows by recompute_verdict.py and run_negatives_q47.py is patched (the strip) — no gate invocation was re-run. (2) The negative fixtures' destination literals still name the q46 tree (inherited by cp -a), so this run's fixture-dest census was vacuous; evidence no cross-tree write happened: ls /root/crucible/q46/fixtures shows no n*-dest/dep-dest directories, and each negative row's stderr shows the leg refusing its own destination. (3) The copies this gate writes are UTF-8 by construction (their header lines push the subject's cookie past PEP 263's line-2 limit), so non-ASCII in executable strings under a cookie would be re-encoded — the fixture carries its byte in a comment only; recorded as a limit, not fixed. (4) The guard stays static — runtime-generated code is out of every static guard's reach (carried from Q46).

Blocked

Nothing in my lane. Same standing gates, all Alexander's: the §4 adoption cp — now collector 4e45021d… + gate 76f2292b… (superseding ae69d5dd…); Q40's finding stands (the deployed gate 7ed1bca8… reads HELM_COLLECTOR zero times), so none of Q31–Q47's protection is in production until it lands — Q9 (checksum handover), Q13 (wording fixes), Q19 (wizard onboarding), Q42 (zephyrus's).

Next

  1. The pipe's verification of this run, when it lands. Everything is re-runnable from /root/crucible/q47/: run_negatives_q47.py, run_green_q47.py, recompute_verdict.py, figures_q47.py and show_rows_q47.py are self-contained.
  2. Merlin's Q45 §9 follow-on stays open and unclaimed: resolve every path the subject declares at runtime, rather than rewriting the literals it declares statically. Beside the adoption decision, that is the one Helm-side follow-on I would name.
  3. Q7, measured: the Helm lane's executable set is empty again. The adoption, Q9, Q13/Q19 and Q42 are human- or counterparty-gated. No new item opened — the next Helm unit is a decision, not a task.

Ask

Alexander: nothing blocking. The standing items, consolidated: (a) rule on the §4 adoption cp — collector 4e45021d… + gate 76f2292b…, the pair that makes python3 helm-selftest.py green with no environment and puts Q31–Q47'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).

Landing evidence for this run lands as its own commit after the push, over the anonymous raw path, the same way Run 12's did.

—

Landing evidence, appended after the push and read back over the anonymous raw path (https://forge.alexanderwhitestone.com/git/stackchain/troupe-standup/raw/branch/main/<path>), no credential — each HTTP 200 and cmp byte-identical on three sides: the bytes the server served, the bytes on this host's disk, and git cat-file -p 8cb941f:<path>. Method: python3 /root/crucible/q47/readback_q47.py, exit 0, VERDICT: ALL READBACKS IDENTICAL (run at 2026-10-13T03:54:02Z, after the push c104fbb..8cb941f).

file status / bytes sha256
recoveries/helm-read-path-guard.md 200 / 12,821 5bf9ba115071a97f07a4d6e09bd4d4add78646680724ff63e32f2d101cdc1516
WORK-QUEUE.md 200 / 774,704 30d735ef91bcd5f094516b6950f02e3cecd03d9de83b4d70c0a15f80163e5a35
checkins/2026-10-13-merlin.md 200 / 20,685 (the committed revision 8cb941f, which is what the server served at readback time and what git cat-file -p 8cb941f:checkins/2026-10-13-merlin.md reproduces locally — a file cannot hash itself, so this row cites the revision it was written against, and this fill lands as its own commit) 88670dbf8094cd0a47ff194424e6d68cc18b55c66a20e225cba3b8710bb2e12a

Re-measured after the push, from this host: the live snapshot /root/.helm/data/snapshot.json is 14,039 B / sha256 aa842b0fd88037f83115b4a54d617f27666f4de06ef84f9a64c8fd0a5f04656d — delta 0 across the whole run — and the deployed /root/.helm/bin/ is untouched (ee0b36a82f23fcf3… / 7ed1bca8f9da624f… / 5c67473d89283c3d…). Nothing was deployed, published, rebuilt or re-run beyond the measurements above; decisions/ and inbox/ were not touched; no another-party file was edited; no secret is printed in this check-in or in the files it lands — the long strings are gate check names, refusal messages, file paths and sha256 digests. This run's commits are authored Merlin <merlin@agents.invalid> (git -c), so the monitor sees them as counterparty content per Q36's v1.3 rule.

— Merlin


Run 14 — Q48: the runtime-resolution redirect

Did

Claimed and finished Q48 — the runtime-resolution redirect (Helm lane; the top unclaimed executable item, discharging Q45's own §7 limit 1 and the follow-on the pipe's Q47 verification block left named and unclaimed). Deliverable: recoveries/helm-runtime-redirect.md (14,030 B / sha256 8da99a7994c7b8fb…, this host). Queue block appended to WORK-QUEUE.md. Scratch tree /root/crucible/q48/; nothing deployed, no cp into /root/.helm/bin/, decisions/ and inbox/ untouched, no another-party file edited, no secret printed.

The class. Q45 relocates every absolute path literal in the subject that resolves inside the live data root. Its §7 limit 1 names what it cannot see, verbatim: "a root resolved at runtime from __file__, an environment variable or argv, where there is no literal to rewrite." The pipe's Q47 block carried that as a named, unclaimed follow-on and said why it had not measured it: a miss writes the live snapshot, and a restore moves the mtime_ns/ctime_ns values three records cite as deltas.

The pair. Collector byte-identical to Q33–Q47's (4e45021d80395d3f…, 720 / 29,954 — every change is in the gate). Gate a590170f… (122,186 B / 2,287 lines) against Q47's 76f2292b… (115,182 / 2,124); out/DIFF-vs-q47.txt (8,300 B / 71c593e2954ecaf3…) = 186 lines = 2 file header + 3 hunk header + 143 added + 0 removed + 18 context, and 2,124 + 143 − 0 = 2,287 exactly (counted by grep -c, not asserted).

Two mechanisms, two checks. (1) The environment redirect: the env every subject leg runs in is rebuilt — every variable the subject's own source reads (inventoried by ast: os.environ.get, os.environ[...], os.getenv) whose value resolves inside the live data root is redirected to the per-invocation sandbox. A redirect, not a refusal; the inventory prints as a Q48 NOTE on stderr on every path, including the zero case. (2) Rule (4), the argv redirect, scoped to the write surface — the live snapshot and the live held directory — not the whole live root. __file__ needs no mechanism: the copy lives in the sandbox, so a root composed from it resolves there by construction (measured, not asserted). Two new check()s: green count 169 + 2 = 171 passed / 0 failed.

Green pair, five consecutive invocations (run_green_q48.py, HELM_COLLECTOR=<the real collector>, Q48_ROOT unset — the real collector carries the live root as a literal, so the control is the shipped collector): rc 0 each, helm-selftest: 171 passed, 0 failed / VERDICT: ALL GATES PASS, 13,514 B stdout, 12.45–13.41 s; stdout digests 81fd73b8a32e5c10 / 7a5cfac833623db7 / e88ff3763a73fef1 / 1f0ec83eccdc2a75 / eefcc271232b005e (identical size, digests differ only in the gate's own mkdtemp deploy path and sandbox name). 171 = 169 + 2; no pre-existing check moved.

The discriminator, on the real collector with one mechanical change (run_witness_q48.py → out/Q48-WITNESS.json, 4,686 B / 50e7180ac30c3aa0…). Subject: collector-env.py = the deployed collector with HELM_HOME = Path(os.environ.get("Q48_ROOT", "/root/.helm")) (29,982 B / 4739ced839a30564…, asserted by count: exactly one HELM_HOME assignment, transform landed). The witness is an extra --q48-witness flag on a copy of the gate (helm-selftest-witness.py); the shipped gate never passes it, so the green run cannot be affected. It builds the copy exactly as the green run does, prints the rebuilt environment for every variable the subject reads, and runs the copy --deploy --pin --skip-tests --out <sandbox> — --out forces the snapshot into the sandbox, so the only path the redirect governs is the HELD_DIR the pin writes.

row root value rc Q48_ROOT the copy runs in copy's held write live held
q48-armed-env <protected> 0 /root/crucible/q48/protected (outside the live root — no redirect fires) [] unchanged
q48-armed-env-liveroot /root/.helm 0 /root/crucible/q48/helm-legs-ppqy3cew — redirected (env_redirects: {"Q48_ROOT": ["/root/.helm", "<sandbox>"]}) <sandbox>/data/held/stackchain-home.html.sha256 unchanged
q48-disarmed-env <protected> 0 /root/crucible/q48/protected [] unchanged
q48-disarmed-env-liveroot /root/.helm ABORT /root/.helm — nothing redirected wrote the LIVE held directory NONZERO, restored by the guard
q47-blindspot-env <protected> 0 n/a HELM_HOME/SNAPSHOT_PATH/HELD_DIR all /root/crucible/q48/protected — the class is invisible to the pre-Q48 gate unchanged

Prevention separated from detection, by measurement: the armed row redirects the live root into the sandbox and the pin lands there; the disarmed row, same subject, same live root value, writes live and the guard catches it, restores byte-exact and aborts. The class is real, one mechanism away, and the mechanism closes it.

Blocked

Nothing blocked the item. One residual is mine and is stated as the item's first limit, not a footnote: the gate's legs run the original subject with the inherited environment (Q43's resolution leg, G8's dry-run leg), so a subject whose root is the live root writes the live held directory from a leg the redirect does not cover — the redirect covers the copy, the snapshot watch is snapshot-only, and the held census is once per run rather than per leg. Closing it needs a per-leg held census, or the original-subject legs to inherit the rebuilt environment; neither is taken here. Rule (4) fired zero times this run (no leg argument resolved inside the held directory), so its only exercised part is the zero case. The gate's suite is not the discriminator for this class — no subject leg runs the copy with --deploy --pin, and the Q43 resolution-leg check is a live-identity check that cannot pass for a scratch-root subject — which is why the witness path exists as an extra flag rather than as a change to the green run.

The incident, disclosed in full — this run wrote the LIVE held directory three times

Not discovered: caused, measured, restored, and disclosed. Three separate live writes of /root/.helm/data/held/stackchain-home.html.sha256 happened in this tree, each by a subject resolving the live root at runtime with the redirect not in the path:

  1. The v1 fixture-verification run (~05:5xZ): make_fixtures_q48.py's verify_interpreter() ran argv-root.py with no --root, so the fixture's live default resolved and it pinned the live held directory with a 21-byte fixture string. Restored byte-exact by restore_held_q48.py, which refuses to restore unless the backup's digest matches the recorded 5b6d7071…. Post-restore: 120 B / 5b6d7071… / mtime_ns 1791478687099553071, VERDICT: RESTORED BYTE-EXACT; ctime_ns moved to 1791871227057299704, which a restore cannot undo. The fix that followed: every fixture carries an explicit protected-root default and an assertion refusing a live default.
  2. The v1 driver's first armed row (~06:0xZ): the driver aimed the armed rows at the LIVE root and its own abort_if_live_moved() guard fired on the first q48-armed-env row and restored byte-exact. Cause: the residual above. v2 of the driver used the protected root as the universal root value and witnessed the class at the mechanism level instead.
  3. The q48-disarmed-env-liveroot falsifier (~06:3xZ): with the redirect cut out and the live root as the value, the copy's pin wrote the live held directory (5b6d7071… → c0532a8b…, a real page hash). The guard aborted, restored byte-exact, and the driver recorded the row as an ABORT and continued, so the JSON the record rests on exists.

Final live state, measured after every row: snapshot 14,039 B / aa842b0fd88037f8… / mtime_ns 1791479579475780303; held pin 120 B / 5b6d70716153c3f4… / mtime_ns 1791478687099553071 — live_delta = 0 on both objects across the whole witness run. The ctime_ns restore traces are the durable record and are not recoverable.

Next

The residual above is the natural successor and is deliberately not opened here: it is a one-line change to a mechanism this run only just built, and it deserves its own measurement rather than a rider on this one. The §4 adoption pair is now collector 4e45021d… + gate a590170f… (superseding Q47's 76f2292b…); the gate is still moving, so a copy now lands Q48 with the residual a known gap. Q40's finding stands — the deployed gate 7ed1bca8… reads HELM_COLLECTOR zero times — so the cp remains Alexander's and is the only thing that puts Q31–Q48's protection into production.

Ask

Alexander's, unchanged: (a) the §4 adoption cp of the pair into /root/.helm/bin/; (b) the manifest_match pin decision, stale since 2026-10-08; (c) the Q13 publication call. This run added no new ask. Two things he may want on the record: the live held directory was written and restored three times during this work (above), and the residual that allowed it is named rather than argued away.

— Merlin


Run 14 landing evidence

Read back over the anonymous raw path with no credential (curl against …/raw/branch/main/…), and byte-compared on three sides — the bytes the server served, the bytes on disk, and git cat-file -p 20623b6:<path>:

file HTTP / bytes sha256 (served = disk = git)
recoveries/helm-runtime-redirect.md 200 / 14,030 8da99a7994c7b8fb…
checkins/2026-10-13-merlin.md 200 / 32,103 6c6eeded0c2bb2c5…
WORK-QUEUE.md 200 / 791,037 bba7bc803b449c9b…

Three-way identical on all three. HEAD = 20623b66fd6d66da8bb38d95ba3a136f606a02dc. The check-in's own row carries no self-hash, per the lane's no-self-hash convention; the blob above is the pre-append revision and this append is the post-append one.

Run 15 — Q49: the runtime-resolved root's live write path

Screen

Merlin, Helm lane, 2026-10-13, this host. Item Q49 — the top open item in the queue and the only Helm unit not human- or counterparty-gated (checked against WIZARDS-TABLE.md first: no named lane for it). Scratch tree /root/crucible/q49/. Every figure measured this run; the two things I could not measure are labelled.

Did

  • (a) Named the invocation and reproduced it. env-root-v1.py (5,634 B / 76b55b8a…) = q48/env-root.py with its two default literals put back to /root/.helm (asserted count 2). Seven rows under a digest-verified guard (q49_guard.py, refuses to restore unless the backup digest matches): the fixture run --dry-run / bare / --deploy --out <scratch> with Q48_ROOT unset each moved the live held pin, and HELM_COLLECTOR=<that fixture> Q48_ROOT unset python3 q48/helm-selftest.py moved it too (rc 1). All three Q48_ROOT=<scratch> controls: no move. Written content measured by a 5 ms polling watcher: 14 B / 784b9bde… / 'q48-env-pin\n'. Artifact out/Q49-ATTRIBUTION.json (6,495 B / 89a82729…).
  • (b) Closed the class, green 5/5. helm-selftest-q49.py (128,948 B / 2,445 lines / 085ddf4f…): the DEFAULT redirect (inventory every os.environ.get("X","<default>") by ast, redirect a default literal resolving inside the live data root), a per-leg census of both live objects, and a guard on the in-process --deploy leg. Checks 120 → 123 (+3 check("Q49", nothing else). Five green runs: rc 0, 174 passed, 0 failed, 13,863 B stdout, digests 13cf5cf7 / d4666b9f / 0abd28cd / 5ed81c37 / bbbd1901, live delta 0. Artifact out/Q49-GREEN.json.
  • The 2×2 discriminator, predictions written first. Armed/unset → delta 0; default-redirect off → the pin moves and the census names the leg; in-process rebind off and both off → the pin moves. Prevention and detection measured separately. Artifact out/Q49-WITNESS.json (13,785 B / 877138d0…).
  • Two findings beyond the item. (i) G8's --deploy branch runs hcg.main() in-process with only SNAPSHOT_PATH patched, so an import-time-resolved root wrote the live snapshot (14,039 → 137 B, reproduced and restored). (ii) See Blocked.

Blocked

  • One unattributed live write, and it is a residual, not a closed item. In the fully disarmed configuration the live pin moves (three runs), the traced twin records exactly one fixture main() per run and its banner names the sandbox, no spawn names the live path, both Q49 censuses report no move — and the watcher still sees 120 B → 14 B at t+280 ms. The write is real and its content is the env fixture's own pin string; the mechanism is unattributed. I will not guess. It is the Helm lane's next unit.
  • Nothing deployed, by design. /root/.helm/bin/helm-selftest.py is still 7ed1bca8… and reads HELM_COLLECTOR 0 times, so none of Q31–Q49 is in production. The cp of collector 4e45021d… + gate 085ddf4f… is Alexander's under §4.

Next

  • The §5 residual: find the writer the trace cannot see, or narrow the census window.
  • Unchanged and still his: the cp, the manifest_match pin stale since 2026-10-08, the Q13 publication call.

Ask

  • Alexander: authorise the cp of collector 4e45021d… + gate 085ddf4f… into /root/.helm/bin/, or say to keep iterating. Nothing else in this run needs a decision.

Run 15 landing evidence

Pushed as de74527 (a7b52e5..de74527, remote main), 3 files / +335. Anonymous raw reads at main, all HTTP 200 and byte-identical to disk:

  • recoveries/helm-resolved-root-live-path.md — 15,220 B, sha256 a64f26e740f61cd0e2f305f7f5e915cd269522b85e68c0c51e0709eeba4edd2b
  • checkins/2026-10-13-merlin.md — 36,515 B, sha256 cdfed86a1a0be6886fa4ba1b905084295f6ac43d669fbb8dff15eb0991eefc36
  • WORK-QUEUE.md — 823,541 B, sha256 d0bf9080f9ef61570d30cb85a639b56adbf98bceb3ba30adf2b15e142677b8d2
  • Commit: git -c user.name=Merlin -c user.email=merlin@agents.invalid commit -F /root/crucible/q49/q49-commit-msg.txt → de74527.
  • Scratch tree /root/crucible/q49/ is not committed; the gate copies (helm-selftest-q49.py 085ddf4f42ae80ea…, -nodefault 46ba830cd4b9e690…, -inproc-off d1481ca890fcd543…, -disarmed 63d5409b3502a22e…) and the fixture (env-root-v1.py 76b55b8ab0203c4f…) stay in the scratch tree, reproducible by patch_gate_q49.py and reproduce_q49.py.