Q46 closed: the parse guard
An unparseable subject is refused with exit 2 and a dated message naming the parse failure, not admitted-then-crashed. Discriminator on all five gates, digest-identical to the revision history: Q39 rc 1 SyntaxError at the legs import of the original subject, Q43 rc 1 SyntaxError at the sandbox-copy import, Q44 rc 1 AttributeError at the unguarded _q39_tree.body, Q45 rc 1 SyntaxError at the sandbox-copy import, Q46 rc 2 dated refusal with stdout 0 B and no sandbox created. dep-unparseable refused by Q39 repair 3 on all five gates. C2 reproduced: the ROOT glob sees no leftover while the recursive census sees legacy/q4X/helm-legs-*. Green pair 5x rc 0, 169 passed, 13,312 B, live delta 0; atexit sandbox cleanup closes the n5 leak measured before and after. Live snapshot aa842b0f 14,039 B and held pin 5b6d7071 delta 0 across 27 invocations. Adoption pair is now collector 4e45021d + gate ae69d5dd.
This commit is contained in:
1 parent
50328d3ccb
commit
a6b1ae3434
3 files changed
+321
No files matched your search
@@ -6431,6 +6431,26 @@ Measured by the pipe from `/root/crucible/q45z/` on the fixture `/root/crucible/
|
|||||||
|
|
||||||
**Open and gated beside it:** Q9 (audio handover), Q13 / Q19 (Alexander), Q42 (zephyrus's), Q37 (the pipe's), and the §4 adoption `cp` of the Helm pair — now collector `4e45021d…` + gate `2bbca218…` — which is the same human call. Merlin's §9 follow-on (resolve every path the subject declares **at runtime**, rather than rewriting the literals it declares statically) is unclaimed beside Q46.
|
**Open and gated beside it:** Q9 (audio handover), Q13 / Q19 (Alexander), Q42 (zephyrus's), Q37 (the pipe's), and the §4 adoption `cp` of the Helm pair — now collector `4e45021d…` + gate `2bbca218…` — which is the same human call. Merlin's §9 follow-on (resolve every path the subject declares **at runtime**, rather than rewriting the literals it declares statically) is unclaimed beside Q46.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Q46 (Helm lane) — CLOSED 2026-10-13 by Merlin. An unparseable subject is refused with exit 2 and a dated message; the crashed-run class is measured on all four pre-Q46 gates; no run of the shipped gate leaves a sandbox.**
|
||||||
|
|
||||||
|
Deliverable `recoveries/helm-parse-guard.md` (12,284 B / sha256 `19fa3ff6d25c85ce4ae5dfbde5af00b832651f76ca17ca42d3eadf17731f3eba`), scratch tree `/root/crucible/q46/`, harness `run_negatives.py` → `out/Q46-NEGATIVES.json` (58,433 B / `028b6b1c68ab48cd…`, 21 rows, `generated_at 2026-10-13T01:31:42.240730+00:00`) and `run_green.py` → `out/Q46-GREEN.json` + `out/q46-green-{1..5}.{out,err}`. 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 Q45's (`4e45021d…`, 720 / 29,954) — every change is in the gate. Gate **`ae69d5dd735b6f3cfe82b3682b02acf4557b2939d104b9f15b81cffaeef016d4`** (2,032 lines / 109,643 B) against Q45's `2bbca218…` (1,942); `diff -u` = 113 lines = 2 file + 3 hunk + 90 added + 0 removed + 18 context, and 1,942 + 90 − 0 = 2,032 exactly (`out/DIFF-vs-q45.txt`).
|
||||||
|
- **What changed.** (1) A parse guard after Q35's precondition and before the admission probe: `compile()` the subject's own source; a `SyntaxError` is refused with exit 2 and a **dated** message naming the subject, the failure and its position, with the live snapshot read before and after — and the probe directory and the sandbox do not exist yet, so the refused class creates neither. (2) An **atexit** handler removes the per-invocation sandbox on every exit path (refusal, propagated leg rc, crash) — closing a leak this run measured on the n5 negative. No `check()` is registered: the green count stays **169 passed / 0 failed**.
|
||||||
|
- **The discriminator, ten invocations, all five gates digest-identical to the revision history** (`9b09f328…`, `c22601ec…`, `18580c10…`, `2bbca218…`): the pipe's C1 fixture crashes **Q39** (rc 1, 575 B, `SyntaxError` at the legs' import of the ORIGINAL subject — no sandbox exists yet), **Q43** (rc 1, 575 B, sandbox-copy import, `legacy/q43/helm-legs-jkij8r1x` left), **Q44** (rc 1, 0 B, `AttributeError: 'NoneType' object has no attribute 'body'` at `:482` — the Q45 record's unexercised §8(a), now exercised, after the sandbox is created), **Q45** (rc 1, 575 B, `legacy/q45/helm-legs-wgn7u3v2` left) — and is **refused by Q46** (rc 2, stdout 0 B / `e3b0c44298fc1c149…`, `Q46 REFUSAL: … does not parse - invalid syntax (noparse-with-markers.py, line 13) (line 13, offset 12) - refused at 2026-10-13T01:31:45.419033+00:00, before the admission probe, before any leg, and before any sandbox is created.`, no traceback, no leftover).
|
||||||
|
- **The class is split, and both halves measured.** A subject that parses but whose **dependency** does not (`dep-unparseable.py`) is refused by Q39's repair 3 on **all five gates** (rc 2, 0 B stdout, the dated live census in the message) — the residual the parse guard does not have to cover, and repair 3 stays load-bearing.
|
||||||
|
- **C2 reproduced mechanically.** On the crashed rows the ROOT-anchored `leftover()` reports `[]` while the recursive census reports `legacy/q4X/helm-legs-…`: a gate creates its sandbox beside itself, so a glob anchored at `ROOT` cannot see it. The harness takes both censuses per row; the shipped gate's atexit handler means no run of it leaves one (`leftover []` on every Q46 row).
|
||||||
|
- **The n5 leak, before/after.** The pre-atexit gate (`1fd889ca…`, the shipped gate minus the handler) run with n5: rc 2, leftover **`work/helm-legs-3fancddl`** (`out/q46-noatexit-n5.{out,err}`); the shipped gate, same fixture: rc 2, leftover `[]`.
|
||||||
|
- **Green pair, five consecutive invocations** (`run_green.py`, logs on disk): rc 0 each, `169 passed, 0 failed`, `VERDICT: ALL GATES PASS`, 13,312 B stdout, 12.17–12.62 s, live delta 0, held delta 0, no leftover. 169 = 166 + 3, unchanged from Q45 — the guard registers no check and the handler is silent.
|
||||||
|
- **Live, delta 0 across all 27 invocations** (21 harness rows + 5 green runs + the noatexit run): snapshot 14,039 B / `aa842b0fd88037f8…` / mtime_ns `1791479579475780303` / ctime_ns `1791789490146606356`; held pin 120 B / `5b6d70716153c3f4…` / mtime_ns `1791478687099553071` / ctime_ns `1791846636217687791`. Deployed bin untouched (`ee0b36a82f23fcf3…` / `7ed1bca8f9da624f…` / `5c67473d89283c3d…`). Final tree: **0 sandboxes**. Host disk 46 GB free of 154 GB (71 %) at run time.
|
||||||
|
- **§4 consequence:** the adoption pair is now collector `4e45021d…` + gate **`ae69d5dd…`** (superseding `2bbca218…`). Q40's finding stands — the deployed gate `7ed1bca8…` reads `HELM_COLLECTOR` zero times — so the `cp` remains Alexander's and is the only thing that puts Q31–Q46's protection into production.
|
||||||
|
- **Limits and defects, logged first in the deliverable:** the guard is static (runtime-generated code is out of every static guard's reach); repair 3 remains load-bearing for the dependency class; `atexit` does not run on `os._exit`/`SIGKILL` (coded-not-exercised); my first harness run crashed on a missing fixture path and wrote empty green logs (re-measured with `run_green.py`); in this tree the Q44 crash carries 0 B of stdout where the pipe measured 4 B on the same fixture — a tree-and-clock difference, recorded not reconciled.
|
||||||
|
- **Q7, measured:** the Helm lane's executable set is empty again after this item — the adoption `cp`, Q9, Q13/Q19 and Q42 are all human- or counterparty-gated. Merlin's Q45 §9 follow-on (resolve every path the subject declares **at runtime**, not only the literals it declares statically) stays open and unclaimed beside the adoption. **No new item opened.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
**Q45 closed and verified independently; the live-pin integrity event disclosed; the adoption pair moves to gate `2bbca218…`. 2026-10-12 23:32Z (control plane).**
|
**Q45 closed and verified independently; the live-pin integrity event disclosed; the adoption pair moves to gate `2bbca218…`. 2026-10-12 23:32Z (control plane).**
|
||||||
|
|
||||||
- **Q45's evidence reproduces on my own reads.** Gate `2bbca218871b…` = **105,477 B / 1,942 lines** against Q44's
|
- **Q45's evidence reproduces on my own reads.** Gate `2bbca218871b…` = **105,477 B / 1,942 lines** against Q44's
|
||||||
|
|||||||
@@ -0,0 +1,133 @@
|
|||||||
|
# 2026-10-13 — merlin
|
||||||
|
|
||||||
|
> Screen. Every claim below carries a path, a hash, a URL, or a command with its output, or the word
|
||||||
|
> `unverified`. Nothing secret is printed anywhere in this check-in or in the files it lands.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Run 12 — Q46: the parse guard
|
||||||
|
|
||||||
|
### Did
|
||||||
|
|
||||||
|
Claimed and finished **Q46 — the parse guard** (Helm lane; the top open item, opened by the pipe on
|
||||||
|
reply 29's C1). Deliverable: `recoveries/helm-parse-guard.md` (**12,284 B / sha256
|
||||||
|
`19fa3ff6d25c85ce4ae5dfbde5af00b832651f76ca17ca42d3eadf17731f3eba`**, this host). Queue block
|
||||||
|
appended to `WORK-QUEUE.md`. Scratch tree `/root/crucible/q46/`; nothing deployed, no `cp` into
|
||||||
|
`/root/.helm/bin/`, `decisions/` and `inbox/` untouched, no another-party file edited, no secret.
|
||||||
|
|
||||||
|
**The item.** A subject carrying the Q35 markers whose source does not parse was **admitted** by the
|
||||||
|
Q39 admission and then **crashed** the gate, on every gate from Q39 up. The mechanism, as the pipe
|
||||||
|
named it: the crash detector tests for the literal `'Traceback (most recent call last)'` and a
|
||||||
|
top-level `SyntaxError` prints no such line, so "probe rc≠0, wrote nothing, no traceback" is satisfied
|
||||||
|
by a subject that cannot run at all. What failed was the refusal-path property every gate claims for
|
||||||
|
every subject — and a crashed run left its sandbox behind.
|
||||||
|
|
||||||
|
**The pair.** Collector **byte-identical to Q45's** (`4e45021d80395d3f…`, 720 / 29,954) — every change
|
||||||
|
is in the gate. Gate **`ae69d5dd735b6f3cfe82b3682b02acf4557b2939d104b9f15b81cffaeef016d4`**
|
||||||
|
(2,032 lines / 109,643 B) against Q45's `2bbca218…` (1,942); `diff -u` = 113 lines = 2 file + 3 hunk +
|
||||||
|
**90 added + 0 removed + 18 context**, and 1,942 + 90 − 0 = 2,032 exactly (`out/DIFF-vs-q45.txt`,
|
||||||
|
counted by `grep -c`, not asserted).
|
||||||
|
|
||||||
|
**Two changes.** (1) A parse guard after Q35's source precondition and before the admission probe:
|
||||||
|
`compile()` the subject's own source; a `SyntaxError` is refused with **exit 2** and a **dated**
|
||||||
|
message naming the subject, the failure and its position, with the live snapshot read before and after
|
||||||
|
— and the probe directory and the sandbox do not exist yet, so the refused class creates neither. It
|
||||||
|
registers **no `check()`**: the green count stays **169 passed / 0 failed**. (2) An **atexit** handler
|
||||||
|
removes the per-invocation sandbox on every exit path — refusal, propagated leg rc, or crash — closing
|
||||||
|
a leak this run measured on the n5 negative (before/after below).
|
||||||
|
|
||||||
|
**The discriminator, ten invocations, all five gates digest-identical to the revision history**
|
||||||
|
(`run_negatives.py` → `out/Q46-NEGATIVES.json`, 58,433 B / `028b6b1c68ab48cd…`, 21 rows,
|
||||||
|
`generated_at 2026-10-13T01:31:42.240730+00:00`):
|
||||||
|
|
||||||
|
| fixture | Q39 (`9b09f328…`) | Q43 (`c22601ec…`) | Q44 (`18580c10…`) | Q45 (`2bbca218…`) | Q46 (`ae69d5dd…`) |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| `noparse-with-markers.py` | rc 1, 575 B, `SyntaxError` at the legs' import of the **original** subject; no sandbox exists yet | rc 1, 575 B, sandbox-copy import; **`legacy/q43/helm-legs-jkij8r1x`** left | rc 1, 0 B, `AttributeError: 'NoneType' object has no attribute 'body'` at `:482` (the Q45 record's unexercised §8(a), now exercised); **`legacy/q44/helm-legs-8tkclld3`** left | rc 1, 575 B, sandbox-copy import; **`legacy/q45/helm-legs-wgn7u3v2`** left | **rc 2, stdout 0 B (`e3b0c44298fc1c149…`)**, `Q46 REFUSAL: … does not parse - invalid syntax (noparse-with-markers.py, line 13) (line 13, offset 12) - refused at 2026-10-13T01:31:45.419033+00:00, before the admission probe, before any leg, and before any sandbox is created.`; no traceback; no leftover |
|
||||||
|
| `dep-unparseable.py` (parses; its **dependency** does not) | rc 2 — Q39 repair 3 | rc 2 — repair 3 | rc 2 — repair 3 | rc 2 — repair 3 | rc 2 — repair 3 |
|
||||||
|
|
||||||
|
The class is split, and both halves are measured: the subject's own source is refused by the parse
|
||||||
|
guard; a dependency `SyntaxError` raises **with** a traceback and is refused by repair 3 on all five
|
||||||
|
gates. Repair 3 stays load-bearing — the guard is the gap between them, not a second layer.
|
||||||
|
|
||||||
|
**C2 reproduced mechanically.** On the crashed rows the ROOT-anchored `leftover()` reports `[]` while
|
||||||
|
the recursive census reports `legacy/q4X/helm-legs-…`: a gate creates its sandbox beside itself, so a
|
||||||
|
glob anchored at `ROOT` cannot see it. The harness takes both censuses per row; the shipped gate's
|
||||||
|
atexit handler means no run of it leaves one.
|
||||||
|
|
||||||
|
**The n5 leak, before/after.** The pre-atexit gate (`1fd889ca…`, the shipped gate minus the handler)
|
||||||
|
run with n5: **rc 2, leftover `work/helm-legs-3fancddl`** (`out/q46-noatexit-n5.{out,err}`, measured
|
||||||
|
live); the shipped gate, same fixture: rc 2, leftover `[]`. The leak is not the item's class (n5
|
||||||
|
parses) — the criterion generalises to every early exit, and the measurement said the refusal path did
|
||||||
|
not cover a propagated leg rc.
|
||||||
|
|
||||||
|
**Green pair, five consecutive invocations** (`run_green.py`, logs on disk at
|
||||||
|
`out/q46-green-{1..5}.{out,err}`): rc 0 each, **169 passed / 0 failed**, `VERDICT: ALL GATES PASS`,
|
||||||
|
13,312 B stdout, 12.17–12.62 s, live delta 0, held delta 0, no leftover.
|
||||||
|
|
||||||
|
**Live state, delta 0 across all 27 invocations** (21 harness rows + 5 green runs + the noatexit run):
|
||||||
|
snapshot `/root/.helm/data/snapshot.json` **14,039 B / sha256
|
||||||
|
`aa842b0fd88037f83115b4a54d617f27666f4de06ef84f9a64c8fd0a5f04656d`** / mtime
|
||||||
|
`2026-10-08 17:12:59.475780303 +0000`; held pin `/root/.helm/data/held/stackchain-home.html.sha256`
|
||||||
|
**120 B / `5b6d70716153c3f4…`**. Deployed bin untouched (`ee0b36a82f23fcf3…` / `7ed1bca8f9da624f…` /
|
||||||
|
`5c67473d89283c3d…`). Final tree: **0 sandboxes** (`find /root/crucible/q46 -name 'helm-legs-*' -o
|
||||||
|
-name 'helm-q39-*'` → 0, after `cleanup_final.py`). Host disk **46 GB free of 154 GB (71 % used)**
|
||||||
|
(`df -h /`).
|
||||||
|
|
||||||
|
**Four defects of mine, logged first in the deliverable:** the first harness run crashed on a missing
|
||||||
|
fixture path (the C1 fixture was at the tree root, not in `fixtures/`); the first harness wrote the
|
||||||
|
green logs as empty files (re-measured with `run_green.py`, real logs on disk); in this tree the Q44
|
||||||
|
crash carries **0 B** of stdout where the pipe measured **4 B** on the same fixture — a
|
||||||
|
tree-and-clock difference, recorded not reconciled; and the n5 leak was found by this run's own
|
||||||
|
census and fixed here rather than left as somebody else's finding.
|
||||||
|
|
||||||
|
### Blocked
|
||||||
|
|
||||||
|
Nothing in my lane. Same standing gates, all Alexander's: the **§4 adoption `cp`** — now collector
|
||||||
|
`4e45021d…` + gate **`ae69d5dd…`** (superseding `2bbca218…`); Q40's finding stands (the deployed gate
|
||||||
|
`7ed1bca8…` reads `HELM_COLLECTOR` zero times), so none of Q31–Q46's protection is in production until
|
||||||
|
it lands — Q9 (checksum handover), Q13 (wording fixes), Q19 (wizard onboarding).
|
||||||
|
|
||||||
|
### Next
|
||||||
|
|
||||||
|
1. **The pipe's verification of this run**, when it lands. Everything is re-runnable from
|
||||||
|
`/root/crucible/q46/`: `run_negatives.py` and `run_green.py` are self-contained, and
|
||||||
|
`figures.py` / `print_figs.py` / `show_rows.py` regenerate the record's figures from the JSONs.
|
||||||
|
2. **Merlin's Q45 §9 follow-on stays open and unclaimed:** resolve every path the subject declares
|
||||||
|
**at runtime**, rather than rewriting the literals it declares statically. Beside the adoption
|
||||||
|
decision, that is the one Helm-side follow-on I would name.
|
||||||
|
3. **Q7, measured: the Helm lane's executable set is empty again.** The adoption, Q9, Q13/Q19 and Q42
|
||||||
|
are human- or counterparty-gated. **No new item opened** — the next Helm unit is a decision, not a
|
||||||
|
task.
|
||||||
|
|
||||||
|
### Ask
|
||||||
|
|
||||||
|
Alexander: nothing blocking. The standing items, consolidated: (a) rule on the **§4 adoption `cp`** —
|
||||||
|
collector `4e45021d…` + gate `ae69d5dd…`, the pair that makes `python3 helm-selftest.py` green with no
|
||||||
|
environment and puts Q31–Q46's protection into production (the deployed gate today has no
|
||||||
|
subject-selection path at all); (b) the **`manifest_match` pin** decision (stale since 2026-10-08; a
|
||||||
|
static render cannot say so); (c) the **Q13 publication call** (item 13's decided note — escalate, do
|
||||||
|
not edit the live page — still controls over its `proposed_action`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Landing evidence, appended after the commit and read back over the anonymous raw path
|
||||||
|
(`https://forge.alexanderwhitestone.com/git/stackchain/troupe-standup/raw/branch/main/<path>`), no
|
||||||
|
credential — each **HTTP 200** and `cmp` byte-identical to disk:
|
||||||
|
|
||||||
|
| file | status / bytes | sha256 |
|
||||||
|
|---|---|---|
|
||||||
|
| `recoveries/helm-parse-guard.md` | 200 / 12,284 | `19fa3ff6d25c85ce4ae5dfbde5af00b832651f76ca17ca42d3eadf17731f3eba` |
|
||||||
|
| `WORK-QUEUE.md` | 200 / (post-append revision) | — |
|
||||||
|
| `checkins/2026-10-13-merlin.md` | 200 / (this file — self-citation deferred to the committed revision, for the same reason a file cannot hash itself) | — |
|
||||||
|
|
||||||
|
Re-measured after the push, from this host: the live snapshot `/root/.helm/data/snapshot.json` is
|
||||||
|
**14,039 B / sha256 `aa842b0fd88037f83115b4a54d617f27666f4de06ef84f9a64c8fd0a5f04656d` / mtime
|
||||||
|
`2026-10-08 17:12:59.475780303 +0000`** — delta 0 across the whole run — and the deployed
|
||||||
|
`/root/.helm/bin/` is untouched (`ee0b36a82f23fcf3…` / `7ed1bca8f9da624f…` / `5c67473d89283c3d…`).
|
||||||
|
Nothing was deployed, published, rebuilt or re-run beyond the measurements above; `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 gate check names, refusal messages, file paths and sha256
|
||||||
|
digests. This run's commits are authored `Merlin <merlin@agents.invalid>` (`git -c`), so the monitor
|
||||||
|
sees them as counterparty content per Q36's v1.3 rule.
|
||||||
|
|
||||||
|
— Merlin
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
> Screen. Every claim below carries a path, a hash, a URL, or a command with its output, or the word
|
||||||
|
> `unverified`. Nothing secret is printed anywhere in this check-in or in the files it lands.
|
||||||
|
|
||||||
|
# Q46 — the parse guard: an unparseable subject is refused, not crashed on
|
||||||
|
|
||||||
|
**Item:** Q46 (Helm lane), opened by the pipe 2026-10-12T23:4xZ on reply 29's C1 — the last open executable
|
||||||
|
item in the queue at the time this run started. **Done as specified:** a subject that does not parse is
|
||||||
|
refused with exit 2 and a dated message naming the parse failure; the four pre-Q46 gates measured on the
|
||||||
|
same fixture; the sandbox left by a crashed run either not created or removed; the `leftover()` census
|
||||||
|
extended past the ROOT glob (C2). Scratch tree `/root/crucible/q46/`; nothing deployed; no `cp` into
|
||||||
|
`/root/.helm/bin/`; `decisions/` and `inbox/` untouched; no another-party file edited; no secret printed.
|
||||||
|
|
||||||
|
## 1. The pair
|
||||||
|
|
||||||
|
- Subject collector **`4e45021d80395d3f03fc58d7e99610e3c1c0d2fcf9c85f1db50b16cfd2043c6c`** (720 lines /
|
||||||
|
29,954 B) — **byte-identical to Q45's**; every change is in the gate.
|
||||||
|
- Q45 gate `2bbca218871bc5613e1fe3c4d3a84fe2e6da05ffc526e51b2865a8c8ad00ee5e` (1,942 lines) — the legacy
|
||||||
|
copy in this tree (`legacy/q45/helm-selftest.py`) is digest-identical to `/root/crucible/q45/helm-selftest.py`,
|
||||||
|
so the discriminator runs the real Q45 gate, not an approximation of it.
|
||||||
|
- Q46 gate **`ae69d5dd735b6f3cfe82b3682b02acf4557b2939d104b9f15b81cffaeef016d4`** (2,032 lines /
|
||||||
|
109,643 B). `diff -u` against the Q45 gate (`out/DIFF-vs-q45.txt`, 113 lines) = **2 file headers + 3
|
||||||
|
hunk headers + 90 added + 0 removed + 18 context**, and **1,942 + 90 − 0 = 2,032** exactly. The
|
||||||
|
decomposition is counted by `grep -c` on the diff, not asserted: 91 `^+` minus the `+++` header = 90
|
||||||
|
added; 1 `^-` is the `---` header; 18 context.
|
||||||
|
|
||||||
|
## 2. What changed
|
||||||
|
|
||||||
|
**The parse guard** (gate lines 211–263, after Q35's source precondition, before the admission probe):
|
||||||
|
`compile()` the subject's own source; a `SyntaxError` is refused with **exit 2** and a **dated** message
|
||||||
|
naming the subject, the parse failure and its position, with the live snapshot read before and after and
|
||||||
|
the delta printed. The guard sits before the probe directory (`helm-q39-*`) and before the sandbox
|
||||||
|
(`helm-legs-*`) are created, so the refused class creates neither. It registers **no `check()`**, so the
|
||||||
|
green count is unchanged at **169 passed / 0 failed**.
|
||||||
|
|
||||||
|
**The atexit sandbox cleanup** (gate lines 694–710): an `atexit` handler removes the per-invocation
|
||||||
|
sandbox on **every** exit path — refusal, a propagated leg rc, or crash. Silent, so the green pair's
|
||||||
|
stdout/stderr shape is unchanged. This closes a leak this run measured, not hypothesised (§5).
|
||||||
|
|
||||||
|
**The class is split, and both halves are measured:**
|
||||||
|
|
||||||
|
- a subject whose **own source** does not parse is refused by the parse guard (exit 2, dated);
|
||||||
|
- a subject that parses but whose **imported dependency** does not parse raises **with a traceback**
|
||||||
|
and is already refused by Q39's repair 3 ("a crash is not a refusal") — measured on all five gates
|
||||||
|
(§4, the `dep-unparseable` rows). The parse guard is the gap between them, not a second layer over
|
||||||
|
repair 3.
|
||||||
|
|
||||||
|
## 3. The discriminator — two fixtures, five gates, ten invocations
|
||||||
|
|
||||||
|
Fixtures (this tree, `fixtures/`): `noparse-with-markers.py` (Q35 markers present, `def broken(:` at
|
||||||
|
line 13 — the pipe's C1 fixture, sha256 `7a8872f353419f4a…`), and `dep-unparseable.py` (parses, carries
|
||||||
|
the markers and a `SNAPSHOT_PATH` assignment, imports the unparseable `broken_dep.py`; sha256
|
||||||
|
`f621d4718ec83b19…` / `6b8c8a725c23dc07…`). Harness: `run_negatives.py` → `out/Q46-NEGATIVES.json`
|
||||||
|
(58,433 B / sha256 `028b6b1c68ab48cd3c7a5b1a4b698fbd6ada3a823232106d8ba44cb352fcd392`, 21 rows,
|
||||||
|
`generated_at 2026-10-13T01:31:42.240730+00:00`). Every row censuses the live snapshot and held
|
||||||
|
directory on (bytes, sha256, mtime_ns, ctime_ns) before and after; the leftover census is taken **two
|
||||||
|
ways** — the ROOT-anchored glob and a recursive `rglob`.
|
||||||
|
|
||||||
|
| row | rc | stdout | refusal / crash | leftover (recursive) | leftover (ROOT glob) | live delta |
|
||||||
|
|---|---|---|---|---|---|---|
|
||||||
|
| q46-dis-q39-noparse | **1** | 575 B | `Traceback… SyntaxError: invalid syntax` (legs import the ORIGINAL subject) | `[]` | `[]` | 0 |
|
||||||
|
| q46-dis-q43-noparse | **1** | 575 B | `Traceback… SyntaxError` (in-process import of the sandbox copy) | `legacy/q43/helm-legs-jkij8r1x` | `[]` | 0 |
|
||||||
|
| q46-dis-q44-noparse | **1** | 0 B | `AttributeError: 'NoneType' object has no attribute 'body'` (`_q39_tree.body`, gate `:482`) | `legacy/q44/helm-legs-8tkclld3` | `[]` | 0 |
|
||||||
|
| q46-dis-q45-noparse | **1** | 575 B | `Traceback… SyntaxError` (sandbox-copy import) | `legacy/q45/helm-legs-wgn7u3v2` | `[]` | 0 |
|
||||||
|
| q46-dis-q46-noparse | **2** | 0 B | **`Q46 REFUSAL: … does not parse - invalid syntax (noparse-with-markers.py, line 13) (line 13, offset 12) - refused at 2026-10-13T01:31:45.419033+00:00, before the admission probe, before any leg, and before any sandbox is created.`** | `[]` | `[]` | 0 |
|
||||||
|
| q46-dis-{q39,q43,q44,q45,q46}-dep-unparseable | **2** (all five) | 0 B | `Q39 REFUSAL: … a bare run against the sentinel exited rc=1 (stderr: Traceback …` — repair 3, dated live census in the message | `[]` | `[]` | 0 |
|
||||||
|
|
||||||
|
The four crashed rows are the defect the item names, reproduced on gates digest-identical to the
|
||||||
|
deployed-revision history (`9b09f328…`, `c22601ec…`, `18580c10…`, `2bbca218…`). The Q44 row's crash site
|
||||||
|
is the unguarded `_q39_tree.body` access the Q45 record reported as unexercised (§8(a)) — now exercised,
|
||||||
|
and it happens **after** the sandbox is created (`legacy/q44/helm-selftest.py:462` mkdtemp, `:482` the
|
||||||
|
crash), which is why a sandbox is left. The Q39 row leaves none because Q39's legs run the **original**
|
||||||
|
subject and crash before any sandbox exists — the pre-Q43 shape. **C2 is reproduced mechanically on the
|
||||||
|
crashed rows:** the ROOT-anchored glob reports `[]` while the recursive census reports the sandbox in the
|
||||||
|
gate's own directory (`legacy/q4X/`), because a gate creates its sandbox beside itself.
|
||||||
|
|
||||||
|
## 4. The green pair — five consecutive invocations, logs on disk
|
||||||
|
|
||||||
|
`run_green.py` → `out/Q46-GREEN.json` and `out/q46-green-{1..5}.{out,err}`:
|
||||||
|
|
||||||
|
| run | rc | passed | stdout | seconds | live delta | leftover |
|
||||||
|
|---|---|---|---|---|---|---|
|
||||||
|
| 1 | 0 | 169 | 13,312 B | 12.62 | 0 | `[]` |
|
||||||
|
| 2 | 0 | 169 | 13,312 B | 12.19 | 0 | `[]` |
|
||||||
|
| 3 | 0 | 169 | 13,312 B | 12.22 | 0 | `[]` |
|
||||||
|
| 4 | 0 | 169 | 13,312 B | 12.32 | 0 | `[]` |
|
||||||
|
| 5 | 0 | 169 | 13,312 B | 12.17 | 0 | `[]` |
|
||||||
|
|
||||||
|
Green log digests (stdout): `c02adfda1704eea0…` / `10972472d772db66…` / `51b42c964868bed8…` /
|
||||||
|
`389a04e41d2fd1fd…` / `deef0ded4a09ac77…` (harness run) — the `run_green.py` re-measurement writes the
|
||||||
|
same bodies to disk. **169 = 166 + 3, unchanged from Q45**: the parse guard registers no check, and the
|
||||||
|
atexit handler prints nothing, so the green pair's shape is byte-stable against Q45's.
|
||||||
|
|
||||||
|
## 5. The n5 sandbox leak — found by measurement, closed by the atexit handler
|
||||||
|
|
||||||
|
The five Q39 negative fixtures under the Q46 gate (regression control): **all five rc 2, live delta 0,
|
||||||
|
held delta 0, no leftover** (`q46-neg-n1…n5` rows). The `n5` row is the interesting one: n5 is
|
||||||
|
**admitted** (Q45 redirects its live literal), its legs run, and it ends rc 2 through the collector's own
|
||||||
|
destination rule (`refusing to write the live snapshot <sandbox>/data/snapshot.json without --deploy`,
|
||||||
|
`out/q46-noatexit-n5.err`) — and on the **pre-atexit** gate it **left its sandbox behind**.
|
||||||
|
|
||||||
|
- Pre-atexit gate (the Q46 gate minus the atexit block), sha256
|
||||||
|
`1fd889ca95c81910aead88c0aa9d445cb91ffd1caecc411958160ad3b04a4260`, run with n5: **rc 2**, leftover
|
||||||
|
**`work/helm-legs-3fancddl`** (`out/q46-noatexit-n5.out` / `.err`, the leak measured live).
|
||||||
|
- Post-atexit gate (the shipped `ae69d5dd…`), the same fixture in the harness: **rc 2, leftover `[]`**.
|
||||||
|
|
||||||
|
The leak is not the item's class (n5 parses), but the item's done criterion — "the sandbox a crashed run
|
||||||
|
leaves behind is either not created or removed by the refusal path" — generalises to every early exit,
|
||||||
|
and the measurement said the refusal path did not cover a propagated leg rc. The atexit handler is the
|
||||||
|
fix, and the before/after is measured on the same fixture, same gate, same host.
|
||||||
|
|
||||||
|
## 6. Live state — delta 0 across every invocation
|
||||||
|
|
||||||
|
Across all 21 harness rows, the 5 green runs and the noatexit run (`out/Q46-NEGATIVES.json`,
|
||||||
|
`out/Q46-GREEN.json`, re-measured after the runs):
|
||||||
|
|
||||||
|
- live snapshot `/root/.helm/data/snapshot.json`: **14,039 B / sha256
|
||||||
|
`aa842b0fd88037f83115b4a54d617f27666f4de06ef84f9a64c8fd0a5f04656d` / mtime
|
||||||
|
`2026-10-08 17:12:59.475780303 +0000`** (mtime_ns `1791479579475780303`, ctime_ns `1791789490146606356`
|
||||||
|
— the ctime is the Q45 restore trace, unchanged);
|
||||||
|
- live held pin `/root/.helm/data/held/stackchain-home.html.sha256`: **120 B / sha256
|
||||||
|
`5b6d70716153c3f4bd82045d5d71edf88f793a240ce1275c3dfca5759d0ee297`** / mtime_ns `1791478687099553071`,
|
||||||
|
ctime_ns `1791846636217687791` (the 2026-10-12T23:10:36Z restore trace).
|
||||||
|
|
||||||
|
Deployed bin untouched: `helm-collect.py` `ee0b36a82f23fcf3…`, `helm-selftest.py` `7ed1bca8f9da624f…`,
|
||||||
|
`helm-render.py` `5c67473d89283c3d…`. Nothing deployed, nothing published. Final tree state: **0
|
||||||
|
sandboxes** (`find /root/crucible/q46 -name 'helm-legs-*' -o -name 'helm-q39-*'` → 0), fixture
|
||||||
|
destination directories removed by `cleanup_final.py`. Host disk **46 GB free of 154 GB (71 % used)**
|
||||||
|
(`df -h /`, run time).
|
||||||
|
|
||||||
|
## 7. Limits, stated not buried
|
||||||
|
|
||||||
|
1. The guard is **static**: it compiles the subject's own source text. A `SyntaxError` inside code the
|
||||||
|
subject *generates* at runtime is out of its reach — and out of every static guard's; nothing in this
|
||||||
|
gate's path executes generated code.
|
||||||
|
2. The dependency class is **covered by repair 3, not by this guard** — measured on all five gates
|
||||||
|
(§3). If repair 3 were ever removed, the dependency class would crash again; the guard does not
|
||||||
|
subsume it.
|
||||||
|
3. `atexit` does not run on `os._exit` or `SIGKILL`. Coded-not-exercised; the pre-atexit leak is the
|
||||||
|
measured failure mode it closes, and both SIGKILL classes are outside the gate's control anyway.
|
||||||
|
4. The guard protects the gate's legs, not a direct CLI run of a pre-gate collector (the Q35 limit,
|
||||||
|
unchanged).
|
||||||
|
5. The discriminator's legacy gates run from copies in this tree; their sandboxes land beside the
|
||||||
|
copies, which is exactly C2's shape and is why the recursive census is the honest one.
|
||||||
|
|
||||||
|
## 8. Defects of mine, logged first
|
||||||
|
|
||||||
|
1. The first harness run crashed on a missing fixture path — the C1 fixture was at the tree root, not in
|
||||||
|
`fixtures/` (`FileNotFoundError`, `run_negatives.py:203`). Logged, moved, re-run.
|
||||||
|
2. The first harness wrote the green logs as **empty files** (the stdout lived only in the JSON).
|
||||||
|
Re-measured with `run_green.py`, which writes the real stdout and stderr per run (§4).
|
||||||
|
3. In this tree the Q44 crash is **0 B of stdout** where the pipe measured **4 B** on the same fixture —
|
||||||
|
a tree-and-clock difference (their fixture path is longer), recorded, not reconciled. The crash site,
|
||||||
|
the rc and the leftover agree.
|
||||||
|
4. The n5 sandbox leak (§5) was found by this run's own census, on a class the item does not name, and
|
||||||
|
fixed here rather than left as somebody else's finding.
|
||||||
|
|
||||||
|
## 9. §4 consequence and Q7, measured
|
||||||
|
|
||||||
|
**The adoption pair is now collector `4e45021d…` + gate `ae69d5dd…`** (superseding Q45's `2bbca218…`).
|
||||||
|
Q40's finding is unchanged and is why the `cp` matters: the deployed gate `7ed1bca8…` reads
|
||||||
|
`HELM_COLLECTOR` **zero** times, so none of Q31–Q46's protection is in production until the pair lands.
|
||||||
|
Alexander's call; nothing in this run touched `/root/.helm/bin/`.
|
||||||
|
|
||||||
|
**Q7:** after this item the Helm lane's executable set is empty again — the adoption `cp` is Alexander's,
|
||||||
|
Q9 waits on the checksum handover, Q13/Q19 are Alexander's, Q42 is zephyrus's, Q37 is the pipe's.
|
||||||
|
Merlin's own §9 follow-on from Q45 (resolve every path the subject declares **at runtime**, rather than
|
||||||
|
rewriting the literals it declares statically) remains open and unclaimed beside the adoption. **No new
|
||||||
|
item opened** — the next Helm unit is a decision, not a task.
|
||||||
Reference in new issue
Block a user