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.
15 KiB
Q49 — The runtime-resolved root's live write path: named, reproduced, and closed (with one residual)
Run 15, Merlin, Helm lane. 2026-10-13, this host. Scratch tree /root/crucible/q49/.
Every figure below was measured on this host in this run. A claim I could not measure is
marked Testimony only or unattributed — nothing here is asserted from the record.
Verdict: Q49 (a) is discharged by measurement — the invocation that moved the live held pin
during Q48 is named with its argv and reproduced under a guard, twice, with the negative
control. Q49 (b) is closed for the class the item describes (the --dry-run original-subject
leg) by the DEFAULT redirect, green 5/5 at 174 passed / 0 failed with no pre-existing check
moved. Two things this run found that the item did not predict: the G8 --deploy leg runs
hcg.main() IN-PROCESS and was entirely unguarded (it wrote the live snapshot, 14,039 B →
137 B, reproduced and restored), and in the fully disarmed configuration the live pin still
moves with no spawn the traced instrument can attribute — that residual is named, not
closed.
0. My own defects, logged first
- The rebind NOTE counts the census, not the rebind. The Q49 NOTE line reports
2 path constant(s) reboundin theC-inproc-offandD-disarmedrows, where the rebind is disarmed. The counter is filled before thesetattris replaced bypass, so the note overstates what happened. No check depends on the count; the note is wrong and is recorded here rather than silently fixed in a re-run. (Affects rows C and D of §4.) - A restore is not silent on ctime.
q49_guard.restore()re-stamps mtime withos.utime, andutimemoves ctime. So after any restore, ctime is the restore's own stamp and cannot evidence the write. Every restore record below therefore carriesmtime_restamped_to(recoverable) andctime_now_ns(the restore's, not the writer's). - The first witness JSON was overwritten.
out/Q49-WITNESS.jsonfirst held a 4-row run whoseq49-armed-liverootrow carried the M2 measurement quoted in §4; the 2×2 re-run replaced it. The M2 evidence survives in that row's captured stdout (out/q49-armed-liveroot.out), in the restore records printed at the time, and in §4 below — not in the current JSON. - The item's stated closure could not work as written. Q49 (b) offers "the
original-subject legs inheriting the rebuilt environment (one line in
_q39_run)". That line already exists in the Q48 gate —helm-selftest.py:1117,_kw["env"] = _q48_env— and it is a no-op for the incident's shape, because with the variable unset there is nothing to inherit. Measured, not argued: the shipped Q48 gate's own NOTE reads0 redirected from the live data root into the sandbox: nonewhile the pin moves (§1, row R2). What closes the class is redirecting the subject's default literal, which is what §2 implements.
1. (a) The attribution: the invocation, named and reproduced
The v1 fixture is rebuilt from q48/env-root.py by one mechanical change back: the two
default literals /root/crucible/q48/protected → /root/.helm (asserted count 2).
env-root-v1.py = 5,634 B / sha256 76b55b8ab0203c4f…. v7's own assertion says the v1
incident was "carries the live default"; the v1 driver's first armed row is recorded in
q48/out/old/q48-armed-env.err firing the redirect from /root/.helm with Q48_ROOT
unset, which is only possible when the subject's own resolution starts there.
Guard: q49_guard.py takes a digest-verified backup of both live objects before any run,
compares (bytes, sha256, mtime_ns, ctime_ns), and refuses to restore unless the backup's
digest matches the recorded value.
Live state at session start (unchanged at session end):
/root/.helm/data/snapshot.json 14,039 B / aa842b0fd88037f8…;
/root/.helm/data/held/stackchain-home.html.sha256 120 B / 5b6d70716153c3f4….
Runner: reproduce_q49.py → out/Q49-ATTRIBUTION.json (6,495 B / 89a827295a3d148d…).
| # | Invocation (exact) | rc | Live held pin | Restored |
|---|---|---|---|---|
| R1a | python3 env-root-v1.py --dry-run --skip-tests, Q48_ROOT unset |
0 | MOVED 120 → 14 B | yes |
| R1b | python3 env-root-v1.py --skip-tests, Q48_ROOT unset |
2 | MOVED | yes |
| R1c | python3 env-root-v1.py --deploy --skip-tests --out <scratch>, Q48_ROOT unset |
0 | MOVED | yes |
| R2 | HELM_COLLECTOR=env-root-v1.py python3 /root/crucible/q48/helm-selftest.py, Q48_ROOT unset |
1 | MOVED | yes |
| N1a | python3 env-root-v1.py --dry-run --skip-tests, Q48_ROOT=<scratch> |
0 | no | — |
| N1b | python3 env-root-v1.py --skip-tests, Q48_ROOT=<scratch> |
2 | no | — |
| N1c | HELM_COLLECTOR=env-root-v1.py Q48_ROOT=<scratch> python3 helm-selftest.py |
1 | no | — |
R1a is the fixture-verification shape (make_fixtures_q48.py v1's verify_interpreter(),
~05:5xZ): the held pin is written before the --dry-run early return, so a "writes
nothing" flag writes it anyway. Its own stdout says so — "root": "/root/.helm",
"held_dir": "/root/.helm/data/held" — with the banner
q48-env: held write -> /root/.helm/data/held.
R2 is the driver's first armed row, and it is the one that settles the item's question.
Q48_ROOT unset, the shipped Q48 gate prints
Q48 NOTE: … 1 variable(s) the subject reads (Q48_ROOT), **0 redirected** … : none, and the
pin moves. Which leg: the Q49 per-leg census in §4 row B names it verbatim —
python3 …/env-root-v1.py --dry-run --skip-tests, the G8 leg whose own source comment reads
"--dry-run at the live destination is NOT gated: it writes nothing"
(helm-selftest.py:2274–2278). That premise is the deployed collector's contract, asserted
for every subject the gate admits.
The written bytes: 14 B, sha256 784b9bded20e9e22…, content 'q48-env-pin\n' —
recorded by a 5 ms polling watcher (watch_pin_q49.py) that copies the pin on every
transition, so the content is measured rather than inferred.
So Q48's §6.2 attribution is upheld as a measurement, not withdrawn. The item allowed
either outcome; this is the first: the gate's own --dry-run original-subject leg moved the
live held pin, with the argv above, and the pipe's negative (live root named → redirect
fires → delta 0) is reproduced exactly by N1c.
2. (b) The closure: three changes, three checks
patch_gate_q49.py → helm-selftest-q49.py, 128,948 B / 2,445 lines / sha256
085ddf4f42ae80ea… against Q48's helm-selftest.py 122,186 B / 2,287 lines
(a590170f…). The shipped Q48 gate is untouched; nothing was copied into /root/.helm/bin/.
- The DEFAULT redirect (prevention).
_q48_env_default_sites()inventories everyos.environ.get("X", "<default>")pair byast. A name whose ambient value is absent and whose default literal resolves inside the live data root is redirected to the per-invocation sandbox. Measured on the v1 fixture:Q49 NOTE: … 1 default literal(s) resolve inside the live data root and were redirected … Q48_ROOT default /root/.helm -> /root/crucible/q49/helm-legs-0idqju7v. - The per-leg census of both live objects (detection).
_q39_live_objs()reads(bytes, sha256, mtime_ns, ctime_ns)of the snapshot and the pin on both sides of every spawn and appends the leg that moved them. - The in-process
--deployleg (prevention). §4.
Check census: check(" registrations go 120 → 123; the per-prefix diff is exactly
+3 check("Q49" and nothing else — no pre-existing check moved, added or reworded.
Green pair, five consecutive invocations (run_green_q49.py, subject = the Q33–Q48
collector 4e45021d…, Q48_ROOT unset):
| run | rc | verdict | stdout | sha256 (16) | s |
|---|---|---|---|---|---|
| 1 | 0 | helm-selftest: 174 passed, 0 failed |
13,863 B | 13cf5cf75cd58b62 |
12.13 |
| 2 | 0 | same | 13,863 B | d4666b9fd3d12676 |
12.64 |
| 3 | 0 | same | 13,863 B | 0abd28cd50fcc406 |
12.70 |
| 4 | 0 | same | 13,863 B | 5ed81c37caf679c4 |
12.57 |
| 5 | 0 | same | 13,863 B | bbbd190164e3cbe6 |
12.39 |
174 = 171 + 3, matching the check census exactly. live_delta 0 on both objects across
the set. Artifact: out/Q49-GREEN.json (5,169 B / bbbc1c84ea0a3f0a…). The control that
makes this meaningful: the real collector reads no environment variable, so both Q49
mechanisms are inert for it (0 env names, 0 default redirects, 0 per-leg moves) — the green
pair proves the additions did not move a result, not merely that the suite passes.
3. The 2×2 discriminator: prevention separated from detection
Same instrument that opened the class (the gate driven with HELM_COLLECTOR at a
runtime-resolving subject), four rows. Predictions were written before the run
(run_witness_q49.py, PREDICT). Artifact: out/Q49-WITNESS.json
(13,785 B / 877138d09bbdac3e…).
| row | gate | Q48_ROOT |
predicted | measured |
|---|---|---|---|---|
| A | helm-selftest-q49.py |
unset | delta 0 | delta 0 (no restore) |
| B | -nodefault |
unset | pin moves (M1) | pin moved, 120 → 14 B, restored |
| C | -inproc-off |
/root/.helm |
both move (M2) | pin moved, restored |
| D | -disarmed |
/root/.helm |
both move | pin moved, restored |
Every row is rc 1 with a named failing set, and the expected red is stated in advance: the Q43 check "the subject's own default destination resolves to the live snapshot the gate protects" cannot pass for a subject whose root is redirected into the sandbox — it is a live-identity check, not a redirect check. The witness is the live objects' location, not the check count.
A vs B is the closure of the class the item describes, by measurement. Identical
configuration, one mechanism switched: armed → live_delta 0 on both objects; disarmed → the
pin moves and the per-leg census names the leg:
"leg": "…python3 …/env-root-v1.py --dry-run --skip-tests". Prevention and detection are
therefore separate: the census does not prevent, the redirect does not report, and each is
measured with the other switched off.
Live state after the whole witness run: snapshot 14,039 B / aa842b0fd880…, pin
120 B / 5b6d70716153… — byte-identical to session start.
4. A defect the item did not name: the in-process --deploy leg
G8's --deploy branch (helm-selftest.py:2280–2294) sets
sys.argv = ["helm-collect.py", "--deploy", "--skip-tests"] and calls hcg.main() inside
the gate's own process, having patched only hcg.SNAPSHOT_PATH.
Measured 2026-10-13 with HELM_COLLECTOR=<a live-default fixture> and
Q48_ROOT=/root/.helm, against the shipped Q48 gate: the live snapshot went
14,039 B → 137 B (sha256 aec681402a3d03cb…, the fixture's own payload) and the live held
pin 120 B → 14 B, both restored byte-exact by the digest-verified guard. The Q44 check
caught the pin (out/q49-armed-liveroot.out: "the held directory changed: before … 120 /
5b6d7071…"); Q39's watch reported "14 legs watched … delta 0 on each" because no
subprocess was spawned; and the per-leg census is silent for the same reason.
A subject that resolves its root at import time ignores the SNAPSHOT_PATH patch, so the
leg wrote both live objects from inside the gate. The Q49 closure runs that leg with the
rebuilt environment and rebinds every path-valued module constant of the imported subject
that resolves inside the live data root into the sandbox, and censuses both live objects
around the call. Measured on row A: 1 env name(s) rebuilt and 2 path constant(s) rebound … live objects it moved: none, and the new foot check passes. (The count in that note is the
census of candidates — see §0.1.)
5. The residual, named and not closed
In the fully disarmed configuration (row D: default redirect off, in-process rebind off,
Q48_ROOT=/root/.helm) the live pin moves — reproduced three times — and the instrument
cannot attribute it:
- The traced twin (
env-root-v1-trace.py, prints argv/pid/ppid/parent cmdline before its held write) records exactly one fixturemain()per run, and its banner readsq48-env: held write -> /root/crucible/q49/helm-legs-chn0zt3b/data/held— the sandbox. grep -a -o "held write -> [^\\\"]*"over the run's full stdout and stderr returns one occurrence, that sandbox one. No spawn names the live path.- The Q49 per-leg census and the in-process census both report no move.
- A 5 ms polling watcher still records the transition: 120 B /
5b6d7071…→ 14 B /784b9bde…, content'q48-env-pin\n', at t+280 ms, and the guard restores byte-exact.
So the write is real, its content is the env fixture's own pin string, and its mechanism is
unattributed. I will not guess at it. It is handed on as the next Helm unit: either a
writer outside the subject's main() (a module-scope path in the fixture family, or a gate
leg that writes the imported module's HELD_DIR directly), or a hole in the census window
itself. Everything else in this run is measured.
6. Limits, and what is owed to Alexander
- Nothing deployed.
/root/.helm/bin/helm-selftest.pyis still7ed1bca8…and still readsHELM_COLLECTOR0 times, so none of Q31–Q49 is in production. The adoption pair is now collector4e45021d…+ gate085ddf4f…(Q48'sa590170f…plus §2), and thecpremains Alexander's — §4 puts deploy outside a wizard's initiative and no approval artifact exists in this repo or indecisions/. - Live objects, whole session: snapshot
aa842b0f…/ 14,039 B and pin5b6d7071…/ 120 B, byte-identical at start and end. Six live writes occurred under guard (R1a/R1b/R1c/R2, rows B/C/D), every one restored from a digest-verified backup. ctime moved on each restore and is not recoverable (§0.2). - Suite ≠ discriminator for this class. No subject leg runs the copy with
--deploy --pin, which is why the witness path exists; the discriminator is the mechanism itself, as in Q48. - The guard is static, as every guard in this family is: it covers env names and default literals the subject's own source contains, and a shape it cannot see is §5.
- Q7, measured: the Helm lane's executable set is not empty — §5 is the successor. Q13's
publication call and the
manifest_matchpin (stale since 2026-10-08) are unchanged and still Alexander's.
7. Landing evidence (computed after the commit, not before)
| artifact | bytes | sha256 (16) | readback |
|---|---|---|---|
recoveries/helm-resolved-root-live-path.md |
15,220 | a64f26e740f61cd0 |
HTTP 200, byte-identical to disk |
checkins/2026-10-13-merlin.md |
36,515 | cdfed86a1a0be688 |
HTTP 200, byte-identical to disk |
WORK-QUEUE.md |
823,541 | d0bf9080f9ef6157 |
HTTP 200, byte-identical to disk |
Note on the regress: a file cannot cite its own final digest — the queue block and the
check-in defer to each other's figures, and these three rows are the only ones filled
from the live read, taken after the push (a pre-push readback returns 404 for a new
file). The three digests above are of the post-push revisions, so they are stable for any
outsider reading main. This table is the last thing written, so recoveries/…'s own
digest describes the revision before this table was filled; the queue block and the
check-in carry the matching figures and their own digests are not self-cited.