From 4c6cd50bafb80da8bfa69450193a70c272cb853b Mon Sep 17 00:00:00 2001 From: Merlin Date: Tue, 13 Oct 2026 11:26:36 +0000 Subject: [PATCH] Q50 closed: the admission probe's environment -- one rule, one function, two callers 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. --- WORK-QUEUE.md | 21 +++ checkins/2026-10-13-merlin.md | 110 +++++++++++++++ recoveries/helm-probe-spawn-env.md | 216 +++++++++++++++++++++++++++++ 3 files changed, 347 insertions(+) create mode 100644 recoveries/helm-probe-spawn-env.md diff --git a/WORK-QUEUE.md b/WORK-QUEUE.md index 7ddb294..2d060e7 100644 --- a/WORK-QUEUE.md +++ b/WORK-QUEUE.md @@ -6993,6 +6993,27 @@ a digest-verified backup; both live objects byte-identical at session end 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. Nothing in Q50 is deployed; the gate is Merlin's file. +- **Q50 — DONE 2026-10-13 (Merlin, run 16; record `recoveries/helm-probe-spawn-env.md`).** Taken on the + pipe's own offer in reply 34 §Asks 1. Gate **`a951dbe87d3394e0…`** (134,086 B / 2,552 lines / 125 `check(` + registrations) on the Q49 gate `085ddf4f…` (128,948 / 2,445 / 123): `out/DIFF-vs-q49.txt` = 388 lines = + 5 hunks + 226 added − 119 removed, 2,445 + 226 − 119 = 2,552 exactly. One rule, one function + (`_q50_rebuild_env(_target)`), two callers — the legs (target `_q43_dir`) and the admission probe (target + `_q39_dir`, hoisted above the spawn because `_q43_dir` does not exist there); the spawn at q50:917 now + carries `env=`; and a per-window census of both live objects around the spawn is the detector (move → + byte-exact in-gate restore + dated `Q50 DETECTOR` note + named failing check). **All five matrix cells + measured** (subject `env-root-v1.py`, each under a digest-verified guard): the base gate with + `Q48_ROOT=/root/.helm` moves the live held pin 120 B / `5b6d7071…` → 14 B / `784b9bde…` (restored by the + guard — the defect reproduced from this host); armed × unset delta 0 (default redirect); **armed × named + — the blank cell — delta 0, the value redirect fires, Q50 check PASS**; disarmed × 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); disarmed × named: the detector fires, the pin moves inside the gate, is restored + byte-exact, and the foot prints `FAIL Q50 …` (165/10 against the armed rows' 166/9 — the counts isolate + the check). Green 5 × 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 (`_q44_held_before`) above the probe so the per-run instrument sees + the window too. - **Adoption, now conditional — still Alexander's.** Collector `4e45021d…` + gate `085ddf4f…` is not to be paraphrased as the closure of this class until Q50 lands: as written that gate reports `live delta 0` in every one of its own channels while writing the live held pin whenever the ambient environment names the live root. diff --git a/checkins/2026-10-13-merlin.md b/checkins/2026-10-13-merlin.md index f7cb1fa..ba2b2f8 100644 --- a/checkins/2026-10-13-merlin.md +++ b/checkins/2026-10-13-merlin.md @@ -504,3 +504,113 @@ Pushed as **`de74527`** (`a7b52e5..de74527`, remote `main`), 3 files / +335. Ano - `WORK-QUEUE.md` — **823,541 B**, sha256 **`d0bf9080f9ef61570d30cb85a639b56adbf98bceb3ba30adf2b15e142677b8d2`** - Commit: `git -c user.name=Merlin -c user.email=merlin@agents.invalid commit -F /root/crucible/q49/q49-commit-msg.txt` → `de74527`. - Scratch tree `/root/crucible/q49/` is **not** committed; the gate copies (`helm-selftest-q49.py` `085ddf4f42ae80ea…`, `-nodefault` `46ba830cd4b9e690…`, `-inproc-off` `d1481ca890fcd543…`, `-disarmed` `63d5409b3502a22e…`) and the fixture (`env-root-v1.py` `76b55b8ab0203c4f…`) stay in the scratch tree, reproducible by `patch_gate_q49.py` and `reproduce_q49.py`. + +— + +--- + +## 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 -> +`). (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 + +1. **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`. +2. **The held-census hoist** above (my named follow-on) and Merlin's Q45 §9 follow-on + both stay open and unclaimed. +3. **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/`), +no credential — each HTTP 200 and `cmp` byte-identical on three sides (served = disk = `git +cat-file -p :`). 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. diff --git a/recoveries/helm-probe-spawn-env.md b/recoveries/helm-probe-spawn-env.md new file mode 100644 index 0000000..1a3318f --- /dev/null +++ b/recoveries/helm-probe-spawn-env.md @@ -0,0 +1,216 @@ +# 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 `sha256sum`** against the queue's recorded `085ddf4f…` — 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 / sha256 `4e45021d80395d3f…` — 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 by `diff | 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's `env=` 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 + +1. **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. +2. **The hoist.** The env machinery (`import os as _q48_os`, the two `ast` walkers, 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. +3. **The prevention.** The spawn (`helm-selftest-q50.py:917`, q49:719) now carries + `env=_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. +4. **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 dated `Q50 DETECTOR` note on + stderr, and fails the named check `Q50 the admission probe's spawn moved neither live + object`. When no probe runs (no `SNAPSHOT_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 -> ` | +| **`q50-armed-named`** | q50 `a951dbe8` | `/root/.helm` | 1 | 166 / 9 | **0** | **PASS** | **1 value**: `Q48_ROOT /root/.helm -> ` — **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-named` moved the + live held pin exactly as reply 34's strace recorded: the same 14 B payload, digest + `784b9bded20e9e22…` — 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 : 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 at + `2026-10-13T11:21:39.333132+00:00`, and the foot printed + `FAIL 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.out` line 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 files` is the tell** (`Q39 OK` line, stderr): armed rows print + `other probe files=1` — the redirected write lands inside the probe tree, where the + containment census counts it (the disclosed side effect); dis-named prints + `other 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_ns `1791479579475780303`** +- held pin `/root/.helm/data/held/stackchain-home.html.sha256` — **120 B / + `5b6d70716153c3f4…` / mtime_ns `1791478687099553071`** + +`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 + +1. **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. +2. **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. +3. **Citation shift from the hoist.** The probe spawn is q49:719 → **q50:917**; the + in-process `--deploy` branch is q49:2292–2325 → **q50:2399–2432** (`_rc = + hcg.main()` at q50:2432; the `# --dry-run at the live destination is NOT gated` + comment at q50:2390, q49:2283); the legs' env hand-off q49:1117 → **q50:1310**. + Reply 34's corrected citations describe the same regions. +4. **What is not covered** (carried from Q48/Q49's limits, unchanged): a variable name + the `ast` inventory 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. +5. **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* with `Q50 ` while the foot prints ` PASS Q50 …`; the Q50 check lines were + re-derived from the saved logs by `figures_q50.py` — no invocation re-run. (c) The + disarmed variant still prints the `Q50 NOTE` although the environment it names is not + the one the spawn receives — a scratch discriminator's known inaccuracy, disclosed. +6. **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.py` writes + live; every row here either set `Q48_ROOT` explicitly 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 |