diff --git a/WORK-QUEUE.md b/WORK-QUEUE.md index 17569f2..9ee46b4 100644 --- a/WORK-QUEUE.md +++ b/WORK-QUEUE.md @@ -6181,10 +6181,11 @@ measurement, never on the live path. **Evidence:** the run transcripts, the gate's and subject's digests, the live snapshot's (sha256, mtime_ns, ctime_ns) and the protected file's (sha256, mtime_ns, ctime_ns) around every invocation — the Q43 evidence contract, unchanged. -**Status: OPEN, offered to the Helm lane (Merlin).** The residual this targets is measured, not hypothesised -(`/root/crucible/q43z/timmy_verify_q43.py`, `replies/2026-10-12-to-merlin-27.md` §5). Alexander's `cp` decision is -**not** a precondition: the pair stays undeployed and this unit runs in scratch. With Q43 closed and Q44 opened, the -Helm lane has one executable item again. +**Status: DONE 2026-10-12 — `recoveries/helm-root-redirect.md` (Merlin, Helm lane); the closure block is at the foot +of this file.** The residual this targets was measured, not hypothesised +(`/root/crucible/q43z/timmy_verify_q43.py`, `replies/2026-10-12-to-merlin-27.md` §5). Alexander's `cp` decision was +**not** a precondition: the pair stayed undeployed and this unit ran in scratch. With Q43 closed and Q44 closed, the +Helm lane's executable set is empty again. **Q43 (leg sandbox) verified here; the disk went 7.0 GB → 46 GB because the music tree and the live swap were MOVED, not deleted. 2026-10-12 18:45Z (control plane).** @@ -6220,3 +6221,77 @@ Helm lane has one executable item again. `/root/.helm/bin/`. The deployed gate `7ed1bca8…` has no subject-selection path, so none of Q31–Q43's protection is in production until the `cp` lands. If Alexander approves it, that is the moment to send Praetorian the exact pair read-only — with the caveat that Praetorian's provider route is still dead. + +--- + +**Q44 (Helm lane) — CLOSED 2026-10-12 by Merlin. The pin class is closed by construction, not by argv rule.** + +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.** Collector **byte-identical to Q43's** (`4e45021d80395d3f…`, 29,954 B / 720 lines) — + every change is in the gate. Gate `18580c102ddb1c3f…` (**1,731 lines / 94,716 B**) against Q43's + `c22601ecea4192d9…` (1,612 / 88,468); `diff -u` = 195 lines = 3 header + **133 added + 14 removed + + 45 context**, and 1,612 + 133 − 14 = **1,731** exactly (`out/DIFF-vs-q43.txt`). +- **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 — + its message now says the root redirect means the leg would write the sandbox's held directory. + 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 run as the original, 0 refusals, sandbox + holds 3 paths with no `data/snapshot.json`). +- **The five Q39 negatives, still refused** — 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. Q44 is additive to the admission machinery, not a + replacement for it. +- **The discriminator, on a scratch protected root (never live)**, rule (3) disarmed and a + `--pin --deploy` leg appended at the foot: + `pinleg` (redirect armed) → **rc 0 / 169 passed**, the pin lands in the sandbox + (`pinned e40308e573d046d7d503acecbfbde82b8ccd10758a345b0440d55b796e2d821e -> /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**). The three reds are the + expected measurement of a disabled mechanism (the Q44 structural check plus the two G8 + destination-naming checks, which fail because the variant relocates the copy's destination). + **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 §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. diff --git a/checkins/2026-10-12-merlin.md b/checkins/2026-10-12-merlin.md index 8bacf0e..bd06e4a 100644 --- a/checkins/2026-10-12-merlin.md +++ b/checkins/2026-10-12-merlin.md @@ -1122,3 +1122,144 @@ authored `Merlin ` (`git -c`), so the monitor sees it as counterparty content per Q36's v1.2 rule. — Merlin + +--- + +# Run 10 — Q44: the pin class is closed by construction, not by argv rule + +### Screen + +Q44 (Helm lane) — the top open item, offered to this lane by the pipe on Q43's +own closing sentence. Scratch tree `/root/crucible/q44/`; deliverable +`recoveries/helm-root-redirect.md`. Nothing deployed, no `cp` into +`/root/.helm/bin/`, `decisions/` and `inbox/` untouched, no another-party file +edited, no secret printed. + +### Did + +- **The pair.** Subject collector `4e45021d80395d3f…` (29,954 B / 720 lines) is + **byte-identical to Q43's** — every change is in the gate. Gate + **`18580c102ddb1c3f3bd92681402f98ef02f75ee8601c72224c508f9121573c79`** + (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. +- **The redirect is applied at the root, not path by path.** 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 + explicit `SNAPSHOT_PATH` rewrite is kept as defence in depth; both point into + the same sandbox. +- **Rule (3) is reclassified, not removed.** A `--pin` leg is still refused + (exit 2, no leg executed), but the refusal message now states it is **defence + in depth**: the root redirect means the leg would write the sandbox's held + directory. 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 (`ctime` because + it is the stamp a restore cannot undo — Q43's discriminator). +- **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 fixture 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. Q44 is additive to the admission machinery, not a replacement. +- **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 -> + /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. +- **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…`). + +Evidence, all from this host, 2026-10-12T20:4xZ: `out/Q44-NEGATIVES.json` +(66,881 B / `611ab1e06e172be535ae5f19e21b4e1a6667a0f1b7542d130fc9d72298c5b895`, +`generated_at 2026-10-12T20:49:04Z`, 17 rows, each with rc, stdout bytes/sha, +the report-block flag, the live snapshot's (bytes, sha256, mtime_ns, ctime_ns) +before and after, the live and protected held censuses, and leftover sandboxes), +per-run `.out`/`.err` logs, `out/DIFF-vs-q43.txt`, `out/run.log`. + +### Blocked + +Nothing in my lane. Same standing gates, all Alexander's: the §4 adoption `cp` +of the Helm pair (now collector `4e45021d…` + gate `18580c1…`), Q9 (checksum +handover), Q13 (the wording fixes), Q19 (wizard onboarding). Host disk not +re-measured this run — the 46 GB reading from Run 9 stands as a dated reading, +**unverified today**. + +### Next + +1. **The pipe's verification of this run**, when it lands. Everything here is + re-runnable from `/root/crucible/q44/`: `run_negatives.py` is self-contained, + `make_variants.py` regenerates the two discriminators from the gate, and + `patch_gate.py` / `patch_gate2.py` reproduce the gate from Q43's. +2. **The Helm lane's executable set is empty again.** Q9, Q13, Q19 and the + adoption all wait on the same human call, so the next Helm unit is a decision + rather than a task. The one follow-on I would name if the lane is re-opened: + the redirect keys on the `HELM_HOME =` assignment, so a subject composing its + live paths from a differently-named root is outside it — closing that would + mean resolving every path literal against the live data root rather than + rewriting named constants. +3. **Q42** stays open and offered to zephyrus — the non-magazine second pass, + whose value now sits in the fresh-bytes half. + +### Ask + +**Alexander** — unchanged and consolidated: (a) rule on the §4 adoption `cp` for +the Helm pair (collector `4e45021d…` + gate `18580c1…`), since Q40 measured that +the deployed gate `7ed1bca8…` has no subject-selection path at all and none of +Q31–Q44's protection is in production until it lands; (b) the +`stackchain-dashboard.service` 32-byte key repair or a stop; (c) the Q13 +publication call, where item 13's decided note (escalate, do not edit the live +page) still controls over its `proposed_action`. + +**The pipe** — nothing owed from this run. One thing to check rather than accept: +`pinleg`'s "the pin landed in the sandbox" is the whole discriminator, and it is +measured by `Q44-PIN`'s stdout line plus the protected held census in +`Q44-NEGATIVES.json` — a re-run of that one variant costs about 13 seconds and +would falsify it if the sandbox name in the line were not the run's own. + +Landing evidence, Run 10, appended after the commit and 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** to disk: + +| file | status / bytes | sha256 | +|---|---|---| +| `recoveries/helm-root-redirect.md` | 200 / 12,811 | `e68be4cdd5a535e22eae87f5ffbf9cb6841ea3d8a044ae34c13e74f86460568b` | +| `WORK-QUEUE.md` | 200 / (this file's own pre-append revision — see note) | — | + +The queue's own row cites the deliverable's digest, which the reader can verify +from the repository; this file's own digest after the fill is deliberately not +printed, for the reason Run 9's block states. Nothing was deployed, published or +rebuilt; `/root/.helm/bin/` was not touched; `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 paths, check names, refusal +messages and sha256 digests. This run's commit is authored +`Merlin ` (`git -c`), so the monitor sees it as +counterparty content per Q36's v1.2 rule. + +— Merlin diff --git a/recoveries/helm-root-redirect.md b/recoveries/helm-root-redirect.md new file mode 100644 index 0000000..5883e90 --- /dev/null +++ b/recoveries/helm-root-redirect.md @@ -0,0 +1,185 @@ +# Q44 — the pin class: redirect every constant composed from the live data root + +> Screen. Every claim below carries a path, a hash, or a command with its output, or the word +> `unverified`. Nothing secret is printed in this file. The long strings are file paths, check names +> and sha256 digests. + +**Item:** Q44 (Helm lane), opened by the pipe on Q43's own closing sentence. +**Scratch tree:** `/root/crucible/q44/`. **Nothing deployed**, no `cp` into `/root/.helm/bin/`, +no production path touched, `decisions/` and `inbox/` untouched, no another-party file edited. + +--- + +## 1. The pair + +| artifact | value | +|---|---| +| subject collector `/root/crucible/q44/helm-collect.py` | **byte-identical to Q43's** — `4e45021d80395d3f03fc58d7e99610e3c1c0d2fcf9c85f1db50b16cfd2043c6c`, 29,954 B / 720 lines | +| gate `/root/crucible/q44/helm-selftest.py` | `18580c102ddb1c3f3bd92681402f98ef02f75ee8601c72224c508f9121573c79`, 94,716 B / **1,731 lines** | +| Q43 gate (superseded) | `c22601ecea4192d98c1140370b448c3d10924c1b4dfac438665f447014ade599`, 88,468 B / 1,612 lines | +| diff | `diff -u q43/helm-selftest.py q44/helm-selftest.py` → 195 lines = 3 header + **133 added + 14 removed + 45 context**; 1,612 + 133 − 14 = **1,731** (`out/DIFF-vs-q43.txt`) | + +The collector did not move. Every change is in the gate — the same shape as Q43. + +## 2. What changed + +**The redirect is applied at the root, not path by path.** Q43 rewrote every `SNAPSHOT_PATH =` +assignment and left `HELD_DIR = HELM_HOME / "data" / "held"` (`helm-collect.py:31`) pointing at the +live tree, so a `--pin` leg was held back by argv rule (3) and by nothing else. The Q44 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 the `HELM_HOME` name — and is +`['SNAPSHOT_PATH', 'HELD_DIR']` for this subject. The explicit `SNAPSHOT_PATH` rewrite is kept as +defence in depth; both point into the same sandbox. + +**Rule (3) is reclassified, not removed.** A `--pin` leg is still refused (exit 2, no leg +executed), but the refusal message now states that it is **defence in depth**: the root redirect +means the leg would write the sandbox's held directory, not the live one. Q39's admission, its +behavioural probe and its per-leg watch are untouched, and no check's meaning changed — the suite +count moved only by the three new checks. + +**Three new checks, all measured on the copy the legs will run:** + +1. `Q44` — *the live-data-root constant is redirected, not only the paths composed from it* + (`HELM_HOME` assignment found, and the probe's printed value non-empty). +2. `Q44` — *every constant composed from the live data root resolves inside the sandbox*. This is + **not a source-marker check**: a subprocess imports the copy and prints the live values of + `HELM_HOME` and every derived constant, and each must resolve to the sandbox root or inside it. +3. `Q44` — *the live held directory is unchanged by every leg in the run*. The held directory is + censused (bytes, sha256, **mtime_ns and ctime_ns**) before any leg and again at the foot. + `ctime` is in the census because it is the stamp a restore cannot undo — Q43's discriminator. + +Green count: **169 passed / 0 failed** (Q43's 166 plus these three). + +## 3. Green pair — 5 consecutive invocations + +`HELM_COLLECTOR=/root/crucible/q44/helm-collect.py python3 /root/crucible/q44/helm-selftest.py`, +run by `/root/crucible/q44/run_negatives.py` at 2026-10-12T20:4xZ (`out/run.log`, +`out/Q44-NEGATIVES.json`, 66,881 B / `611ab1e06e172be535ae5f19e21b4e1a6667a0f1b7542d130fc9d72298c5b895`): + +| run | rc | stdout | stdout sha256 (first 16) | passed line | seconds | +|---|---|---|---|---|---| +| `q44-green-1` | 0 | 12,966 B | `5c519157eaec4a40…` | `helm-selftest: 169 passed, 0 failed` | 12.07 | +| `q44-green-2` | 0 | 12,966 B | `97916ab27b8521c0…` | `169 passed, 0 failed` | 11.95 | +| `q44-green-3` | 0 | 12,966 B | `6f5553364b7a0d09…` | `169 passed, 0 failed` | 12.14 | +| `q44-green-4` | 0 | 12,966 B | `ad57bad15689dfc5…` | `169 passed, 0 failed` | 12.08 | +| `q44-green-5` | 0 | 12,966 B | `00a1ae6dc2519efc…` | `169 passed, 0 failed` | 12.00 | + +`VERDICT: ALL GATES PASS` in all five. Every run's live snapshot and live held directory are +**delta 0** (see §6). The Q43 counters are unchanged: `4 subject legs redirected to the rewritten +copy … 3 --dry-run legs run as the original … 0 refusals; the sandbox holds 3 paths: +__pycache__, __pycache__/subject-legs.cpython-311.pyc, subject-legs.py` — no `data/snapshot.json`, +so on a green run no leg wrote even the sandbox destination. + +The Q44 line, identical on all five (sandbox name per-run): + +``` +Q44: 1 live-data-root assignment(s) redirected to ; 2 constant(s) composed from it +(HELD_DIR, SNAPSHOT_PATH) resolving inside the sandbox: HELD_DIR=/data/held, +HELM_HOME=, SNAPSHOT_PATH=/data/snapshot.json; the live held directory +/root/.helm/data/held holds 1 file(s), unchanged (sha, mtime_ns and ctime_ns compared) +``` + +## 4. The five Q39 negatives, still refused + +Each of the five Q39 fixtures run as the subject of the **Q44 gate** and, as the pre-Q44 +comparator, of the **Q43 gate**. All ten runs: **rc 2, stdout 0 B (`e3b0c44298fc1c149…`, the +empty-file digest), no `helm-selftest:` report block, live snapshot delta 0, live held delta 0, +0.14–0.21 s, no sandbox left behind.** Stderr names the condition (`out/q44-neg-*.err`): + +| fixture | Q44 rc | Q43 rc | refusal named | +|---|---|---|---| +| `n1-incident-shape` | 2 | 2 | `carries no WORKING destination rule — a bare run against the sentinel exited rc=0 … sentinel written=True` | +| `n2-traceback` | 2 | 2 | `… exited rc=1 (stderr: Traceback …)` | +| `n3-decoy` | 2 | 2 | same as n1 | +| `n4-comment-only` | 2 | 2 | same as n1 | +| `n5-live-literal` | 2 | 2 | `names the live destination as an absolute literal, which the probe's rewrite cannot redirect` | + +Q44 changed nothing here, which is the result: the new mechanism is additive to the admission +machinery, not a replacement for it. + +## 5. The discriminator — prevention separated from detection, by measurement + +Two variants of the Q44 gate (`variants/helm-selftest-pinleg.py`, +`0f9cc660ea8a821141a3c4ffe583e0523ad58012656bf4ccb5d0ebcacdafebc1`, and +`variants/helm-selftest-pinleg-noredirect.py`, +`084a4fcdf85f2c5c87757d1162a342eae95c7b4a6a4288ff9f71c3531fb056ee`), each with **rule (3)'s +`--pin` refusal disarmed** and a `--pin --deploy` leg appended at the foot so it runs through the +same leg wrapper the suite uses. Both protect a **scratch** sentinel +(`protected/data/snapshot.json`) and stand the live data root in for a scratch protected root +(`protected/`), so neither variant can touch `/root/.helm`. + +| | `pinleg` (root redirect **armed**) | `pinleg-noredirect` (root redirect **disarmed**) | +|---|---|---| +| rc | **0**, `169 passed, 0 failed` | **1**, `166 passed, 3 failed` | +| the `--pin --deploy` leg | rc 0 | rc 0 | +| where the pin landed | `pinned e40308e573d046d7d503acecbfbde82b8ccd10758a345b0440d55b796e2d821e -> /data/held/stackchain-home.html.sha256` | `pinned e40308e573d046d7d503acecbfbde82b8ccd10758a345b0440d55b796e2d821e -> /root/crucible/q44/protected/data/held/stackchain-home.html.sha256` | +| sandbox held file | exists (per-run digest; embeds the pin clock) | **does not exist** | +| protected held file, before | `114 B / b58d2d0eb9f45862b3c7bd270374153c8304b446f7d034428a921ebd8e3ca596 / mtime_ns 1791478687000000000 / ctime_ns 1791838054542473495` | same | +| protected held file, after | **identical — delta 0** | `120 B / f98215b460bf89764924096c6fc657e62173e4c26f99bc4a2e5c489f3ed0f416 / mtime_ns 1791838143983952745 / ctime_ns 1791838143991953414` — **NONZERO** | +| the 3 reds | none | `Q44 every constant composed from the live data root resolves inside the sandbox` (values are the protected root), plus the two G8 destination-naming checks, because the copy's destination is now the protected path rather than the sandbox | + +So: **the same `--pin --deploy` shape that Q43 could only refuse by argv rule now cannot reach the +protected root at all**, and the one variant where the redirect is disarmed writes it — measured on +a stand-in, never on the live path. The live held directory is delta 0 in both variants (§6). + +The live-path write was **not** re-measured: doing it would clobber +`/root/.helm/data/held/stackchain-home.html.sha256`. Q43 already measured that write +(`pinned 5e5cd6d26a70…`), and this run's `pinleg` variant reproduces the same body digest +`e40308e5…` from the same code path, so the class is the same one. + +## 6. Live state, across all 17 invocations + +| target | reading | delta | +|---|---|---| +| `/root/.helm/data/snapshot.json` | 14,039 B / `aa842b0fd88037f83115b4a54d617f27666f4de06ef84f9a64c8fd0a5f04656d` / mtime_ns `1791479579475780303` / ctime_ns `1791789490146606356` | **0** on every one of the 17 runs | +| `/root/.helm/data/held/stackchain-home.html.sha256` | 120 B / `5b6d70716153c3f4bd82045d5d71edf88f793a240ce1275c3dfca5759d0ee297` / mtime_ns `1791478687099553071` / ctime_ns `1791478687102553111` | **0** on every one of the 17 runs | + +Deployed bin, untouched: `helm-collect.py` `ee0b36a82f23fcf3…`, `helm-selftest.py` +`7ed1bca8f9da624f…`, `helm-render.py` `5c67473d89283c3d…` (also `helm-validate.py` +`12570786ba703787…`, `summarize.py` `7386999867789694…`). No `cp`; the §4 `cp` remains Alexander's. +Public page `https://alexanderwhitestone.com/helm/` — **not re-fetched this run; unverified**. + +## 7. Limits, stated + +- The redirect keys on the **`HELM_HOME =` assignment**. A subject that composes its live paths from + a differently-named root, or builds them at call time from a literal, is not covered by the + substitution — Q39's literal scan and the per-leg watch remain the backstop, unchanged. +- The `--pin` refusal is kept as defence in depth, so **the gate never exercises the redirect on a + `--pin` leg itself**. The redirect's effect on that shape is measured by the `pinleg` variant, + which is a copy of the gate with the refusal disarmed — not by the shipped gate. +- The Q44 probe imports the copy in a subprocess and reads module attributes. A subject whose + derived constant is a call rather than a module-level `Assign` would not appear in the derived + set; the held-directory census is what would catch an actual write. +- `pinleg-noredirect`'s three reds are the **expected** measurement of a disabled mechanism, not + defects in the gate. Two of them (the G8 destination-naming checks) fail because the variant + relocates the copy's destination; one edit was needed to keep the Q43 resolution-leg check + meaningful under that relocation (see §8, defect 2). +- One observation, not chased: in `pinleg-noredirect` the Q43 sandbox census holds **5 paths** + including `data/snapshot.json`, i.e. a leg wrote a snapshot inside the sandbox. It is the sandbox + doing its job, the protected snapshot is delta 0, and the green runs show 3 paths, so nothing + rides on it — but it is unexplained and recorded rather than explained away. + +## 8. This run's own defects, logged first + +1. **The inside-sandbox comparator excluded the sandbox root itself.** `startswith(dir + "/")` is + false for `HELM_HOME`, whose value *is* the sandbox root, so the first green run printed + `168 passed, 1 failed` with the mechanism demonstrably working. Caught by reading the failure + text rather than the tally; fixed in `patch_gate2.py`, and the fix is the only reason the count + is 169 and not 168. +2. **The Q43 resolution-leg check false-fails under the variants.** It compares the subject's own + `--dry-run` banner against `_q35_live`, which the variants relocate to the scratch sentinel, so + the real collector's live default can never match. Fixed **in the variant only** + (`patch_variants.py`, one anchor line plus one comparison), by restoring the check's target to + the deployed collector's own destination while every protection check keeps the scratch path. + The shipped gate is untouched by this edit. +3. **`run_negatives.py` referenced `PROT_ROOT` before it was defined** — a `NameError` on the first + harness run, before any gate invocation. Fixed before the measured run; the figures above are + from the corrected harness. + +## 9. Ask + +Alexander: the §4 adoption is the only thing between this pair and production, and it is now a +**three-file** ask — collector `4e45021d…` (unchanged since Q34) plus gate `18580c1…`, with the +adoption of the Q40 finding (the deployed gate has no subject-selection path at all) unchanged. +Nothing else is blocking from this lane.