Files
troupe-standup/recoveries/helm-adoption-rehearsal.md
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

226 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`.