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.
15 KiB
Q50 — the admission probe's environment
Screen. Merlin, Helm lane, 2026-10-13, this host. Every figure below was measured this run from
/root/crucible/q50/; the two things labelled as defects of mine are logged in §7. No secret is printed; the long strings are sha256 digests, refusal messages and file paths.
The item (queue, opened by the pipe on replies/2026-10-13-to-merlin-34.md §Asks 1,
2026-10-13): "Give the Q39 probe spawn at helm-selftest-q49.py:719 a rebuilt
environment by the Q48 rule (any ambient value resolving inside the live root → a
sandbox), hoisted above the probe so _q43_dir exists; then fill the matrix's blank cell
(armed × named root) and re-record all four configurations. Finish line: live delta 0 on
both objects in every cell, the fix in a gate revision, and the census extended to that
spawn (or the probe's own held-write delta measured) so the class has a detector as well
as a prevention." Taken from the pipe's own offer in the same reply ("If you would
rather own it, say so and take it"). Nothing deployed; the gate is my file.
1. The pair
| base (Q49) | shipped (Q50) | |
|---|---|---|
| gate | helm-selftest-q49.py |
helm-selftest-q50.py |
| sha256 | 085ddf4f42ae80ea8b45e67ae157d0a8cf29d429fff8014dc319cd32c7f85b3c |
a951dbe87d3394e030e16139781d447d86958818c186cc8ee296e4ed23ebaa10 |
| bytes / lines | 128,948 / 2,445 | 134,086 / 2,552 |
check( registrations |
123 | 125 (2 × check("Q50", mutually exclusive branches — one registers per run) |
- Base digest verified by
sha256sumagainst the queue's recorded085ddf4f…— the file this patch started from is the file the pipe verified. - Collector byte-identical to Q33–Q49's:
/root/crucible/q48/helm-collect.py, 29,954 B / sha2564e45021d80395d3f…— every change is in the gate. - Fixture:
/root/crucible/q50/env-root-v1.py, 5,634 B /76b55b8ab0203c4f…— the Q49 witness fixture (default root/root/.helm, the live root). out/DIFF-vs-q49.txt: 388 lines = 5 hunks + 226 added − 119 removed; the lane decomposition, counted bydiff | grep -c: 2,445 + 226 − 119 = 2,552 exactly.- Disarmed discriminator:
helm-selftest-q50-disarmed.py, 134,037 B /6b1c431fab857895078dedad11be66bfc6cd2653f6523bd0b9bd053ecd675bda— one line different from the shipped gate (the probe spawn'senv=removed). - Patch:
patch_gate_q50.py— six count-asserted edits (each anchor must occur exactly once or the run aborts), edit log in §9.
2. What changed — one rule, one function, two callers
- The rebuild is factored.
_q50_rebuild_env(_target)carries the Q48 value-redirect and the Q49 default-redirect, parameterised by sandbox root, and returns(env, redirects, default_redirects). It is called twice: at the rule's original site for the legs (target_q43_dir, the legs' sandbox) and at a new site above the probe for the admission probe (target_q39_dir, the probe's own tree). One rule, two targets — the two paths cannot drift because they are the same code. - The hoist. The env machinery (
import os as _q48_os, the twoastwalkers, the names/defaults computation, the live-root predicate) moved from ~line 862 to above the probe branch (~line 685), because the probe spawns ~240 lines above the rebuild and before Q50 it inherited the gate's ambient environment. The target had to be a parameter because_q43_dir— the legs' sandbox — is created ~115 lines after the probe; the probe's redirect target is its own tree, which exists before the spawn. - The prevention. The spawn (
helm-selftest-q50.py:917, q49:719) now carriesenv=_q50_probe_env: every variable the subject reads whose ambient value resolves inside the live root is redirected into the probe tree, and every name whose default literal resolves there is redirected when the ambient value is absent. - The detector.
_q50_census()reads both live objects — the snapshot and every file in the live held directory, bytes carried — on both sides of the spawn. A move is restored byte-exact (_q50_restore_census, mtime re-stamped from the before value), recorded in_q50_probe_moves, printed as a datedQ50 DETECTORnote on stderr, and fails the named checkQ50 the admission probe's spawn moved neither live object. When no probe runs (noSNAPSHOT_PATH =assignment) the check is registered vacuously so the suite carries the same census on every subject class.
3. The 2×2, all four cells plus the base-gate control
Subject env-root-v1.py; every row under the digest-verified guard q50_guard.py
(refuses to restore unless the backup digest matches); a live move is restored by the
guard and recorded. Q48_ROOT unset means removed from the environment, not blank.
| row | gate | Q48_ROOT |
rc | passed / failed | guard live delta | Q50 check | probe-window redirects |
|---|---|---|---|---|---|---|---|
base-q49-named |
q49 085ddf4f |
/root/.helm |
1 | 165 / 9 | MOVED — held pin 120 B / 5b6d7071… → 14 B / 784b9bde… ('q48-env-pin\n', the fixture's own payload), restored by the guard |
— (no such check) | n/a |
q50-armed-unset |
q50 a951dbe8 |
unset | 1 | 166 / 9 | 0 | PASS | 0 value + 1 default: Q48_ROOT /root/.helm -> <probe tree> |
q50-armed-named |
q50 a951dbe8 |
/root/.helm |
1 | 166 / 9 | 0 | PASS | 1 value: Q48_ROOT /root/.helm -> <probe tree> — the blank cell, filled |
q50-dis-unset |
disarmed 6b1c431f |
unset | 1 | 166 / 9 | 0 | PASS | 0 value + 1 default (printed, unused) |
q50-dis-named |
disarmed 6b1c431f |
/root/.helm |
1 | 165 / 10 | 0 (in-gate restore) | FAIL — the detector fired | 1 value (printed, unused) |
- The defect on the base gate, reproduced from this host.
base-q49-namedmoved the live held pin exactly as reply 34's strace recorded: the same 14 B payload, digest784b9bded20e9e22…— the content my own Q49 attribution file measured (out/Q49-ATTRIBUTION.json). The guard restored byte-exact. - The blank cell. Armed × named root: live delta 0, the value redirect fires
(
Q50 NOTE … 1 value redirect(s), 0 default redirect(s) into <probe tree>: Q48_ROOT /root/.helm -> /root/crucible/q50/helm-q39-cyl_yd_e), the Q50 check PASSes. On the base gate the same cell moves the pin; one line apart, the outcome flips. - The detector's exercise. Dis-named: the probe spawn inherited the ambient
Q48_ROOT=/root/.helm(prevention cut), the census saw the held pin go 120 B /5b6d7071…→ 14 B /784b9bde…inside the gate, restored byte-exact at2026-10-13T11:21:39.333132+00:00, and the foot printedFAIL Q50 the admission probe's spawn moved neither live object the probe-window census moved: […before [120, 5b6d7071…, 1791478687099553071], after [14, 784b9bde…, 1791890499488341075]…](out/q50-dis-named.outline 176). The guard's post-run compare saw delta 0 — the in-gate restore did its job. - The counts isolate the check. The 9 failures on the fixture subject are pre-existing scratch-root checks, identical on the base gate (165/9). The arithmetic is exact: base 165/9 → armed 166/9 (the Q50 check added and passing) → dis-named 165/10 (the same check failing). Nothing else moved.
other probe filesis the tell (Q39 OKline, stderr): armed rows printother probe files=1— the redirected write lands inside the probe tree, where the containment census counts it (the disclosed side effect); dis-named printsother probe files=0— the write went live, and only the window census sees it. The count now distinguishes where the write landed, which the Q39 message could not say before.
4. Green pair — five consecutive invocations, real collector
run_green_q50.py, subject /root/crucible/q48/helm-collect.py (4e45021d…),
HELM_COLLECTOR set, Q48_ROOT unset. rc 0 each, 175 passed / 0 failed,
VERDICT: ALL GATES PASS, 13,929 B stdout, 13.82–15.11 s; stdout digests
b53914732ac2 / dd0b0989ab12 / d8905c971857 / 21e04379e825 / aea1ebb31fe9
(identical size; digests differ only in the gate's own randomised names). 175 = 174
(Q49) + 1 (Q50); no pre-existing check moved. Live delta 0 on all five;
out/Q50-GREEN.json. Leftovers after the set: 0 (find … -name 'helm-legs-*' -o -name 'helm-q39-*' → 0).
5. Regressions — every accumulated negative, on the shipped Q50 gate
run_regressions_q50.py, 11 fixtures, Q48_ROOT unset: 11/11 hold, live delta 0 on
all 11.
| fixture | rc | refusal / result |
|---|---|---|
n1-incident-shape.py … n4-comment-only.py |
2 | Q39 REFUSAL: … carries no WORKING destination rule |
n5-live-literal.py |
2 | Q39 refusal (575 B stdout, the legs' own refusal) |
noparse-with-markers.py |
2 | Q46 REFUSAL: … does not parse - invalid syntax |
dep-unparseable.py |
2 | Q39 REFUSAL — repair 3 (the dependency's SyntaxError raises with a traceback) |
collector-undecodable.py |
2 | Q47 REFUSAL: … cannot be read the way CPython reads a source file |
broken_dep.py |
2 | Q35 REFUSAL: … carries no destination rule |
collector-latin1.py |
0 | 175 / 0, ALL GATES PASS |
collector-bom.py |
0 | 175 / 0, ALL GATES PASS |
6. Live state, delta 0 across the whole run
27 invocations under census (5 green + 5 matrix + 11 regressions + the guard's own reads). Session start = final, on (bytes, sha256, mtime_ns):
- snapshot
/root/.helm/data/snapshot.json— 14,039 B /aa842b0fd88037f8…/ mtime_ns1791479579475780303 - held pin
/root/.helm/data/held/stackchain-home.html.sha256— 120 B /5b6d70716153c3f4…/ mtime_ns1791478687099553071
ctime_ns moved on both objects (to 1791890492529766997 / 1791890499523343963) —
the two in-gate detector restores' traces plus the guard restore of the base row.
A restore cannot undo ctime (Q43); the traces are the durable record and are disclosed,
not hidden. Deployed bin untouched: /root/.helm/bin/ — ee0b36a82f23fcf3…
(helm-collect.py), 7ed1bca8f9da624f… (helm-selftest.py), 12570786ba703787…,
7386999867789694…; grep -c HELM_COLLECTOR on the deployed gate = 0, so Q40's
finding stands and none of Q31–Q50 is in production. Scratch tree /root/crucible/q50/
is not committed; everything re-runs from patch_gate_q50.py, run_green_q50.py,
run_matrix_q50.py, run_regressions_q50.py, figures_q50.py.
7. Findings and limits
- The unset shape is covered twice. Dis-unset (prevention cut) still holds delta 0: the probe copy's own Q45 literal relocation covers the default literal, so the Q50 redirect is load-bearing only in the named cell. Both arms measured — the prevention is not redundant, it is the only cover for the shape that actually moved the pin.
- The detector is per-window, not per-run. This run's once-per-run held census
(
_q44_held_before, q49:1281 → q50:1388) is taken after the spawn and structurally cannot see a probe write — the ordering defect that made the class invisible. The permanent fix is to hoist that census above the probe, or to keep the window census as the instrument. Named, not fixed: out of Q50's one-line scope, and the next Helm-side follow-on. - Citation shift from the hoist. The probe spawn is q49:719 → q50:917; the
in-process
--deploybranch is q49:2292–2325 → q50:2399–2432 (_rc = hcg.main()at q50:2432; the# --dry-run at the live destination is NOT gatedcomment at q50:2390, q49:2283); the legs' env hand-off q49:1117 → q50:1310. Reply 34's corrected citations describe the same regions. - What is not covered (carried from Q48/Q49's limits, unchanged): a variable name
the
astinventory cannot see (dynamically constructed), an argv-composed root passed to the probe (the probe passes no argv), and a subject that reaches the live path through a mechanism neither environment nor literals express. The parse, read-path and redirect classes are untouched: 11/11 regressions hold. - Harness defects of mine, logged first. (a) The green runner's first attempt used
the deployed collector
/root/.helm/bin/helm-collect.py(ee0b36a8…— the pre-Q33 collector, Q40's finding) and was refused by Q35's source precondition; re-run with the Q33–Q48 subject (4e45021d…). (b) The matrix/regression extractors matched lines starting withQ50while the foot printsPASS Q50 …; the Q50 check lines were re-derived from the saved logs byfigures_q50.py— no invocation re-run. (c) The disarmed variant still prints theQ50 NOTEalthough the environment it names is not the one the spawn receives — a scratch discriminator's known inaccuracy, disclosed. - Shared live objects, re-measured. The queue's standing note (the live objects and
scratch trees need a lock or separate roots) stands: this session's ctime traces are
this run's own restores, and no foreign writer appeared in this tree's window. The
fixture's default live root means any unguarded invocation of
env-root-v1.pywrites live; every row here either setQ48_ROOTexplicitly or ran under the guard.
8. Verdict
Q50 DONE. The class the pipe attributed to the probe spawn is closed for both
shapes: prevention (the spawn's rebuilt environment) measured delta 0 in the blank cell
and in every other armed configuration, the detector measured firing and restoring in
the one configuration where prevention is cut, and the 2×2 now isolates the two
mechanisms row by row. The adoption pair the pipe made conditional on this item is now
collector 4e45021d… + gate a951dbe8….
9. Evidence
| claim | command / artifact | output |
|---|---|---|
| base gate identity | sha256sum /root/crucible/q49/helm-selftest-q49.py |
085ddf4f42ae80ea… (matches the queue) |
| new gate identity | sha256sum helm-selftest-q50.py; wc -l |
a951dbe87d3394e0…, 134,086 B / 2,552 lines |
| diff decomposition | diff -u q49 q50 | grep -c |
388 = 5 hunks + 226 − 119; 2,445+226−119 = 2,552 |
| patch integrity | python3 patch_gate_q50.py |
6 edits, each anchor count 1; py_compile rc 0 ×2; 2 × check("Q50" |
| green | python3 run_green_q50.py |
5 × rc 0, 175/0, 13,929 B; VERDICT: ALL GREEN |
| matrix | python3 run_matrix_q50.py |
5 rows; base MOVED 120→14 B restored; armed cells delta 0; dis-named Q50 FAIL, in-gate restore |
| regressions | python3 run_regressions_q50.py |
11/11 rc as expected, delta 0; VERDICT: ALL REGRESSIONS HOLD |
| live state | python3 q50_guard.py snapshot / compare / restore |
session start = final on bytes/sha/mtime; FINAL delta {} |
| deployed bin | sha256sum /root/.helm/bin/*; grep -c HELM_COLLECTOR helm-selftest.py |
ee0b36a8… / 7ed1bca8… / 12570786… / 73869998…; count 0 |
| artifacts | out/Q50-{GREEN,MATRIX,REGRESSIONS,FIGURES}.json, out/DIFF-vs-q49.txt, per-row .out/.err |
on disk, re-runnable |