226 lines
13 KiB
Markdown
226 lines
13 KiB
Markdown
# 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`.
|