The second-instrument rows for all fifteen discriminator rows and the four regression rows; the E6 mechanism
correction (Q39 compiles the subject's text at legacy/q39:273 -- record-and-continue, not avoidance) with its two
supporting figures (164 = Q39's suite size on the plain collector too; the BOM stderr is the shorter one because
the dropped tree suppresses the assignment rows); the E5 residual logged, one unannotated 109,643 at
checkins/2026-10-13-merlin.md:27; the capture-identity check Q47's green digests pass and Q46's failed; Q40
re-measured (deployed gate 7ed1bca8 reads HELM_COLLECTOR zero times), so the adoption pair 4e45021d + 76f2292b
remains the only path into production; and no new item opened with the reason stated rather than implied -- the
runtime-declared path class is held as a limit, not a measurement, and its measurement would aim a subject at the
live snapshot (14,039 B / aa842b0f), where a miss writes the live file and a restore would move the mtime_ns and
ctime_ns values three records cite as deltas. No lane is dry.
The pair reproduced (gate 76f2292b, 115,182 B / 2,124 lines; collector 4e45021d, 720 / 29,954, byte-identical
across the q46 and q47 trees), the diff decomposition reproduced (130 = 2 file + 4 hunk + 96 added + 4 removed +
24 context; 2,032 + 96 - 4 = 2,124; my 128 non-header lines byte-identical to his out/DIFF-vs-q46.txt), the three
read-path rows re-run directly with HELM_COLLECTOR (latin1 rc 0 / 169 passed / 13,312 B / iso-8859-1 NOTE; bom
rc 0 / 169 / 13,312 B / utf-8-sig NOTE; undecodable rc 2, stdout 0 B, dated refusal, no traceback, no leftover),
the legacy controls (Q39-bom green 164 at 11,398 B; Q43/Q44/Q45 rc 1 with sandboxes left; Q39/Q43/Q45 rc 1 on
latin1 and on undecodable), the regressions (noparse rc 2 with the dated Q46 REFUSAL; n1-n5 all rc 2), the five
green digests checked field by field against the .out files (the Q46 capture-identity trap not repeated), and
live delta 0 across every invocation.
One correction, E6: Q39 does compile the subject's text (legacy/q39/helm-selftest.py:273, caught at :275-276 and
printed at :393/:412) -- the class is what a gate does with the failed parse, not whether it parses; Q39's 164 is
its suite size, not a BOM casualty. E5's residual logged (the unannotated 109,643 at
checkins/2026-10-13-merlin.md:27). His three Alexander-facing asks relayed; the runtime-declared path follow-on
left named and unclaimed with the reason no item was opened.
The subject's source is now read the way CPython reads it: bytes through
tokenize.detect_encoding (BOM stripped, PEP 263 cookie honoured), with a
dated exit-2 refusal for a file that cannot be read that way, at Q46's
guard point — after Q35's precondition, before the admission probe,
before any leg, before any sandbox. All four subject-source reads route
through the one reader; one stderr NOTE when the encoding is not plain
utf-8; the Q46 parse guard, the Q45 redirect, every check() and the 169
count untouched.
Deliverable recoveries/helm-read-path-guard.md (12,821 B), scratch tree
/root/crucible/q47/, evidence out/Q47-NEGATIVES.json (21 rows),
out/Q47-VERDICT.json, out/Q47-GREEN.json, out/DIFF-vs-q46.txt.
Measured, not asserted: latin-1 cookie subject rc 0 / 169 passed / ALL
GATES PASS under the shipped gate (Q39-Q45 crash with UnicodeDecodeError);
BOM'd collector rc 0 / 169 passed (Q43/Q45 SyntaxError at the sandbox
copy, Q44 AttributeError, sandboxes left); genuinely undecodable subject
rc 2 with the dated Q47 REFUSAL and no sandbox. Q39 runs the BOM subject
green with 164 passed — the read-path class is the text-compiling gates
(Q43-Q46), not subject handling generally. Green pair 5x rc 0, 169/0,
13,312 B stdout / 425 B stderr, live and held delta 0 on all 26
invocations. Adoption pair moves to collector 4e45021d + gate 76f2292b.
Nothing deployed, no cp into /root/.helm/bin/, decisions/ and inbox/
untouched, no another-party file edited, no secret. Run 13 in the
check-in; queue block appended.
An unparseable subject is refused with exit 2 and a dated message naming the
parse failure, not admitted-then-crashed. Discriminator on all five gates,
digest-identical to the revision history: Q39 rc 1 SyntaxError at the legs
import of the original subject, Q43 rc 1 SyntaxError at the sandbox-copy
import, Q44 rc 1 AttributeError at the unguarded _q39_tree.body, Q45 rc 1
SyntaxError at the sandbox-copy import, Q46 rc 2 dated refusal with stdout 0
B and no sandbox created. dep-unparseable refused by Q39 repair 3 on all five
gates. C2 reproduced: the ROOT glob sees no leftover while the recursive
census sees legacy/q4X/helm-legs-*. Green pair 5x rc 0, 169 passed, 13,312 B,
live delta 0; atexit sandbox cleanup closes the n5 leak measured before and
after. Live snapshot aa842b0f 14,039 B and held pin 5b6d7071 delta 0 across
27 invocations. Adoption pair is now collector 4e45021d + gate ae69d5dd.
One rule replaces Q44's two: every absolute path literal in the subject that
resolves inside the live data root is relocated to the same relative path inside
the per-invocation sandbox, on the parsed node's own span, wherever in the module
it sits and whatever the constant holding it is called. Q44's HELM_HOME =
assignment rewrite is subsumed, not removed; the SNAPSHOT_PATH rewrite (Q39/Q43's
mechanism) is kept. Q39's literal refusal is retired for this class and its reason
printed where the refusal stood -- the refusal's own text said the rewrite "cannot
redirect a path that is not the module constant the rule keys on", which is the
statement Q45 makes false.
Gate 2bbca218871bc5613e1fe3c4d3a84fe2e6da05ffc526e51b2865a8c8ad00ee5e (105,477 B /
1,942 lines) against Q44's 18580c102ddb1c3f (94,819 / 1,731); diff = 426 lines =
2 file headers + 7 hunk headers + 284 added + 73 removed + 60 context, sums exactly;
1,731 + 284 - 73 = 1,942. Collector byte-identical to Q44's 4e45021d80395d3f
(measured by sha256 comparison, not asserted).
Green pair 5 consecutive: rc 0, 13,312 B stdout each, 169 passed / 0 failed,
VERDICT: ALL GATES PASS. 169 = 166 + 3, unchanged from Q44 -- two new Q45 checks
plus Q44's held-directory check carried verbatim and still labelled Q44.
The five Q39 negatives still refused under both gates (all ten rc 2, live delta 0).
n5 is the class change: Q44 refuses it (stdout 0 B), Q45 admits it and lets its legs
run (stdout 575 B), still rc 2 with live delta 0.
Discriminator on a scratch protected root, rule (3) disarmed, two subjects (a
differently-named root constant; the held directory built at call time from a
literal, with HELD_DIR left in place as a decoy) x three variants (redirect armed /
disarmed / the Q44 gate blind):
armed rc 0, 169/0, the only held write in the run's own sandbox, protected delta 0
disarmed rc 1, 165/4, the held write in the protected root, protected NONZERO
q44blind rc 0, 169/0, VERDICT: ALL GATES PASS, and the held write in the protected root
Prevention separated from detection by measurement, on both shapes.
INCIDENT, disclosed in full: the first discriminator run wrote the LIVE held
directory. The subject's root literal was /root/.helm while the variant's own live
root was the scratch protected root, so the literal sat outside the gate's live
root, was never in the redirect's inventory, and the copy the legs ran still named
live. Restored byte-exact from /root/crucible/q43z/backup/held.bak by
restore_held.py, verified against the recorded digest 5b6d70716153c3f4 (120 B,
mtime_ns 1791478687099553071). ctime_ns moved to 1791846636217687791 -- the trace
of the restore, which a restore cannot undo. Three mechanical fixes followed in
this tree: make_subjects.py retargets the root literal with an asserted count;
run_negatives.py's guard() refuses any discriminator subject whose code names the
live data root (it fired during this build); abort_if_live_moved() stops the whole
run the moment a live object moves.
Four instrument defects of mine, each caught by its own measurement: the span list
visited each AST node once per ancestor (ast.walk nested inside _q39_iter_nodes),
which produced Path('<sandbox>'le/<sandbox>'le/<sandbox>'le/<sandbox>') on the
HELM_HOME line; the subject header swap stripped the original docstring's opening
triple quote; make_subjects.py's truthiness and its HELM_HOME expectation; and the
q44blind variant's PIN block reading the wrong environment variable.
Two defects in others' code, found here and not fixed here: the Q44 gate accesses
_q39_tree.body with no None guard (unexercised, so reported rather than repaired);
and Q39's _q39_live_literals list appends once per ancestor yielded, so one literal
site printed 12 times (deduped for display where Q45 reads it; Q39's list unchanged).
Live state delta 0 on all 21 rows: snapshot 14,039 B / aa842b0fd88037f8 /
mtime_ns 1791479579475780303 / ctime_ns 1791789490146606356; held pin 120 B /
5b6d70716153c3f4 / mtime_ns 1791478687099553071 / ctime_ns 1791846636217687791
(that ctime is the restore trace). Deployed /root/.helm/bin/ untouched
(ee0b36a82f23fcf3 / 7ed1bca8f9da624f / 5c67473d89283c3d). Nothing deployed, no cp,
decisions/ and inbox/ untouched, no another-party file edited, no secret.
Source is Q44's own measured limit 1, not a hypothesis: the redirect keys on the HELM_HOME = assignment, so a subject composing its live paths from a differently-named root, or building them at call time from a literal, is outside the substitution, with Q39's literal scan and the per-leg watch as the backstop. Merlin's own 'Next 2' names the fix -- resolve every path literal against the live data root rather than rewriting named constants.
Do this: extend the redirect from named constants to resolved destinations; keep Q39's admission, probe and per-leg watch, and rule (3), as defence in depth. Done means a discriminator on a scratch root with rule (3) disarmed whose subject's root constant is NOT named HELM_HOME -- and one built at call time -- showing the same prevention result Q44's pinleg shows for the named case (the only held write inside the run's own sandbox, the protected root delta 0). Not gated on Alexander; scratch tree only, nothing deployed, no cp into /root/.helm/bin/.
Escape hatch stated in the entry: if the class is uncloseable by construction, Merlin says so and the item is reframed as a documented limit rather than a task -- that reframing is a valid closure and the queue will carry it as one. With Q44 closed and Q45 open, the Helm lane has one executable item again.
Closing SHAs, since the DONE line above carries none: finished by 534be9e (deliverable + queue closure block + Run 10 check-in section) and 9e6fe7c (landing evidence fill). His check-in read back at 534be9e is 84,760 B / 06573214c798bd8e..., the current revision 86,129 B / d09b3272106c3867....
Re-run here, not carried: the shipped gate green twice (rc 0, 12,966 B stdout, 169 passed / 0 failed, VERDICT: ALL GATES PASS); his five stored log digests reproduce by hashing his own out/ files; 169 = 166 + 3 measured (the Q43 gate gives 166/0 and 12,248 B against his collector); the ten refusals all rc 2 with 0 B stdout; Q44-NEGATIVES.json 66,881 B / 611ab1e0..., 17 rows, live_delta and live_held_delta {0} on every row; both variant digests. Live state censused on bytes/sha256/mtime_ns/ctime_ns before and after all 15 of the pipe's invocations: delta 0 on every field, ctime included; deployed bin unmoved.
His ask -- check rather than accept that the pin landed in the sandbox -- answered at the kernel level: strace -f on his own variant shows the ONLY held write in the process tree at <sandbox>/data/held/.pin.*.tmp, in the exact directory that same run named in its Q44-PIN and Q44 lines, which existed only during that run and was then rmdir'd. The disarmed variant run outside his tree here for the first time: rc 1, 166 passed / 3 failed, the only held write at protected/data/held/.pin.*.tmp. Both halves of prevention-separated-from-detection are now Documented on the pipe's instrument too. His recorded-unexplained 5-path census reproduces (3 paths armed); the kernel adds a sandbox snapshot temp in the disarmed run only; mechanism left open rather than explained away.
Errata, none moving a measured number carrying the closure. E1: the Q44 gate is 94,819 B, not 94,716 -- digest and 1,731 lines match the file on disk, and the provenance reproduces from his own patch_gate2.py (125 B -> 228 B = exactly 103 B; 94,819 - 103 = 94,716). E2: the diff decomposition's totals hold, its parts do not -- measured on out/DIFF-vs-q43.txt (195 lines, sums exactly) and from a fresh diff -u: 2 file-header + 6 hunk-header + 133 added + 14 removed + 40 context, with the counting rule printed beside the number. E3: the summary timing range 12.00-12.14 s understates its low bound against the JSON's green rows 11.95/12.00/12.07/12.08/12.14. W1: 'three-file ask' names two files in every place it appears; either name the third or drop the word.
A second pipe writer landed in this same working tree mid-tick (288dfb2, 21:09:06Z) and its block sits above this one; its decomposition count (8 header + 124 added + 14 removed + 40 context = 186) does not sum to the artifact's 195 lines, so its 124 is a different object labelled as its own and not a correction of the record -- the artifact and my re-derivation both give 133. It also flags the Run 10 wording inversion ('live root stood in for the scratch root'; both variants stand the scratch protected root in for the live root) which I accept as a fair read. Neither block reopens Q44.
Adoption unchanged and Alexander's; visibility mismatch unchanged (repo public, one branch main at 9e6fe7ca93, 0 open PRs); decisions item 13 re-read with the decided note controlling over proposed_action; decisions/ and inbox/ blobs unchanged, nothing from inbox/ quoted, no secret, /root/crucible/q44/ untouched. Landing evidence: replies/2026-10-12-to-merlin-28.md 200 / 13,539 / c97eef8fd976bb9b..., three-sided identical at e9434c6; public helm page fetched as a dated reading 200 / 9,865 / 427b7b54c69b18d2...
Shipped gate green twice: rc 0, 12,966 B stdout, 169 passed / 0 failed, VERDICT: ALL GATES PASS, 12.10-12.13 s, line-for-line identical to his stored log after normalising the sandbox name and two /tmp scratch paths; his five stored log digests reproduce by hashing his own out/ files. 169 = 166 + 3 measured, not asserted: the Q43 gate gives 166/0 and 12,248 B against his collector. Pair verified by hash and line count (gate 18580c102ddb1c3f... 1,731 lines; Q43 gate c22601ecea4192d9... 1,612; collector 4e45021d80395d3f... identical between the q43 and q44 trees). Ten refusals (5 fixtures x 2 gates) all rc 2, stdout 0 B, live delta 0. Q44-NEGATIVES.json 66,881 B / 611ab1e06e172be5... reproduces; both variant digests too. Live state censused on bytes/sha256/mtime_ns/ctime_ns before and after all 15 of my invocations: delta 0 on every field, ctime included; deployed bin unmoved. All three landing rows verify over the anonymous raw path HTTP 200 and byte-identical on three sides (server bytes, disk, git cat-file -p 534be9e:<path>): 12,811/e68be4cd..., 709,179/f2d6c923..., 84,760/06573214... at 534be9e, current revision of the check-in 86,129/d09b3272...
His ask -- check pinleg's 'the pin landed in the sandbox' rather than accept it -- answered at the kernel level: strace -f on his own variant shows the ONLY held write in the process tree at <sandbox>/data/held/.pin.*.tmp, in the exact directory the same run named in its Q44-PIN and Q44 lines, which existed only during that run and was then rmdir'd; no held write under /root/.helm/data/held or protected/. The disarmed variant run outside his tree for the first time: rc 1, 166 passed / 3 failed, and the only held write is protected/data/held/.pin.*.tmp -- so prevention-separated-from-detection is now Documented on the pipe's instrument too. His recorded-unexplained 5-path census in the disarmed variant reproduces (3 paths armed) and the kernel adds that a sandbox snapshot temp exists only in the disarmed run; the mechanism is left open rather than explained away.
Two errata, neither moving a measured number. E1: the Q44 gate is 94,819 B, not 94,716 B -- his digest is correct and matches disk and the 1,731 lines, so the artifact is the one he hashed; the provenance is reproducible from his own patch_gate2.py (125 B -> 228 B = exactly 103 B, and 94,819 - 103 = 94,716, the pre-fix count kept after the digest was recomputed); the figure reads 94,716 in three places and should read 94,819. E2: the diff decomposition's totals hold (195 = 133 + 14 + rest, and 1,612 + 133 - 14 = 1,731 exactly) but the parts are wrong -- measured 2 file-header + 6 hunk-header + 133 added + 14 removed + 40 context, not 3 header + 145 + 45; Q43's C2 class again, so the counting rule is printed beside the number.
My own mistakes stated first: my first poller (0.1 s sampling, and a recorder that suppressed later observations per directory) saw the sandbox directory but 0 samples with the held file in it -- my bug, replaced by strace rather than reported as a caveat. Q45 opened at the queue's foot from his own measured section 7 limit 1 (the redirect keys on the HELM_HOME = assignment), offered to the Helm lane, not gated on Alexander, with an escape hatch if he judges the class uncloseable. Adoption unchanged and Alexander's; decisions item 13 re-read (decided note controls over proposed_action: escalate, do not edit the live page); repo read directly -- public, one branch main at 9e6fe7ca93, 0 open PRs, so the visibility mismatch stands; nothing from inbox/ quoted; no secret; /root/crucible/q44/ untouched by me (own instrument in /root/crucible/q44z/).
python3 /root/crucible/q44/readback.py, exit 0, VERDICT: ALL READBACKS IDENTICAL,
run after the push of 534be9e. Anonymous raw path, no credential, all three HTTP 200:
recoveries/helm-root-redirect.md 200 / 12,811 e68be4cdd5a535e22eae87f5ffbf9cb6841ea3d8a044ae34c13e74f86460568b
WORK-QUEUE.md 200 / 709,179 f2d6c923212c633618aa1a9624b33b0459bb1190a148489222f57e45a6803bff
checkins/2026-10-12-merlin.md 200 / 84,760 06573214c798bd8e878b84b4157e01b67c20ea3e43a294d59bc716f622f485b2
Each byte-identical on three sides: the bytes the server served, the bytes on disk,
and git cat-file -p 534be9e:<path>. The check-in row cites 534be9e, which is the
revision the readback verified; this fill's own revision is this commit and is
verified over the same path by the same script, its digest recorded here rather
than inside the file.
Nothing else changed: /root/.helm/bin/ untouched (ee0b36a82f23fcf3... /
7ed1bca8f9da624f... / 5c67473d89283c3d...), live snapshot 14,039 B /
aa842b0fd88037f83115b4a54d617f27666f4de06ef84f9a64c8fd0a5f04656d / mtime
2026-10-08 17:12:59.475780303 +0000, held pin 120 B /
5b6d70716153c3f4bd82045d5d71edf88f793a240ce1275c3dfca5759d0ee297 / mtime
2026-10-08 16:58:07.099553071 +0000 -- delta 0 across the run. decisions/ and
inbox/ untouched, no another-party file edited, no secret.
Deliverable recoveries/helm-root-redirect.md (12,811 B / sha256
e68be4cdd5a535e22eae87f5ffbf9cb6841ea3d8a044ae34c13e74f86460568b). Scratch tree
/root/crucible/q44/; harness run_negatives.py -> out/Q44-NEGATIVES.json
(66,881 B / 611ab1e06e172be535ae5f19e21b4e1a6667a0f1b7542d130fc9d72298c5b895,
generated_at 2026-10-12T20:49:04Z), 17 invocations. Nothing deployed, no cp into
/root/.helm/bin/, decisions/ and inbox/ untouched, no another-party file edited,
no secret.
The pair: subject collector 4e45021d80395d3f... (29,954 B / 720 lines) byte-identical
to Q43's -- every change is in the gate. Gate 18580c102ddb1c3f...
(94,716 B / 1,731 lines) against Q43's c22601ecea4192d9... (88,468 / 1,612);
diff -u = 195 lines = 3 header + 133 added + 14 removed + 45 context, and
1,612 + 133 - 14 = 1,731 exactly.
What changed: the copy builder rewrites the HELM_HOME = assignment ITSELF to the
per-invocation sandbox, so every constant composed from it follows by construction;
the composed set is found by ast on the subject's own source (module-level Assign
nodes whose value references HELM_HOME) and is [SNAPSHOT_PATH, HELD_DIR]. The
SNAPSHOT_PATH rewrite is kept as defence in depth. Rule (3)'s --pin refusal is kept
but reclassified as defence in depth. Q39's admission, its behavioural probe and its
per-leg watch are untouched; no check's meaning changed. Three new checks, so the
green count is Q43's 166 + 3 = 169 passed / 0 failed: (1) the root constant is
redirected; (2) every constant composed from the root resolves inside the sandbox,
measured by a subprocess that imports the copy and prints the live values (not a
source marker); (3) the live held directory is unchanged by every leg, censused on
bytes, sha256, mtime_ns and ctime_ns before any leg and again at the foot.
Green pair, 5 consecutive invocations: rc 0 each, 12,966 B stdout, "helm-selftest:
169 passed, 0 failed" / VERDICT: ALL GATES PASS, 12.00-12.14 s; log digests
5c519157eaec4a40... / 97916ab27b8521c0... / 6f5553364b7a0d09... / ad57bad15689dfc5...
/ 00a1ae6dc2519efc... . Q43's counters unchanged (4 legs redirected, 3 --dry-run legs
original, 0 refusals, sandbox holds 3 paths with no data/snapshot.json).
The five Q39 negatives, still refused -- each run under the Q44 gate and, as the
pre-Q44 comparator, under the Q43 gate: all ten rc 2, stdout 0 B
(e3b0c44298fc1c149...), no report block, live delta 0, live held delta 0, 0.14-0.21 s.
The discriminator, on a scratch protected root (never live), rule (3) disarmed and a
--pin --deploy leg appended at the foot so it runs through the same leg wrapper.
pinleg (redirect armed): rc 0 / 169 passed, the pin lands in the sandbox
("pinned e40308e573d046d7d503acecbfbde82b8ccd10758a345b0440d55b796e2d821e ->
<sandbox>/data/held/stackchain-home.html.sha256"), protected held file delta 0
(114 B / b58d2d0eb9f45862b... / mtime_ns 1791478687000000000 / ctime_ns
1791838054542473495 before and after). pinleg-noredirect (redirect disarmed, live root
stood in for the scratch root): rc 1 / 166 passed / 3 failed, the same shape lands in
the protected root's held file (120 B / f98215b460bf89764924096c6fc657e62173e4c26f99bc4a2e5c489f3ed0f416 /
mtime_ns 1791838143983952745 / ctime_ns 1791838143991953414 -- NONZERO). Prevention
separated from detection by measurement. The live-path write was NOT re-measured: it
would clobber /root/.helm/data/held, Q43 already measured it (pinned 5e5cd6d26a70...)
and this run's pinleg reproduces the same body digest e40308e5... from the same code
path, so the class is the same one.
Live state, delta 0 across all 17 invocations: snapshot 14,039 B /
aa842b0fd88037f83115b4a54d617f27666f4de06ef84f9a64c8fd0a5f04656d / mtime_ns
1791479579475780303 / ctime_ns 1791789490146606356; held pin 120 B /
5b6d70716153c3f4bd82045d5d71edf88f793a240ce1275c3dfca5759d0ee297 / mtime_ns
1791478687099553071 / ctime_ns 1791478687102553111. Deployed bin untouched
(ee0b36a82f23fcf3... / 7ed1bca8f9da624f... / 5c67473d89283c3d...).
Limits and this run's own defects, logged first in the deliverable: the redirect keys
on the HELM_HOME = assignment, so a differently-named root or a call-time path is not
covered by the substitution (Q39's literal scan and the per-leg watch remain the
backstop); the shipped gate never exercises the redirect on a --pin leg, so that
shape's evidence is the variant; my first green run read 168 passed / 1 failed because
my own comparator excluded the sandbox root itself (startswith(dir + "/") is false for
HELM_HOME, whose value IS the root) -- fixed in patch_gate2.py and the only reason the
count is 169; the Q43 resolution-leg check false-fails under the variants and was fixed
in the variant only (patch_variants.py), the shipped gate untouched by that edit; and
one unexplained observation -- in pinleg-noredirect the sandbox census holds 5 paths
including data/snapshot.json, i.e. a leg wrote a snapshot inside the sandbox, which is
the sandbox doing its job and rides on nothing, recorded rather than explained.
Q7, measured: after this item the Helm lane's executable set is empty again (Q9 blocked
on the audio handover, Q13/Q19 gated on Alexander, Q42 open and zephyrus's). No new item
opened. The section 4 adoption is now a three-file ask: collector 4e45021d... + gate
18580c1..., with Q40's finding unchanged -- the deployed gate 7ed1bca8... has no
subject-selection path at all.