From fad802c0f528e4b14838d5e1dda6cbb6354afa9e Mon Sep 17 00:00:00 2001 From: Merlin Date: Mon, 12 Oct 2026 09:32:18 +0000 Subject: [PATCH] Q39: the destination guard's admission is keyed on the subject's own destination, and every leg is watched (Helm lane) Five repairs to the Q38 gate (74aba348 -> 9b09f328, +309/-38): the probe injects from pathlib import Path so the rewritten line cannot NameError; EVERY SNAPSHOT_PATH assignment is rewritten; a probe that dies with a traceback is refused; an ast scan refuses an absolute path literal resolving inside /root/.helm/data/; and live bytes and mtime are read around every subprocess and around the subject's in-process import, a change dying the run and restoring the bytes it read. Negative controls (q39-negatives.py, Q39-NEGATIVES.json): five fixtures, each run under BOTH gates. n1 (the incident's shape) and n2 (a probe that dies with a traceback) are ADMITTED by the Q38 gate - n1's in-process import writes the fixture's destination inside the gate's own run - and refused by the Q39 gate. Green pair: 5 consecutive invocations, rc 0, 164 passed / 0 failed, 17 legs watched, live delta 0 on every run. Live snapshot aa842b0f delta 0 throughout; nothing deployed, decisions/ and inbox/ untouched. --- WORK-QUEUE.md | 10 ++ checkins/2026-10-12-merlin.md | 96 +++++++++++++ recoveries/helm-destination-guard-fix.md | 165 +++++++++++++++++++++++ 3 files changed, 271 insertions(+) create mode 100644 recoveries/helm-destination-guard-fix.md diff --git a/WORK-QUEUE.md b/WORK-QUEUE.md index 4462262..cb6fc4d 100644 --- a/WORK-QUEUE.md +++ b/WORK-QUEUE.md @@ -5155,3 +5155,13 @@ Read from source and from the forge API, not from the PR's narrative. **The four fix candidates, in priority order, from reply 21:** key admission on the subject's own **resolved destination** (refuse anything resolving inside `/root/.helm/data/`, and refuse a probe whose stderr carries a traceback rather than a refusal); rewrite **every** `SNAPSHOT_PATH =` assignment and make the probe independent of the subject's imports (`from pathlib import Path` injected, or `__import__('pathlib').Path(...)`); stop running the original subject in the legs, or assert live bytes **and** mtime after every leg; add the negative control for the class. **Evidence to hand:** `replies/2026-10-12-to-merlin-21.md`; `/root/troupe-verify/q38/` (my reproduction, `containment/`); `/tmp/hermes-probe/` (the incident's probe, fixtures, transcript and restore, mtimes 07:17–07:18Z); commit `8d2b56c` and this file's Q38 section. **Not in scope:** re-litigating the delivered transcripts — they reproduce exactly; the defect is in the criterion and the leg path, not in the measurements. + +**Status: DONE — `recoveries/helm-destination-guard-fix.md` (12,253 B), Merlin, 2026-10-12, scratch tree `/root/crucible/q39/`. Fixed gate `9b09f328…` (1,412 lines / 77,704 B, +309/−38 against Q38's `74aba348…`); subject collector byte-identical at `4e45021d…`; nothing deployed, no `cp` into `/root/.helm/bin/`, live snapshot `aa842b0f…` delta 0 across every run.** +- **Five repairs:** (1) the probe injects `from pathlib import Path` above the subject's body, so the rewritten line cannot NameError on a subject that never imported it (C22's mechanism); (2) **every** `SNAPSHOT_PATH =` assignment is rewritten, not the first; (3) a probe whose stderr carries a traceback is refused — a crash is not a refusal; (4) an ast-based destination-resolution scan refuses any **absolute** path literal in the subject that resolves inside `/root/.helm/data/` (docstrings excluded by a walk that does not descend into `Expr`, which is why the deployed collector's *composed* live path is admitted); (5) live **bytes and mtime** are read around every `subprocess.run` and around the subject's in-process import, a change dying the run with exit 2 and **restoring** the bytes it read. +- **Negative controls, all refused with stdout 0 B, live delta 0, no probe dir left:** `n1-incident-shape` (probe rc=0, sentinel written 2 B) — **Q38 admits it, rc 0, and its in-process import writes the fixture's destination inside the gate's own run**; `n2-traceback` (traceback condition) — **Q38 admits it and then crashes at the import, rc 1**; `n3-decoy` (sentinel written) — **Q38's probe writes the decoy's *real* destination, the containment failure reply 21 measured**; `n4-comment-only`; `n5-live-literal` (absolute-literal rule) — Q38 admits it. `Q39-NEGATIVES.json` at 2026-10-12T09:28:09Z. +- **Green pair, 5 consecutive invocations:** rc 0, `helm-selftest: 164 passed, 0 failed`, `VERDICT: ALL GATES PASS`, `Q39 OK` with probe rc=2, `Q39: 17 legs watched … delta 0 on each`, live delta 0 on all five; the five log digests are in the record. +- **Leg watch measured both ways** against a scratch watched file (`q39-legguard-test.py`, guard extracted by its own BEGIN/END markers): clean leg passes and counts; a leg whose subprocess writes the watched file exits 2, restores bytes **and** mtime, and prints both digests. +- **Two self-corrections logged:** a three-tuple state including atime made every leg fail (reading updates atime) — the state is `(bytes, mtime_ns)`; and the summary line moved from stderr to a flushed stdout `print` because the two streams interleave into one log. +- **Limits:** the static scan is literal-based; repair 4 refuses a legitimate absolute-literal shape (a design decision, not a measurement); the legs still run the original subject, so repair 5 detects-and-restores rather than prevents; the timeout branch and the direct-CLI limit from Q38/Q35 are unchanged; 5 fixtures are not a census. +- **Adoption, Alexander's as before:** the pair is collector `4e45021d…` + gate `9b09f328…`; deployed bin unmoved (`helm-collect.py` `ee0b36a8…`, `helm-selftest.py` `7ed1bca8…`); public page 200 / 9,865 B / `427b7b54c69b18d2…` at 2026-10-12T09:29:03Z; `decisions/` and `inbox/` untouched. +- **Q7, measured:** the archive lane's unclaimed executable set is still empty (Q9 audio handover, Q13/Q19 Alexander); the Helm lane's open items are Q31 (`cp` decision) and Q34-adjacent gates, all gated on the same adoption call. **No new item opened this tick** — the queue is not thin and the lane's next unit is a decision, not a task. diff --git a/checkins/2026-10-12-merlin.md b/checkins/2026-10-12-merlin.md index 9339648..dfbc0a2 100644 --- a/checkins/2026-10-12-merlin.md +++ b/checkins/2026-10-12-merlin.md @@ -467,3 +467,99 @@ authored `Merlin ` (`git -c`), so the monitor sees them a content per Q36's v1.2 rule. — Merlin + +--- + +## Run 5 — Q39: the admission admits a writer, and its legs run the unrewritten subject + +**Did.** Claimed **Q39**, opened by the pipe on Run 4's falsified protective claim (reply 21, C22/C23) — +the top open item and the only executable work in the queue: the archive lane's unclaimed set is empty +(Q9 waits on the audio handover, Q13/Q19 on Alexander), and the Helm lane's remaining items are all +gated on the same adoption call. Deliverable `recoveries/helm-destination-guard-fix.md` (12,253 B); +fixed gate `/root/crucible/q39/helm-selftest.py` **1,412 lines / 77,704 B / sha256 +`9b09f3283008e695bb0634bc2d5492f2c24067b9eeb9ee2100099b7cbfcd1429`**, **+309 / −38** against Run 4's +`74aba348…` (1,141 / 64,454 B); `DIFF.txt` 408 lines / `b8adc9126397f61a…`; subject collector +byte-identical to the Q34/Q35/Q38 pair (`4e45021d…`). + +Five repairs, each with a measurement behind it: (1) the probe injects `from pathlib import Path` above +the subject's body, so the rewritten line cannot `NameError` on a subject that never imported it — C22's +mechanism, removed rather than detected; (2) **every** `SNAPSHOT_PATH =` assignment is rewritten, not the +first; (3) a probe whose stderr carries a traceback is refused, because a crash is not a refusal; (4) an +ast scan refuses any **absolute** path literal in the subject that resolves inside `/root/.helm/data/` — +docstrings are excluded by a walk that does not descend into `Expr` nodes, which is why the deployed +collector's *composed* live path is admitted (`assign line 30: SNAPSHOT_PATH <- 'data' / 'snapshot.json' +-> resolves inside the live data root: /root/.helm/data/snapshot.json`, printed on every green run); +(5) live **bytes and mtime** are read around every `subprocess.run` and around the subject's in-process +import, a change dying the run with exit 2 and **restoring** the bytes it read. + +**Measured, not asserted** — `python3 /root/crucible/q39/q39-negatives.py` → +`RESULT: ALL NEGATIVE CONTROLS REFUSED AS EXPECTED`, `Q39-NEGATIVES.json` at 2026-10-12T09:28:09Z. Each +fixture runs under **both** gates, so every row is a discriminator; the fixtures' destination files are +deleted before each run so "created" is per-run: + +| fixture | Q39 exit | Q39 condition named | Q38 exit | Q38 admitted? | side-effect files created (Q39 / Q38) | +|---|---|---|---|---|---| +| `n1-incident-shape` (the incident's shape) | 2 | probe rc=0, sentinel written 2 B | **0** | **yes** | `[]` / `['n1-dest']` | +| `n2-traceback` (raises at import) | 2 | `the probe died with a traceback …` | 1 | **yes** | `[]` / `[]` | +| `n3-decoy` (decoy first assignment) | 2 | probe rc=0, sentinel written 2 B | 2 | no | `[]` / `['n3-real']` | +| `n4-comment-only` (markers in the docstring) | 2 | probe rc=0, sentinel written 2 B | 2 | no | `[]` / `[]` | +| `n5-live-literal` (proper refusal, live path as a literal) | 2 | `names the live destination as an absolute literal` | 2 | **yes** | `[]` / `[]` | + +Every Q39 run: stdout 0 B, live delta 0, no `helm-q39-*` probe directory left behind. `n1` is the +incident's own shape: **Q38 admits it, and the gate's in-process import writes the fixture's destination +inside the gate's own run.** `n3` reproduces reply 21's containment finding — Q38's probe writes the +decoy's **real** destination; Q39 writes nothing. + +**Green pair, 5 consecutive invocations** (`bash /root/crucible/q39/run-green.sh 5`): rc 0 each, +`helm-selftest: 164 passed, 0 failed`, `VERDICT: ALL GATES PASS`, `Q39 OK … (probe rc=2, sentinel +written=False, other probe files=0, no traceback, live delta 0)`, and `Q39: 17 legs watched … delta 0 on +each`. Log digests: `f0bfaca19d5d2a56…` / `a13a184de21a45a7…` / `e305e9f7dfd5fdc9…` / +`0b2343b6a2fb64bd…` / `eaaf90e14ba85777…`. The leg watch itself is measured both ways +(`q39-legguard-test.py`, guard extracted from the shipped gate by its own `Q39-LEG-GUARD-BEGIN/END` +markers, lines 416–474 / 2,207 B, run against a **scratch** watched file): a clean leg passes and counts; +a leg whose subprocess writes the watched file exits **2**, restores bytes **and** mtime, and prints both +digests (`aef3121c6c5097a3…` before, `8ddb948bf967125e…` after). + +**Three instrument defects of mine, logged first.** (1) A three-tuple live state including **atime** made +every leg refuse — reading the file updates atime on this filesystem — so the state is `(bytes, +mtime_ns)`. (2) The Q39 summary line on stderr landed concatenated onto the last `PASS` row (stdout is +block-buffered when redirected); it is now a flushed stdout `print`. (3) Twenty empty `helm-q39-*` probe +directories were left by the runs that crashed with `NameError` during the build (four `_q38_*` names had +been renamed to `_q39_*` in the setup block but not in the body); removed by `cleanup-temp.py` — 20 dirs, +0 kept — and the final runs leave none. + +**Live state, delta 0 across every run in this item.** `/root/.helm/data/snapshot.json` **14,039 B / +`aa842b0fd88037f8…` / mtime_ns `1791479579475780303`**. Deployed bin unmoved: `helm-collect.py` +`ee0b36a82f23fcf3…`, `helm-selftest.py` `7ed1bca8f9da624f…`, `helm-render.py` `5c67473d89283c3d…`. +Public page `https://alexanderwhitestone.com/helm/` **200 / 9,865 B / `427b7b54c69b18d2…`** at +2026-10-12T09:29:03Z. `/root/stackchain-legends-build/build_blocks.py` still `97fedb5dd5e85d2b…`, nothing +under that tree modified since 2026-10-11 08:00Z. `decisions/2026-09-26.md` `40b7cc14a829…` and +`inbox/seed-2026-09-26.json` `e4429147bc1b…` unchanged. No another-party file edited; no secret printed. + +## Blocked + +Nothing. One limit named rather than buried: repair 5 is **detect-and-restore, not prevention** — the +legs still run the original subject. Running the legs against a rewritten copy is the stronger form and +is not built. + +## Next + +1. **The stronger form, if you want it:** run the legs against a copy of the subject with every + destination rewritten, so an admitted writer can never execute against live. Measured effort: the + rewrite already exists in the probe path; the legs' `COLLECTOR` references would point at it. +2. **Q37 is open and unclaimed** — can this pipe distinguish its own commits from a counterparty's pushes + under its credential? It is the only queue item with no human in front of it, and it is the pipe's + tooling, not mine. I am not taking it without your nod, because it is Timmy's lane. +3. No new queue item opened: the Helm lane's next unit is the adoption decision, not capacity. + +## Ask + +Alexander — the adoption call Run 4 asked for is now sharper, because the gap is measured rather than +argued: **the gate in production today (`7ed1bca8…`) admits `n1` and `n2`**, the two classes that wrote +the live snapshot on 2026-10-12, while the fixed gate (`9b09f328…`) refuses both before any leg. The pair +is collector `4e45021d…` + gate `9b09f328…`, a one-file `cp` plus the collector. Say **adopt**, **hold**, +or **build the stronger form first** — my recommendation is adopt-with-the-stronger-form as the next +unit, since the fixed gate already removes the two measured holes and the stronger form closes the last +one. Nothing else is blocking. + +— Merlin diff --git a/recoveries/helm-destination-guard-fix.md b/recoveries/helm-destination-guard-fix.md new file mode 100644 index 0000000..fd92a2c --- /dev/null +++ b/recoveries/helm-destination-guard-fix.md @@ -0,0 +1,165 @@ +# Q39 — the destination guard's admission admits a writer, and its legs run the unrewritten subject + +**Merlin, 2026-10-12, this host. Scratch tree `/root/crucible/q39/`. Nothing deployed, nothing published, no +`cp` into `/root/.helm/bin/`, `decisions/` and `inbox/` untouched.** + +Q38 delivered a behavioural destination guard and the pipe falsified its protective claim the same day +(`replies/2026-10-12-to-merlin-21.md`, corrections C22/C23). This item is the repair. The fixed gate is +`/root/crucible/q39/helm-selftest.py`, **1,412 lines / 77,704 B / sha256 +`9b09f3283008e695bb0634bc2d5492f2c24067b9eeb9ee2100099b7cbfcd1429`**, a **+309 / −38** unified diff against +Q38's gate (`74aba348…`, 1,141 lines / 64,454 B); `DIFF.txt` 408 lines / sha256 `b8adc9126397f61a…`. The +subject collector is byte-identical to the Q34/Q35/Q38 pair (`4e45021d…`, 720 lines / 29,954 B). + +## 1 What was broken, restated from the falsification + +| # | Defect | Mechanism | Consequence measured by the pipe | +|---|---|---|---| +| C22 | the probe injected `SNAPSHOT_PATH = Path()` and `Path` lives in the **gate's** namespace, not the subject's | a subject importing `pathlib` without the name raises `NameError` inside the probe → rc=1, nothing written, live delta 0 → **admitted** | a writer reached the legs | +| C23 | only the **first** `SNAPSHOT_PATH =` line was rewritten, and the legs run the **original** subject | the probe tested a destination the subject does not use; the legs then executed the subject against its live default | 2026-10-12T07:18Z: the live snapshot became `{}` (2 B / `44136fa3…`) inside the gate's own run, while the gate printed `live delta 0` | + +## 2 The five repairs, with line numbers in the fixed gate + +1. **The probe is import-independent** (`:330` rewrite loop, `:333–338` preamble). The probe file is now the + subject's source with an injected `from pathlib import Path` **above** the body, so the rewritten line + cannot `NameError` on a subject that never imported the name. This is the C22 mechanism removed rather + than detected. +2. **Every** `SNAPSHOT_PATH =` assignment is rewritten, not the first (`:330` loops over `_q39_assign`). + A decoy first assignment can no longer hide the real destination from the probe. +3. **A crash is not a refusal** (`:356–358`, condition at `:380–381`). A probe whose stderr carries + `Traceback (most recent call last)` is refused. Q38's own injection was the class that produced exactly + that stderr, which is why this condition and repair 1 are both needed. +4. **The destination-resolution scan** (`:225–302`, refusal at `:304–320`). Every `SNAPSHOT_PATH =` + assignment is parsed with **ast** (not regex) and every **absolute** path literal in the subject's + source is resolved — cumulatively onto three bases (the subject's directory, the live data root's + parent, the cwd) — and refused if it lands inside the live data root. Bare string statements + (docstrings) are excluded by a walk that does not descend into `Expr` nodes (`:236–249`), which is why + the deployed collector is admitted: its live path is **composed** (`HELM_HOME / "data" / + "snapshot.json"`, `helm-collect.py:29–30`) and never written as one absolute literal. The report is + printed on both the refusal and the green path. + Measured on the gated collector, every green run: + `assign line 30: SNAPSHOT_PATH <- 'data' / 'snapshot.json' -> resolves inside the live data root: + /root/.helm/data/snapshot.json` +5. **Every leg is watched** (`:415–474`, markers `# Q39-LEG-GUARD-BEGIN/END`; import watch at + `:1047–1049`; summary at `:1407`). Live **bytes and mtime** are read around every `subprocess.run` + the gate spawns and around the subject's in-process import; a change dies the run with **exit 2**, + prints both digests, and **restores the bytes it read** (plus the mtime). Q38 read live once before the + legs and once at the end and summarised the difference — C23 is that a mid-run write is invisible to + that. + +Two self-corrections made while building this, logged rather than hidden: + +- the first `_q39_live_state()` returned `(bytes, mtime_ns, atime_ns)` and **every leg failed**: reading + the file updates its atime on this filesystem, so a three-tuple comparison flags a clean leg as a + change. The state is `(bytes, mtime_ns)` (`:431–438`); the requirement is live bytes **and** mtime, and + mtime is the stamp a writer moves. +- the Q39 summary line was first written to stderr and landed concatenated onto the last `PASS` line when + both streams go to one file (stdout is block-buffered, stderr is not). It is now a `print` after + `sys.stdout.flush()` (`:1405–1411`), and the `Q39 OK` line flushes stdout first (`:405`). + +## 3 The negative-control suite — five refusals, each re-run under Q38 as the discriminator + +`python3 /root/crucible/q39/q39-negatives.py` → `RESULT: ALL NEGATIVE CONTROLS REFUSED AS EXPECTED`, +`Q39-NEGATIVES.json` written at 2026-10-12T09:28:09Z, gate digest inside it matching the file above. +The suite resets the fixtures' destination files first, so "created" is measured per run, not inherited. + +| fixture | Q39 exit | Q39 condition named | Q38 exit | Q38 admitted? | side-effect files created (Q39 / Q38) | +|---|---|---|---|---|---| +| `n1-incident-shape` (the incident's shape: writes at import, imports `pathlib` not `Path`) | 2 | `the probe did not refuse (rc=0); the probe wrote the sentinel (2 B)` | **0** | **yes** | `[]` / `['n1-dest']` | +| `n2-traceback` (raises at import) | 2 | `the probe died with a traceback, which is a crash, not a refusal` | 1 (crashed at the import) | **yes** | `[]` / `[]` | +| `n3-decoy` (first assignment a decoy, real destination on the next line) | 2 | `the probe did not refuse (rc=0); the probe wrote the sentinel (2 B)` | 2 | no | `[]` / `['n3-real']` | +| `n4-comment-only` (markers in the docstring, no rule) | 2 | `the probe did not refuse (rc=0); the probe wrote the sentinel (2 B)` | 2 | no | `[]` / `[]` | +| `n5-live-literal` (refuses its default properly, names the live path as an absolute literal) | 2 | `names the live destination as an absolute literal` | 2 | **yes** | `[]` / `[]` | + +Every Q39 run: **stdout 0 B, live delta 0** (bytes and mtime), no `helm-q39-*` probe directory left +behind. Two of the five are the C22/C23 classes Q38 got wrong, and both are admitted by Q38 to this day — +`n1` is admitted **and** its in-process import writes the fixture's destination inside the gate's own run, +`n3`'s probe writes the decoy's **real** destination inside the probe, which is the containment failure +reply 21 measured. Under Q39 neither file is created. + +## 4 The leg watch, measured both ways + +`python3 /root/crucible/q39/q39-legguard-test.py` extracts the shipped guard **by its own +`# Q39-LEG-GUARD-BEGIN/END` markers** (lines 416–474, 2,207 B) and runs it against a **scratch** watched +file, because the live snapshot cannot be clobbered to prove a clobber is caught: + +``` +clean leg: rc=0 watch_calls 0 -> 1 bytes_unchanged=True +writing leg: exit_code=2 restored=True mtime_ns 1791797218872835164 (original 1791797218872835164) + refusal: Q39 REFUSAL: a leg's subprocess (… open('/tmp/q39-legguard-669_7xhk/snapshot.json', + 'w').write('CLOBBERED')) changed the live snapshot - sha256 aef3121c6c5097a3… before, + sha256 8ddb948bf967125e… after; the before-bytes were restored and the run is refused. +RESULT: LEG WATCH MEASURED BOTH WAYS +``` + +## 5 The green pair — 5 consecutive invocations + +`bash /root/crucible/q39/run-green.sh 5`, `HELM_COLLECTOR=/root/crucible/q39/helm-collect.py`: + +``` +run 1..5 rc=0 live_delta=0 pass_lines=164 legs_watched=17 q39_ok_lines=1 + live=aa842b0fd88037f83115b4a54d617f27666f4de06ef84f9a64c8fd0a5f04656d + mtime=2026-10-08 17:12:59.475780303 +0000 +``` + +Each log carries `Q39 OK: … (probe rc=2, sentinel written=False, other probe files=0, no traceback, live +delta 0)`, the destination-resolution report, `helm-selftest: 164 passed, 0 failed`, `VERDICT: ALL GATES +PASS`, and `Q39: 17 legs watched … delta 0 on each`. Log digests: + +``` +f0bfaca19d5d2a56c42e8bd3b7c112c076544dfe90e9d874b395a29219604be5 green-run-1.log +a13a184de21a45a72ed4ec2409e5aa969dc18591752b82d5fac256a1cfad99a0 green-run-2.log +e305e9f7dfd5fdc9d8a34bc10ccbc4a3d3f0f0c1feb77863b376a66437771b70 green-run-3.log +0b2343b6a2fb64bd5078a7ecef5fcfbdfb5e9e0f9271cb4cdc11626f5a7e9d8a green-run-4.log +eaaf90e14ba8577749c718d5430ec2cd969a1bad07839bfc3826baded16efad9 green-run-5.log +``` + +**17** legs is the count of `subprocess.run` calls the gate makes on the green path (10 in the Q38 gate's +own text plus G7's renderer runs), each watched individually — the number is printed so the watch is not +vacuous. + +## 6 Live state and what was not touched + +- `/root/.helm/data/snapshot.json` **14,039 B / sha256 `aa842b0fd88037f8…` / mtime_ns + `1791479579475780303`** before and after every run in this item — 5 green, 5 fixtures × 2 gates, the + leg-watch test, and the cleanup. **Delta 0 throughout.** +- `/root/.helm/bin/` unmoved: `helm-collect.py` `ee0b36a8…` (the deployed pre-Q35 collector — it carries + no destination rule, which is why the gate's default `HELM_COLLECTOR` is refused at Q35 and the gated + pair is named explicitly), `helm-selftest.py` `7ed1bca8…`, `helm-render.py` `5c67473d…`, + `helm-validate.py` `12570786…`, `summarize.py` `73869998…`. **No `cp` was made; adoption stays + Alexander's.** The adoption pair is now collector `4e45021d…` + gate `9b09f328…`. +- Public page `https://alexanderwhitestone.com/helm/` **200 / 9,865 B / sha256 + `427b7b54c69b18d270288910fc810732463129f48ec4bdd7dbc75cdb22997ce4`** at 2026-10-12T09:29:03Z — the + Q38 record's figure. +- `/root/stackchain-legends-build/build_blocks.py` still `97fedb5dd5e85d2b…`, nothing under that tree + modified since 2026-10-11 08:00Z (`find … -newermt` → 0 files). +- `decisions/2026-09-26.md` `40b7cc14a829…` and `inbox/seed-2026-09-26.json` `e4429147bc1b…` unchanged and + not opened for content. No other party's file edited. No secret printed. + +## 7 Limits, stated + +- **The static scan is literal-based.** A subject that composes the live path from a variable the scan + cannot see, or reads it from the environment, is not caught by repair 4; the behavioural probe and the + leg watch are what cover it, and the leg watch is detection-with-restore, not prevention. +- **Repair 4 refuses a legitimate shape.** A subject that names the live snapshot as one absolute literal + — even one that guards it properly — is refused. That is the conservative reading and it is a design + decision, not a measurement: the gate cannot redirect a path that is not the module constant. +- **The legs still run the original subject.** Repair 5 makes a live write fatal to the run and restores + it; it does not stop the subject from executing with its live default. Running the legs against a + rewritten copy would be the stronger form and is not built. +- **The probe substitution still touches one module constant.** A subject that resolves its destination + by another route is not covered by the rewrite (Q38's limit, unchanged); the live-delta assertion + catches an actual write only after the fact. +- **The timeout branch is coded and not exercised** (Q38's limit, unchanged). +- **The guard protects the gate's legs, not a direct CLI run** of a pre-gate collector (Q35's limit, + unchanged). +- 5 fixtures are the item's own four classes plus the decoy containment case; they are not a census of + subject shapes. Byte counts and digests are dated readings from this host, 2026-10-12 ~08:5x–09:3xZ. + +## 8 What is left for Alexander + +The same gate as Q38: **do not `cp` `9b09f328…` into `/root/.helm/bin/` without a decision**, and note +that the shipped state is now measurably the weaker one — pre-Q39, a subject that carries no destination +rule is refused at Q35 but a subject that *documents* one is admitted and executed; with `9b09f328…` the +four classes above die before any leg. The publish precondition the queue already carries stands: +**adopt the fixed gate into the shared tree first, then publish** — and it is a one-file change plus the +collector, not a build change.