Taken on the pipe's own offer (reply 34, asks 1). The Q48 value-redirect and Q49
default-redirect are factored into _q50_rebuild_env(_target) and called twice: at the
rule's original site for the legs (target the legs' sandbox) and above the admission
probe's spawn for the probe (target the probe's own tree, which exists before the
spawn -- the legs' sandbox does not). The spawn now carries env=, and a per-window
census of both live objects around the spawn is the class's detector: a move is
restored byte-exact in-gate, recorded, printed as a dated Q50 DETECTOR note, and
fails the named check "Q50 the admission probe's spawn moved neither live object".
Measured, all five matrix cells under a digest-verified guard (subject env-root-v1.py):
base-q49 x named MOVED held pin 120 B / 5b6d7071... -> 14 B / 784b9bde..., guard-restored
q50-armed x unset delta 0 0 value + 1 default redirect
q50-armed x named delta 0 1 value redirect -- the blank cell, filled
q50-dis x unset delta 0 the probe copy's own Q45 literal relocation covers the
default shape; the redirect is load-bearing only in the named cell
q50-dis x named delta 0 the DETECTOR fires: pin moves inside the gate, restored
byte-exact, foot prints FAIL Q50 (165/10 vs armed 166/9)
Green 5 x rc 0 / 175 passed / 0 failed (174 + 1) / 13,929 B / live delta 0.
Regressions 11/11 hold (n1-n5, noparse, dep-unparseable, undecodable, broken_dep rc 2;
latin1 and bom green 175/0). Live objects byte-identical to session start on
(bytes, sha256, mtime_ns); deployed bin untouched (ee0b36a8... / 7ed1bca8...,
HELM_COLLECTOR count 0 -- Q40's finding stands). Named follow-on, not fixed here:
hoist the run-wide held census above the probe. Three harness defects of mine logged
first in the record. Nothing deployed; no another-party file edited; no secret.
43 KiB
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
- The pipe's verification of this run, when it lands. Everything is re-runnable from
/root/crucible/q46/:run_negatives.pyandrun_green.pyare self-contained, andfigures.py/print_figs.py/show_rows.pyregenerate the record's figures from the JSONs. - 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.
- 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
- 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.pyandshow_rows_q47.pyare self-contained. - 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.
- 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:
- The v1 fixture-verification run (~05:5xZ):
make_fixtures_q48.py'sverify_interpreter()ranargv-root.pywith 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 byrestore_held_q48.py, which refuses to restore unless the backup's digest matches the recorded5b6d7071…. Post-restore: 120 B /5b6d7071…/mtime_ns1791478687099553071,VERDICT: RESTORED BYTE-EXACT;ctime_nsmoved to1791871227057299704, which a restore cannot undo. The fix that followed: every fixture carries an explicit protected-root default and an assertion refusing a live default. - 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 firstq48-armed-envrow 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. - The
q48-disarmed-env-liverootfalsifier (~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.pywith 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>withQ48_ROOTunset each moved the live held pin, andHELM_COLLECTOR=<that fixture> Q48_ROOT unset python3 q48/helm-selftest.pymoved it too (rc 1). All threeQ48_ROOT=<scratch>controls: no move. Written content measured by a 5 ms polling watcher: 14 B /784b9bde…/'q48-env-pin\n'. Artifactout/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 everyos.environ.get("X","<default>")byast, 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--deployleg. Checks 120 → 123 (+3 check("Q49", nothing else). Five green runs: rc 0,174 passed, 0 failed, 13,863 B stdout, digests13cf5cf7/d4666b9f/0abd28cd/5ed81c37/bbbd1901, live delta 0. Artifactout/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
--deploybranch runshcg.main()in-process with onlySNAPSHOT_PATHpatched, 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.pyis still7ed1bca8…and readsHELM_COLLECTOR0 times, so none of Q31–Q49 is in production. Thecpof collector4e45021d…+ gate085ddf4f…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, themanifest_matchpin stale since 2026-10-08, the Q13 publication call.
Ask
- Alexander: authorise the
cpof collector4e45021d…+ gate085ddf4f…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, sha256a64f26e740f61cd0e2f305f7f5e915cd269522b85e68c0c51e0709eeba4edd2bcheckins/2026-10-13-merlin.md— 36,515 B, sha256cdfed86a1a0be6886fa4ba1b905084295f6ac43d669fbb8dff15eb0991eefc36WORK-QUEUE.md— 823,541 B, sha256d0bf9080f9ef61570d30cb85a639b56adbf98bceb3ba30adf2b15e142677b8d2- 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.py085ddf4f42ae80ea…,-nodefault46ba830cd4b9e690…,-inproc-offd1481ca890fcd543…,-disarmed63d5409b3502a22e…) and the fixture (env-root-v1.py76b55b8ab0203c4f…) stay in the scratch tree, reproducible bypatch_gate_q49.pyandreproduce_q49.py.
—
Run 16 — Q50: the admission probe's environment
Did
Claimed and finished Q50 (Helm lane; the top unclaimed executable item, opened by the
pipe in replies/2026-10-13-to-merlin-34.md §Asks 1 with an explicit offer to take it —
"the gate is your file and I will not edit it, only propose"). Deliverable:
recoveries/helm-probe-spawn-env.md. Queue block appended to WORK-QUEUE.md. Scratch
tree /root/crucible/q50/; nothing deployed, no cp into /root/.helm/bin/,
decisions/ and inbox/ untouched, no another-party file edited, no secret printed.
The item's three parts, all measured. (1) The spawn's rebuilt environment: the
Q48 value-redirect + Q49 default-redirect, factored into one function
_q50_rebuild_env(_target) with two callers — the legs (original site, target the legs'
sandbox) and the admission probe (target its own tree, hoisted above the spawn because
the legs' sandbox does not exist there). (2) The blank cell (armed × named root): the
base gate with Q48_ROOT=/root/.helm moves the live held pin 120 B / 5b6d7071… → 14 B
/ 784b9bde… (guard-restored; the defect reproduced from this host, same payload digest
as my Q49 attribution record); the Q50 gate in the same cell holds delta 0 with the
value redirect firing (Q50 NOTE … 1 value redirect(s) … Q48_ROOT /root/.helm -> <probe tree>). (3) The detector: a per-window census of both live objects around the
spawn — a move is restored byte-exact in-gate, recorded, printed as a dated
Q50 DETECTOR note, and fails the named check. Exercised on the disarmed variant
(prevention cut): the pin moves inside the gate, is restored, and the foot prints
FAIL Q50 the admission probe's spawn moved neither live object (165/10 against the
armed rows' 166/9 — the counts isolate the check; the 9 fixture-subject failures are
pre-existing scratch-root checks, identical on the base gate).
The pair. Collector byte-identical to Q33–Q49's (4e45021d…, 29,954 B, measured by
sha256) — every change is in the gate. Gate a951dbe87d3394e0… (134,086 B / 2,552
lines / 125 check( registrations, 2 × check("Q50" in mutually exclusive branches)
on the Q49 gate 085ddf4f… (128,948 / 2,445 / 123, digest verified against the queue's
record); out/DIFF-vs-q49.txt = 388 lines = 5 hunks + 226 added − 119 removed, and
2,445 + 226 − 119 = 2,552 exactly. Disarmed discriminator 6b1c431f… — one line
different (the spawn's env= removed).
Green pair, five consecutive invocations (run_green_q50.py, subject
/root/crucible/q48/helm-collect.py, Q48_ROOT unset): rc 0 each, 175 passed / 0
failed, ALL GATES PASS, 13,929 B stdout, 13.82–15.11 s, digests b5391473 / dd0b0989
/ d8905c97 / 21e04379 / aea1ebb3, live delta 0 on all five. 175 = 174 + 1; no
pre-existing check moved. Regressions 11/11 (run_regressions_q50.py): n1–n5, noparse
(Q46 REFUSAL), dep-unparseable (repair 3), undecodable (Q47 REFUSAL), broken_dep (Q35
REFUSAL) rc 2; latin1 and bom green 175/0; live delta 0 on all 11.
The full matrix, all five cells (run_matrix_q50.py, subject env-root-v1.py, each
under the digest-verified guard q50_guard.py): base × named MOVED (above); armed ×
unset delta 0 (0 value + 1 default redirect); armed × named delta 0 (1 value redirect —
the blank cell); disarmed × unset delta 0 (the probe copy's own Q45 literal relocation
covers the default shape — the Q50 redirect is load-bearing only in the named cell);
disarmed × named delta 0 with the detector firing (in-gate restore). The other probe files count in the Q39 OK line is the tell: 1 on armed rows (the redirected write
lands inside the probe tree, counted by the containment census), 0 on dis-named (the
write went live, and only the window census sees it).
Live state, delta 0 across all 27 invocations (5 green + 5 matrix + 11 regressions +
guard reads): snapshot 14,039 B / aa842b0fd88037f8… / mtime_ns 1791479579475780303;
held pin 120 B / 5b6d70716153c3f4… / mtime_ns 1791478687099553071. ctime_ns moved
twice (the in-gate detector restores + the guard restore of the base row) — disclosed,
not hidden. Deployed bin untouched (ee0b36a8… / 7ed1bca8… / 12570786… / 73869998…,
grep -c HELM_COLLECTOR on the deployed gate = 0 — Q40's finding stands). Final tree:
0 sandboxes. Nothing of Q31–Q50 is in production.
Three defects of mine, logged first in the deliverable: the green runner's first
attempt used the deployed collector (ee0b36a8…, the pre-Q33 collector — Q40's finding)
and was refused by Q35; re-run with the Q33–Q48 subject. The matrix/regression extractors
matched lines starting with Q50 while the foot prints PASS Q50 …; the Q50 lines
were re-derived from the saved logs (figures_q50.py), no invocation re-run. The
disarmed variant still prints the Q50 NOTE although the env it names is not the one the
spawn receives — a scratch discriminator's known inaccuracy.
Blocked
Nothing in my lane. The named follow-on (record §7.2): hoist the run-wide held census
(_q44_held_before, taken after the spawn) above the probe so the per-run instrument
sees the window too. Same standing gates, all Alexander's: the §4 adoption cp —
now collector 4e45021d… + gate a951dbe8… (superseding 085ddf4f…, which the pipe
made conditional on this item) — Q9, Q13, Q19, Q42.
Next
- The pipe's verification of this run, when it lands. Everything re-runs from
/root/crucible/q50/:patch_gate_q50.py,run_green_q50.py,run_matrix_q50.py,run_regressions_q50.py,figures_q50.py. - The held-census hoist above (my named follow-on) and Merlin's Q45 §9 follow-on both stay open and unclaimed.
- Q7, measured: the Helm lane's executable set is empty again. No new item opened — the next Helm unit after the two follow-ons is a decision, not a task.
Ask
Alexander: nothing blocking. The standing items, consolidated: (a) rule on the §4
adoption cp — collector 4e45021d… + gate a951dbe8…, the pair that makes
python3 helm-selftest.py green with no environment and puts Q31–Q50's protection into
production; (b) the manifest_match pin decision (stale since 2026-10-08); (c) the
Q13 publication call (item 13's decided note still controls over its
proposed_action).
Run 16 landing evidence
Pushed as its own commit (git -c user.name=Merlin -c user.email=merlin@agents.invalid),
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 (served = disk = git cat-file -p <rev>:<path>). Table and digests land with the push; the check-in's own row
cites the revision it was written against, per the lane's no-self-hash convention.