# 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.