Q35 DONE: the destination-rule guard is a precondition, not a report (recoveries/helm-precondition-gate.md, scratch /root/crucible/q35/).
The subject collector named by HELM_COLLECTOR is checked for the rule (--deploy and SNAPSHOT_PATH.resolve() in its source) at helm-selftest.py:96-114 BEFORE the subject is imported (:679) and before G1, and refused with exit 2, a stderr message naming the subject and the missing rule, and the live snapshot read before/after the refusal. Gate +61/-2 (985 -> 1044 lines, 45e53187 -> 643e5387); collector byte-identical to the Q34 pair (4e45021d); the precondition registers no check() so the suite count is unchanged at 164 and the old foot-of-suite source check is kept at :1032. Seven consecutive green invocations (164 passed / 0 failed each, log digests in the deliverable), live snapshot delta 0 across every leg (aa842b0f / 14039 B / mtime_ns 1791479579475780303). Discriminator both sides in one run pair: deployed pre-gate collector as subject -> exit 2, stdout 0 bytes (e3b0c442, no summary and no PASS line, which is how "no leg executed" is measured), live delta 0; missing subject refused the same way, not crashed. Mechanism probe from this tree (q35-mechanism.py, mech-probe.log): default destination rebound to scratch, bare main() -- deployed ee0b36a8 rc=0 no refusal, 18523 B written (9331b218, schema helm.snapshot/1); pair 4e45021d rc=2, refusal naming the path and --deploy, nothing written. Limits stated: the gate run bare now refuses itself (default subject is the pre-gate deployed collector; the section-4 cp is what makes the shipped gate green, Alexander's); the precondition reads a source marker, not behavioral proof; the rule protects the gate's legs, not a direct CLI run. Nothing deployed; decisions/ and inbox/ untouched.
This commit is contained in:
1 parent
7e306dc2cd
commit
dd0bc69061
3 files changed
+206
No files matched your search
@@ -4678,3 +4678,32 @@ both sides in one run pair (deployed collector as subject → refused, no leg ex
|
|||||||
readings (sha256 and mtime_ns), and the mechanism probe repeated from the pipe's tree. **Out of scope:** the §4
|
readings (sha256 and mtime_ns), and the mechanism probe repeated from the pipe's tree. **Out of scope:** the §4
|
||||||
`cp` into `/root/.helm/bin/` — the shipped gate travelling with the collector is still Alexander's, and this item
|
`cp` into `/root/.helm/bin/` — the shipped gate travelling with the collector is still Alexander's, and this item
|
||||||
does not move it.
|
does not move it.
|
||||||
|
|
||||||
|
**Status: DONE — `recoveries/helm-precondition-gate.md` (8,261 B / sha256
|
||||||
|
`a5d3ad6af205f3c1c8caf4f4baa2ee01548b5a89697ff5478e3fc52a9a61656b`), Merlin, 2026-10-12T02:10–02:36Z, this host.**
|
||||||
|
- The guard is now a **precondition**: the subject collector named by `HELM_COLLECTOR` is checked for the rule
|
||||||
|
(`--deploy` and `SNAPSHOT_PATH.resolve()` in its source) at `helm-selftest.py:96–114`, **before the subject is
|
||||||
|
imported** (`_load_collector` at `:679`) and before G1; a subject without the rule is refused with **exit 2**, a
|
||||||
|
stderr message naming the subject and the missing rule, and the live snapshot read before/after the refusal.
|
||||||
|
Gate moved **+61/−2** (985 → 1,044 lines), sha256 `45e53187…` → `643e5387…`; the collector is byte-identical to
|
||||||
|
the Q34 pair's (`4e45021d…`). The old foot-of-suite source check is **kept** at `:1032` (it passes on every green
|
||||||
|
run, implied by the precondition), so the suite count is unchanged: the precondition registers no `check()`.
|
||||||
|
- **Green pair, 7 consecutive invocations**, exit 0 each, `164 passed, 0 failed` / `VERDICT: ALL GATES PASS`
|
||||||
|
(log digests in the deliverable). Live snapshot **delta 0 across every leg** — sha256
|
||||||
|
`aa842b0fd88037f83115b4a54d617f27666f4de06ef84f9a64c8fd0a5f04656d` / mtime_ns `1791479579475780303` /
|
||||||
|
14,039 B throughout.
|
||||||
|
- **Discriminator, both sides in one run pair.** Deployed collector as subject (`HELM_COLLECTOR=/root/.helm/bin/
|
||||||
|
helm-collect.py`): **exit 2, stdout 0 bytes** (the empty-file digest `e3b0c442…` — no `helm-selftest:` summary,
|
||||||
|
no PASS line, which is how "no leg executed" is measured, since the report block is at the suite's foot), live
|
||||||
|
delta 0. A missing subject is refused the same way, not crashed. Pair as subject: unchanged green, above.
|
||||||
|
- **Mechanism probe from the pipe's tree** (`q35-mechanism.py`, `mech-probe.log`): each collector imported, its
|
||||||
|
default destination (`SNAPSHOT_PATH`) rebound to scratch, `main()` called bare — deployed `ee0b36a8…` **rc=0,
|
||||||
|
no refusal, 18,523 B written** (sha256 `9331b218…`, schema `helm.snapshot/1`); pair `4e45021d…` **rc=2, refusal
|
||||||
|
naming the path and `--deploy`, nothing written**. Rebinding does not disable the rule — it keys on the module's
|
||||||
|
own `SNAPSHOT_PATH` — it only removes the blast radius.
|
||||||
|
- **Limits stated in the deliverable:** the gate run bare now refuses itself (the default subject is the pre-gate
|
||||||
|
deployed collector — the §4 `cp` is what makes the shipped gate green, still Alexander's); the precondition
|
||||||
|
reads a source marker, not behavioral proof (the probe is the behavioral half; sandboxing an untrusted import
|
||||||
|
is the very thing the precondition prevents); and the rule protects the gate's legs, not a direct CLI run of a
|
||||||
|
pre-gate collector. Nothing deployed; `decisions/` and `inbox/` untouched.
|
||||||
|
|
||||||
@@ -116,3 +116,72 @@ no secret is printed in this check-in or in the files it lands — the long stri
|
|||||||
names, snapshot JSON keys, file paths and sha256 digests.
|
names, snapshot JSON keys, file paths and sha256 digests.
|
||||||
|
|
||||||
— Merlin
|
— Merlin
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Run 2 — Q35: the destination-rule guard is now a precondition
|
||||||
|
|
||||||
|
Claimed and finished **Q35** (Helm lane, opened by the pipe this day after Q34 closed) — the twice-fired
|
||||||
|
class from Q34's incident, made mechanical at the gate instead of at the operator. Deliverable:
|
||||||
|
`recoveries/helm-precondition-gate.md` (**8,261 B / sha256
|
||||||
|
`a5d3ad6af205f3c1c8caf4f4baa2ee01548b5a89697ff5478e3fc52a9a61656b`**, this host). Scratch tree
|
||||||
|
`/root/crucible/q35/`; nothing deployed; the §4 `cp` is untouched.
|
||||||
|
|
||||||
|
**What changed.** The gate's source check — *"the collector the gate runs carries the destination rule"* —
|
||||||
|
used to sit at the **foot of the suite**, after every leg that could execute the subject collector. Q34's
|
||||||
|
incident is what that costs: the bare `--skip-tests` refusal leg executed an ungated subject and wrote the
|
||||||
|
live snapshot, and the check red-flagged it only afterwards. The check is now a **precondition** at
|
||||||
|
`helm-selftest.py:96–114`: the subject named by `HELM_COLLECTOR` is verified to carry the rule
|
||||||
|
(`--deploy` and `SNAPSHOT_PATH.resolve()` in its source) **before the subject is imported** (`:679`) and
|
||||||
|
before G1, and refused with **exit 2** and a stderr message naming the subject and the missing rule, with
|
||||||
|
the live snapshot read before and after the refusal. Gate moved **+61/−2** (985 → 1,044 lines), sha256
|
||||||
|
`45e53187…` → `643e53874e42f7ae58160c06c47eb07846725eae397104d9265964506bfcd0c9`; the collector is
|
||||||
|
byte-identical to Q34's (`4e45021d…`). The precondition registers **no `check()`**, so the suite count is
|
||||||
|
unchanged — the old foot-of-suite check is kept at `:1032` and passes on every green run.
|
||||||
|
|
||||||
|
**Green pair, 7 consecutive invocations**, exit 0 each, `helm-selftest: 164 passed, 0 failed` /
|
||||||
|
`VERDICT: ALL GATES PASS` (`HELM_COLLECTOR=/root/crucible/q35/helm-collect.py python3 helm-selftest.py`;
|
||||||
|
log digests `32faa0e9…` / `bfa6d09e…` / `8bd47387…` / `62756176…` / `1c9639ef…` / `f9992981…` / `e5a2c5fd…`).
|
||||||
|
|
||||||
|
**Discriminator, both sides in one run pair.** Deployed collector as subject
|
||||||
|
(`HELM_COLLECTOR=/root/.helm/bin/helm-collect.py`, pre-gate `ee0b36a8…`): **exit 2, stdout 0 bytes**
|
||||||
|
(`q35-refuse-deployed.log` sha256 `e3b0c442…`, the empty-file digest — no `helm-selftest:` summary and no
|
||||||
|
PASS line exists, which is how "no leg executed" is measured, since the report block is at the suite's
|
||||||
|
foot), stderr the refusal with live-snapshot readings, **live snapshot delta 0** (sha256
|
||||||
|
`aa842b0fd88037f83115b4a54d617f27666f4de06ef84f9a64c8fd0a5f04656d` / mtime_ns `1791479579475780303` /
|
||||||
|
14,039 B — identical before and after, and across all seven green runs). A missing subject is refused the
|
||||||
|
same way, not crashed (`q35-refuse-missing.err`, sha256 `90038616…`).
|
||||||
|
|
||||||
|
**Mechanism probe from the pipe's tree** (`q35-mechanism.py` / `mech-probe.log`, sha256
|
||||||
|
`31df78f6…` / `c4714d74…`): each collector imported, its default destination (`SNAPSHOT_PATH`) rebound to
|
||||||
|
scratch, `main()` called bare — deployed `ee0b36a8…` **rc=0, no refusal, 18,523 B written** (sha256
|
||||||
|
`9331b218…`, schema `helm.snapshot/1`); pair `4e45021d…` **rc=2, refusal naming the path and `--deploy`,
|
||||||
|
nothing written**. Rebinding does not disable the rule — it keys on the module's own `SNAPSHOT_PATH` — it
|
||||||
|
only removes the blast radius.
|
||||||
|
|
||||||
|
**Limits stated, not buried:** the gate run bare now refuses itself, because the default subject is the
|
||||||
|
pre-gate deployed collector — that is the item working as specified, and the §4 `cp` is what makes the
|
||||||
|
shipped gate green; the precondition reads a source marker, not behavioral proof (the probe is the
|
||||||
|
behavioral half, and sandboxing an untrusted import is the very thing the precondition prevents); and the
|
||||||
|
rule protects the gate's legs, not a direct CLI run of a pre-gate collector.
|
||||||
|
|
||||||
|
## Blocked
|
||||||
|
|
||||||
|
Nothing in my lane. Same three gates as Run 1 (Q13/Q19 on Alexander, Q9 on the audio handover, Q20 is
|
||||||
|
zephyrus's).
|
||||||
|
|
||||||
|
## Next
|
||||||
|
|
||||||
|
Q7's never-dry obligation: the unclaimed executable set is **zero** after this run — Q30–Q35 are closed,
|
||||||
|
Q9/Q13/Q19/Q21 wait on humans or the counterparty, Q20 is zephyrus's active sweep. The next item I can
|
||||||
|
see is a Helm-lane one, and it is the §4 adoption itself: the gate now green-refuses its own default
|
||||||
|
subject, so the shipped pair is a two-file `cp` (collector `4e45021d…` + gate `643e5387…`) that makes
|
||||||
|
`python3 helm-selftest.py` green with no environment. That is Alexander's move; I have not touched
|
||||||
|
`/root/.helm/bin/`. If he approves, the natural follow-on item is a verification run of the *deployed*
|
||||||
|
pair — same discriminator, one host change.
|
||||||
|
|
||||||
|
## Ask
|
||||||
|
|
||||||
|
Alexander: unchanged from Run 1 — nothing blocking. The two standing items are the §4 adoption (now a
|
||||||
|
cleaner ask: the pair travels together or the shipped gate stays self-refusing) and the `manifest_match`
|
||||||
|
pin decision (stale since 2026-10-08; a static render cannot say so).
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
# The gate refuses an ungated subject before any leg runs
|
||||||
|
|
||||||
|
**Q35 — Helm-lane gate precondition. Author: Merlin (`merlin@agents.invalid`). Clock: 2026-10-12T02:10–02:36Z, this host.**
|
||||||
|
**Scratch tree: `/root/crucible/q35/`. Nothing deployed; the §4 adoption `cp` into `/root/.helm/bin/` is out of scope and untouched.**
|
||||||
|
|
||||||
|
The twice-fired class, made mechanical at the gate. Two live-snapshot writes this day came from the same
|
||||||
|
shape — a negative-control leg of the gate running a subject collector that carries **no destination rule
|
||||||
|
of its own**, so the leg's bare `--skip-tests` call executed it and it wrote its default destination
|
||||||
|
(`2026-10-11T13:28Z` Q31; `2026-10-12T00:26:01.933369921Z` Q34, restored byte-exact). The rule was in
|
||||||
|
the skill and in the gate's own source check, but that check ("the collector the gate runs carries the
|
||||||
|
destination rule (source guard)") sat at the **foot of the suite** — after every leg that could execute
|
||||||
|
the collector. It red-flagged the subject *after* the damage. Q35 hoists the rule to a **precondition**:
|
||||||
|
the subject named by `HELM_COLLECTOR` is checked **before it is imported** and before G1, and refused
|
||||||
|
with exit 2 and a message naming the subject and the missing rule. No leg runs.
|
||||||
|
|
||||||
|
## What moved, and where the guard now lives
|
||||||
|
|
||||||
|
The collector did not move: `/root/crucible/q35/helm-collect.py` is byte-identical to the Q34 pair's
|
||||||
|
(**sha256 `4e45021d80395d3f03fc58d7e99610e3c1c0d2fcf9c85f1db50b16cfd2043c6c`**, 720 lines). The gate moved
|
||||||
|
**+61 / −2** (`git diff --no-index --numstat helm-selftest-q34-before.py helm-selftest.py` → `61 2`),
|
||||||
|
985 → 1,044 lines, **sha256 `45e53187c01398df7a0edcbf24a08b7d91255eda0cfe93b9cb01e8398f0d7091` →
|
||||||
|
`643e53874e42f7ae58160c06c47eb07846725eae397104d9265964506bfcd0c9`**.
|
||||||
|
|
||||||
|
The precondition's code path, in `helm-selftest.py`:
|
||||||
|
|
||||||
|
| line | what |
|
||||||
|
|---|---|
|
||||||
|
| `:96` | `COLLECTOR = Path(os.environ.get("HELM_COLLECTOR", "/root/.helm/bin/helm-collect.py"))` — resolved **once**, at the top |
|
||||||
|
| `:97–99` | live snapshot path from the deployed module (`hc.SNAPSHOT_PATH`, the module the gate has always imported at `:42`), source text read |
|
||||||
|
| `:100–114` | the rule check: `"--deploy" in src and "SNAPSHOT_PATH.resolve()" in src`; on failure the live snapshot is read before and after, the refusal (subject, missing rule, both readings, delta) is written to **stderr**, and `sys.exit(2)` |
|
||||||
|
| `:679` | `hcg = _load_collector(COLLECTOR)` — the subject is imported **only after** the precondition has passed |
|
||||||
|
| `:1032` | the old foot-of-suite check is **kept** as the in-suite statement of the same rule (`PASS G8 the collector the gate runs carries the destination rule (source guard)`, measured at `q35-gate-run-7.log:176`). On any green run it is implied by the precondition having passed |
|
||||||
|
|
||||||
|
The precondition registers **no `check()`**: a refusal fails the *run*, not a suite result, so the green
|
||||||
|
pair still reports exactly **164 passed / 0 failed** — unchanged from Q34, Q34's four pointer checks
|
||||||
|
included (measured at `q35-gate-run-7.log:163-166`).
|
||||||
|
|
||||||
|
## The mechanism probe — the rule measured on both subjects, not read
|
||||||
|
|
||||||
|
`q35-mechanism.py` (sha256 `31df78f6e9bcf755000cd7ed47bf92e6f097e5aef7c63a2aaf2a005ad53dca11`), run from
|
||||||
|
this tree (`mech-probe.log`, sha256 `c4714d743a388ad3e1a52049a3347af8e939aca67db9f363bbef83125ab26dba`):
|
||||||
|
each collector is imported, its `SNAPSHOT_PATH` (the default destination) **rebound to scratch**, and
|
||||||
|
`main()` called bare with `--skip-tests`. Rebinding does not disable the rule — the refusal keys on the
|
||||||
|
module's own `SNAPSHOT_PATH`, whatever path it holds — it only removes the blast radius.
|
||||||
|
|
||||||
|
| subject | rc | scratch default written | stderr | live snapshot delta |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| deployed, pre-gate (`/root/.helm/bin/helm-collect.py`, `ee0b36a8…`, `--deploy` ×0, `SNAPSHOT_PATH.resolve()` ×0) | **0** | **yes — 18,523 B, sha256 `9331b21860e5c9a481e8a6b8e6b6edbb3040832262a717a3349acff7b2f44211`**, schema `helm.snapshot/1` | none — `wrote …` on stdout | 0 (`aa842b0f…`, 14,039 B) |
|
||||||
|
| pair, Q33/Q34 (`helm-collect.py`, `4e45021d…`) | **2** | **no** | `refusing to write the live snapshot … without --deploy; pass an explicit --out <scratch path> instead` | 0 |
|
||||||
|
|
||||||
|
The discriminator **reproduces**: the deployed collector writes its default with no refusal; the pair
|
||||||
|
refuses its default. That is the behavioral half of the source marker the precondition reads.
|
||||||
|
|
||||||
|
## The green pair — 7 consecutive invocations
|
||||||
|
|
||||||
|
`HELM_COLLECTOR=/root/crucible/q35/helm-collect.py python3 helm-selftest.py`, exit 0 every time,
|
||||||
|
`helm-selftest: 164 passed, 0 failed` / `VERDICT: ALL GATES PASS`:
|
||||||
|
|
||||||
|
| run | log | sha256 of log |
|
||||||
|
|---|---|---|
|
||||||
|
| 1 | `q35-gate-run-1.log` | `32faa0e91a8da5a4bf6a3be8a6be60e9ac92aa8750dbbbb636fe02d3b2c0e0a1` |
|
||||||
|
| 2 | `q35-gate-run-2.log` | `bfa6d09e7c510d562d1b0d370e30092144e25de215a7fb50ebb9bc05dc2aadf3` |
|
||||||
|
| 3 | `q35-gate-run-3.log` | `8bd47387cba01b5f3429637ea48eaa8b6cc3fefc72e49a65570beb071f14eeb1` |
|
||||||
|
| 4 | `q35-gate-run-4.log` | `627561761ccb34a1d737eb2fb6dcc88dc160984eafe9db72bcd51b3d8cebf33c` |
|
||||||
|
| 5 | `q35-gate-run-5.log` | `1c9639efc502d4a7ccb4e0621bcaa5fe3ff763c5f588fb3f553eaa49d40e6bb2` |
|
||||||
|
| 6 | `q35-gate-run-6.log` | `f99929816ae3b28126bef11b8a9f29e5e35dc6f72a3a03439ced6b168d2d1317` |
|
||||||
|
| 7 | `q35-gate-run-7.log` | `e5a2c5fd484e874d954feee1fecb22da2ed6eafd2ff05be0bfa402fda6b911ca` |
|
||||||
|
|
||||||
|
Log digests differ run to run only by clocks and durations; the verdict line is identical in all seven.
|
||||||
|
|
||||||
|
## The refusal side of the discriminator — both subjects, one run pair
|
||||||
|
|
||||||
|
`HELM_COLLECTOR=/root/.helm/bin/helm-collect.py python3 helm-selftest.py`
|
||||||
|
(`q35-refuse-deployed.err`, sha256 `2aaa36c1f8277e900268ff4c5ff1d8ebda8b5fce16d9dd15eaa9521e1c66ca96`):
|
||||||
|
|
||||||
|
- **exit 2**, and **stdout is 0 bytes** (`q35-refuse-deployed.log`, sha256
|
||||||
|
`e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855` — the empty-file digest). No
|
||||||
|
`helm-selftest: N passed` summary and no `PASS` line exists, which is what "no leg executed" is
|
||||||
|
measured by: the report block is at the foot of the suite, so a run that reaches it prints.
|
||||||
|
- stderr, verbatim: `Q35 REFUSAL: the subject collector /root/.helm/bin/helm-collect.py carries no
|
||||||
|
destination rule (missing '--deploy' and/or 'SNAPSHOT_PATH.resolve()'), so no leg is executed.` plus
|
||||||
|
the live-snapshot readings line.
|
||||||
|
- Live snapshot across the refusal: **sha256 `aa842b0fd88037f83115b4a54d617f27666f4de06ef84f9a64c8fd0a5f04656d`
|
||||||
|
/ mtime_ns `1791479579475780303` before and after — delta 0**, the same reading held across all seven
|
||||||
|
green runs and both probe legs (14,039 B throughout).
|
||||||
|
|
||||||
|
A missing subject is refused, not crashed: `HELM_COLLECTOR=/root/crucible/q35/no-such-collector.py`
|
||||||
|
→ exit 2, same refusal shape (`q35-refuse-missing.err`, sha256
|
||||||
|
`900386166ac630ce957afb6c805b797170963572fd4ebc64841d8d0b955a9f88`), stdout 0 bytes, live delta 0.
|
||||||
|
|
||||||
|
## Limits, stated
|
||||||
|
|
||||||
|
- **The gate run bare now refuses itself.** The default `HELM_COLLECTOR` is the deployed collector,
|
||||||
|
which is pre-gate — so `python3 helm-selftest.py` with no environment exits 2. That is the item
|
||||||
|
working as specified, and it is why the green runs above name the subject explicitly. The §4 `cp`
|
||||||
|
(collector + this gate as a pair) would make the shipped gate green; that move is Alexander's and is
|
||||||
|
unchanged by this run.
|
||||||
|
- **The precondition reads a source marker, not a proof of behavior.** `"--deploy"` and
|
||||||
|
`"SNAPSHOT_PATH.resolve()"` are strong markers — the second is the rule's own comparison — but a
|
||||||
|
subject carrying both strings without the rule would pass the check. The mechanism probe above is the
|
||||||
|
behavioral half; making the marker itself behavioral (import in a sandbox, measure the refusal) is
|
||||||
|
not attempted here because importing an untrusted subject is the very thing the precondition exists
|
||||||
|
to prevent.
|
||||||
|
- **The rule protects the gate's legs, not a direct CLI run.** A human or script running a pre-gate
|
||||||
|
collector bare still overwrites the live snapshot. The gate refuses to *use* such a subject; nothing
|
||||||
|
in this change stops a collector from being executed outside the gate.
|
||||||
|
- The live-snapshot readings above are this host's, at the clocks stated; every figure was re-measured
|
||||||
|
in the same session as the runs it describes.
|
||||||
Reference in new issue
Block a user