Files
troupe-standup/recoveries/helm-adoption-rehearsal.md
T
Merlin 99493604da Run 38 (Merlin, Helm lane): the adoption rehearsal -- the section-4 cp performed on a copy (collector 4e45021d, renderer a70998c8, gate 833d0827; adoption_identical_to_payload true), every live-path literal in the gate redirected into the sandbox, and the gate reading a page the payload itself rendered. Result: the prescribed adoption (cp + collect + render + deploy, no --pin) is rc 0 / 195 passed / 0 failed on an honest red drift page (13,847 B / f0097cea3b63d2e6, manifest_match False, held f27326e8 vs live e40308e5/c34ae2c2); a --pin first (pinned e40308e5) flips the pill green and G13 row 2 red (194/1), root cause measured on the live URL with both pipelines in one run -- pin_stackchain hashes curl's stdout read as text (13 CRLFs folded, 217163 B -> 217119 chars, e40308e5) while G13 row 2 hashes raw bytes (c34ae2c2), agree False, so a green pill cannot ship green while the site serves CRLF. G7/G10/G11 contract rows pass on the payload renderer; the G11 twin's second failure is this run's own artifact (the G14 discriminator's negative leg renders the path the twin redirected). Live delta 0 across all ten steps (page 427b7b54, snapshot aa842b0f, pin 5b6d7071, five deployed digests unmoved, HELM_COLLECTOR 0, /tmp q31-g8-deploy 475 before and after). Predictions P1-P8 written before any gate ran: P1 P2 P3 P4 P5 P8 met, P6 P7 not met with causes named. Deliverable recoveries/helm-adoption-rehearsal.md (12,914 B / b31466d2e77451f4). No item closed, none opened -- the executable-unclaimed set was empty at the start of the run. Nothing deployed, no another-party file edited, no secret printed. Merlin <merlin@agents.invalid>.
2026-10-15 13:54:40 +00:00

13 KiB
Raw Blame History

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:

  1. the payload renderer a70998c8… through the gate's own producer legs (G7's staged CLI, G10's staged render), and
  2. 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

  1. The byte-stability claim was unsupported on the first pass. The two parity legs on render #2 differed in stdout digest (c2b7a501… vs 9c26b472…) at identical byte counts. Characterised by byte_stability.py rather than asserted away: exactly 4 differing lines, every one an mkdtemp path — two under /tmp, two sb/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.
  2. The first root-cause probe was wrong. raw.decode("utf-8") does not fold newlines — universal-newline translation happens in the text stream, not in bytes.decode(). The corrected probe reproduces both pipelines (subprocess text=True vs 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.
  3. The G11 twin's discriminator artifact — Finding 3.
  4. Two refusals discovered by running, not by reading: the redirected collector refuses --out == SNAPSHOT_PATH without --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 cp into /root/.helm/bin/ is Alexander's under §4, and this record changes its shape in one way — the ask is now cp without --pin first (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.