13 KiB
The adoption rehearsal: the §4 payload, run end to end, on a copy
Merlin, Run 38, 2026-10-15. Scratch /root/crucible/q67/. Nothing deployed;
no file under /root/.helm or /var/www written; no another-party file edited.
Why this unit exists
Every acceptance figure on record for the section-4 adoption ask — the
rc 0 / 195 passed / 0 failed leg — was measured with a gate whose
RENDERER constant and renderer import still named /root/.helm/bin/helm-render.py
(the deployed pre-G14 renderer 5c67473d…), and whose _deployed page was a
hand-built fixture cell (/root/crucible/q65/cells/r36-g14-stale.html). The
Run-36 twins changed only the four _deployed constants. So the acceptance
leg has never exercised:
- the payload renderer
a70998c8…through the gate's own producer legs (G7's staged CLI, G10's staged render), and - the collect → render sequence that actually produces the page the gate would read.
This run closes both gaps: the adoption cp is performed on a copy, every
live path the gate names is redirected into the sandbox, and the page the gate
reads is an artifact the payload itself rendered.
The payload and the adoption, measured
| artifact | path | bytes | sha256 |
|---|---|---|---|
| collector | /root/crucible/q48/helm-collect.py |
29,954 | 4e45021d80395d3f03fc58d7e99610e3c1c0d2fcf9c85f1db50b16cfd2043c6c |
| renderer | /root/crucible/q65/helm-render-g14.py |
22,930 | a70998c8592a03cd68447e02126ce175732796c225f57ccaf90f973dfb5ff654 |
| gate | /root/crucible/q65/helm-selftest-q65.py |
190,330 | 833d08275977054a92dc6c9382ed08127307ac54552d738aef35b07825a39d3e |
build_rehearsal.py → out/BUILD.json: the sandbox bin is a copy of
/root/.helm/bin with the three payload files copied over it
(adoption_identical_to_payload: true, asserted per file), the live held pin
seeded byte-for-byte into the sandbox, and two gate twins generated by
asserted replace-all over every live-path literal:
| literal redirected | occurrences |
|---|---|
/root/.helm/bin/helm-collect.py |
2 |
/root/.helm/bin/helm-render.py |
8 |
/var/www/alexander-whitestone/helm/index.html |
8 |
/root/.helm/data/held/stackchain-home.html.sha256 |
2 |
/root/crucible/q59/helm-render-g11.py (twin B only) |
3 |
The held-pin redirect exists because of a split-brain the first run
exposed: without it the gate compared a page rendered from the sandbox
snapshot against the live held pin — two states of one thing — and returned
193 passed / 2 failed on the post-re-pin page. With it, the sandbox held pin
stands in for the live one and its state (stale seed vs re-pinned) is the
variable under test.
The sequence and its results (run_rehearsal.py → out/RUN.json)
Clock 2026-10-15T13:42Z. HELM_COLLECTOR=sb/bin/helm-collect.py,
TMPDIR=/tmp, HOME=/root.
| step | command | result |
|---|---|---|
| S2 collect #1 | helm-collect-sandboxed.py --out sb/data/snapshot.json --deploy --skip-tests |
rc 0; manifest_match=False, held f27326e8…, live normalised e40308e5…, live wire c34ae2c2…, FRESH, observed 2026-10-15T13:42:14.099Z |
| S3 render #1 | helm-render-sandboxed.py --deploy |
rc 0; page 13,847 B / f0097cea3b63d2e6…; as of ×2, not re-checked since ×0, red drift pill |
| S4 gate (parity) on render #1 | gate-parity.py |
rc 0, 195 passed / 0 failed, VERDICT: ALL GATES PASS |
| S5 re-pin | helm-collect-sandboxed.py --pin --deploy |
rc 0; pinned e40308e573d046d7… -> sb/data/held/stackchain-home.html.sha256 (file digest 5b6d7071… → a5cabe8c…) |
| S6 collect #2 | same as S2 | rc 0; manifest_match=True, held e40308e5… = live normalised, wire still c34ae2c2… |
| S7 render #2 | same as S3 | rc 0; page 13,847 B / 7cdfc56676b1323f…; as of ×2, disclaimer ×0, green match pill |
| S8 gate (parity) on render #2 | gate-parity.py |
rc 1, 194 passed / 1 failed |
| S9 gate (G11-contract twin) on render #2 | gate-g11-payload.py |
rc 1, 193 passed / 2 failed |
| S10 gate (parity) repeat on render #2 | gate-parity.py |
rc 1, 194 passed / 1 failed — same row |
Predictions, written in build_rehearsal.py before any gate ran and scored
in RUN.json: P1, P2, P3, P4, P5, P8 met; P6 and P7 not met, with the
cause of each named below rather than restated as a near-miss.
Finding 1 — the adoption as prescribed is green, and the page it ships is honest-red
The prescribed sequence — cp the three payload files, re-collect, re-render,
deploy, without --pin — was reproduced end to end on the payload's own
artifacts: collect #1 (manifest_match=False, because the live body changed
since the pin), render #1 (red drift pill, as of clock, no disclaimer),
and the gate over that page at 195 passed / 0 failed. The acceptance
criterion is therefore reachable, and what it accepts is the honest state:
the held pin is seven days stale against a body that has changed, and the
page says so in red instead of claiming a match it cannot support.
Finding 2 — a --pin before adoption makes the gate red, and the cause is a hashing-convention split (the blocker)
The re-pin leg is the one the records had never run. With the pin re-based,
manifest_match goes True, the page prints the green pill — and G13 row 2
goes red, naming itself:
G13 an unqualified green pill is confirmed by a fresh fetch of the
snapshot's own URL
url=https://www.stackchainmagazine.net/ fetch_status=200
fetch_bytes=217163 live_sha256=c34ae2c245117ca7
held_pin_digest=e40308e573d046d7 pin_matches_live_now=False
unqualified_green_pill=True
Root cause, measured (probe_normalisation.py, same URL, both pipelines in
one run):
status 200 raw_bytes 217163 curl_text_chars 217119
crlf_in_raw 13 lf_in_raw 1859
gate_digest_raw_bytes c34ae2c245117ca73ba5720b2c22b6fab689840070ff68cda7bffe3e723986d4
collector_digest_text e40308e573d046d7d503acecbfbde82b8ccd10758a345b0440d55b796e2d821e
agree False
pin_stackchain (helm-collect.py:664-680) hashes body.encode("utf-8", "replace") where body is curl's stdout read as text — universal
newlines fold the served body's 13 CRLFs to LF (217,163 B → 217,119 chars).
G13 row 2 (helm-selftest-q65.py:3360-3377) hashes the raw bytes of its
own urllib fetch. The two digests agree only for a body with no CRLF, and the
live body has 13. So the gate's "one instrument that can see a false match"
can never confirm a true match after a re-pin: a green pill cannot ship
green while the site serves CRLF line endings. This is a code-level
misalignment between two artifacts of the same lane, not an operator call —
it is the Helm lane's next executable item (align the convention: have the
gate fold newlines the way the collector's text read does, or have --pin
hash the raw bytes), and until it is closed the adoption ask must not be
paired with a --pin.
Finding 3 — the G11 contract holds on the payload renderer; the twin's second failure is my own artifact
Twin B swapped G11's scratch renderer path (/root/crucible/q59/helm-render-g11.py,
the pre-G14 306d4bee) for the payload renderer. G11's contract rows —
amber unverified pill on a stale clock, green pill on a fresh one — passed
on the payload renderer; the payload is a superset of the G11 renderer, as
predicted. The twin's second failure is the G14 discriminator row, whose
negative leg renders that same path: my redirect handed the discriminator
the fixed renderer as its negative, so the "violates the predicate" leg no
longer violated it. The failure is the twin's construction, not a property
of the payload — the parity twin, which leaves the q59 path alone, shows the
discriminator green. Post-adoption G11's _g11_renderer should be re-based
on the adopted renderer (and its byte figures on the payload's 11,917 /
11,695 B rather than the q59 renderer's 11,819 / 11,624 B) — a one-line
adoption-side follow-up, not a payload defect.
Finding 4 — the gate's producer legs never ran the payload until now (now closed for G7/G10/G14)
G7's staged CLI render and G10's staged render execute RENDERER
(helm-selftest-q65.py:2123), and the renderer import at :1779/:1792
names the deployed file. In the parity twin both name the payload renderer,
and every one of those rows passed — including the destination-gate negatives
(the payload renderer still refuses the production destination without
--deploy, and the refusal names the live document root). The G14
discriminator already ran the payload from its scratch path
(:3442); the rehearsal adds the rest. The gate is therefore adoptable
against the payload it will guard, on the evidence of this run rather than
by inference.
Live state: delta 0 across every leg
Censused (bytes + sha256 + mtime_ns) before and after all ten steps:
| object | before = after |
|---|---|
deployed page /var/www/alexander-whitestone/helm/index.html |
9,865 B / 427b7b54c69b18d2… |
live snapshot /root/.helm/data/snapshot.json |
14,039 B / aa842b0fd88037f8… |
| live held pin | 120 B / 5b6d70716153c3f4… |
| deployed bin (5 digests) | ee0b36a8… collector, 5c67473d… renderer, 7ed1bca8… selftest, 12570786… validate, 73869998… summarize — all unmoved |
grep -c HELM_COLLECTOR over the deployed bin is 0, so Q40 stands.
/tmp/q31-g8-deploy-* census 475 dirs before and after (the gate's leg
sandboxes clean up after themselves; the Run-37 leak site is unchanged),
/tmp 13 G, disk 40 G free of 154 G.
My own defects, logged first
- The byte-stability claim was unsupported on the first pass. The two
parity legs on render #2 differed in stdout digest (
c2b7a501…vs9c26b472…) at identical byte counts. Characterised bybyte_stability.pyrather than asserted away: exactly 4 differing lines, every one anmkdtemppath — two under/tmp, twosb/helm-legs-*paths my normaliser did not cover. The gate is byte-stable after path normalisation; the normaliser had the gap, and the residual claim is now stated as "identical after normalising mkdtemp paths" with the four lines named. - The first root-cause probe was wrong.
raw.decode("utf-8")does not fold newlines — universal-newline translation happens in the text stream, not inbytes.decode(). The corrected probe reproduces both pipelines (subprocesstext=Truevs raw urllib bytes) and the figures above are from it. The wrong probe's output (both digests equal) is recorded so the error is not silently replaced. - The G11 twin's discriminator artifact — Finding 3.
- Two refusals discovered by running, not by reading: the redirected
collector refuses
--out == SNAPSHOT_PATHwithout--deploy(Q31's destination gate), and the redirected renderer refuses its default destination without--deploy(G7's). In the sandbox both defaults are the scratch paths, so both legs carry--deploy; the live census around every leg is the proof that nothing outside the sandbox was written.
Limits, stated
- The page the gate reads is rendered from the sandbox snapshot, while the gate's own producer legs (G7, G10, G11) render from the live snapshot — the live snapshot cannot be written from here, and the two collects read the same sources minutes apart. The asymmetry is disclosed, not hidden: the acceptance rows that read the artifact (G10 row 2, G12, G13 rows 1-2, G14 row 1) see the payload's own page; the rows that exercise the producer see the live snapshot.
- The gate's G13 fresh fetch is a real network read of
https://www.stackchainmagazine.net/— Finding 2's measurement is live, not staged. - The rehearsal redirects the gate's live-path literals by replace-all; the
gate's own comments carrying those paths are rewritten with them (20
literals across 4 patterns + 3 in twin B). No logic line changed; the
twins' digests are recorded in
BUILD.json. - What this run does not decide: the adoption itself. The
cpinto/root/.helm/bin/is Alexander's under §4, and this record changes its shape in one way — the ask is now cp without--pinfirst (195/0 on an honest red page), with the normalisation alignment filed as the next Helm item before any green pill can ship.
Artifacts
/root/crucible/q67/build_rehearsal.py(sandbox + twins, predictions P1-P8)/root/crucible/q67/run_rehearsal.py(the ten-step sequence, scoring)/root/crucible/q67/probe_normalisation.py(Finding 2's root cause)/root/crucible/q67/byte_stability.py(defect 1's characterisation)/root/crucible/q67/out/BUILD.json,/root/crucible/q67/out/RUN.json- host-local scratch; nothing committed from
/root/crucible.