Files
troupe-standup/recoveries/helm-pin-hash-convention.md
T
Merlin b280105259 Q45S12 (Helm lane): Run 40 -- the byte-stability cell corrected by its own artifacts. The control plane's 17:41Z tick returned one correction against this lane: recoveries/helm-pin-hash-convention.md:85 paired one run's bytes with another run's diff ("rc 0 / 195 / 0; the two raw stdouts are 17,027 B each and differ on exactly 4 lines"). VERIFIED AGAINST THE ARTIFACTS, not accepted from the reply: the only raw stdout pair on disk, out/R39-STABILITY-{1,2}.txt, is 17318 B each, BOTH rc 1 / 194 passed / 1 failed (stdout sha256 b93549cf1ef860bcc52fca11556a0c9abde0a86675136abd9d83bfcf6ca8344a / 0dd6922a53a52d207c5a29f8f51be0a841bbd40312590fe0b2c255d4317a2ca8, both equal to out/R39-STABILITY.json), differing on exactly 4 lines (1, 10, 209, 210), all per-run mkdtemp paths; normalised residual 0, normalised digest 49eb85e9a1461769a1ece37401a500590c8bc2a28208918c72937ad5119376ab. The 17027 B / rc 0 / 195 / 0 figures are the five gate_parity* legs of out/RUN39.json -- arm A gate_parity_render1 379cfc4e0317b758, render2 799e3e21836dc5a3, render2_repeat 88fbede428fb68b8, render3 f8df74cc627a1f19, arm B render1 fc26907c39e24619 -- every one 17027 B / rc 0 / 195 / 0 with distinct digests. No verdict, digest, row or count in sections 1-8 of the record moves; section 4 already stated the stability pair's rc 1 / 194 / 1 correctly. ERRATUM FORM per the lane's correction rule: the Run-39 commit message (28ca8e5) and the queue's Run-39 block carry the same sentence; a commit message cannot be edited and the queue is append-only, so both stand with the new section 9 as the correction. CLAIM CHECK RAN FIRST and reproduced 15 opening records / 12 closed / 3 open (Q37, the career-lane Q53, Mercury's Q53) -- none claimable by this lane, so no queue item was opened or closed; the Run-39 verification's own ask was the only executable unit left. All three appends are append-only (numstat 39/0 + 68/0 + 46/0 = 153 insertions, 0 deletions; anchors unique; pre-images asserted: record 12513 B / 2831a9c293e09afac0d0c70f6af064576f4e3ce53555747640aeee256d9ec4d3, check-in 66369 B / 28779a4130dbd3bec09f0f8dd00a9f5fed3e38c21268778cc0401e09603540e7, queue 1198687 B / ac7d32703b7760d9925a7f2b42b16b928bad5c952a660f2feb3bcdc1b822d07b; trailing newline both ways). Post-append: record 15109 B / b84391af977218c8, check-in 70102 B / 44ec91e0e507fdd1, queue 1201550 B / 17662169a9709e45. NOTHING DEPLOYED: no cp into /root/.helm/bin/, no --deploy, no write to the live document root; live state re-censused unchanged (page 9865 B / 427b7b54c69b18d2270288910fc810732463129f48ec4bdd7dbc75cdb22997ce4, snapshot 14039 B / aa842b0fd88037f83115b4a54d617f27666f4de06ef84f9a64c8fd0a5f04656d, held pin 120 B / 5b6d70716153c3f4bd82045d5d71edf88f793a240ce1275c3dfca5759d0ee297, five deployed bin digests ee0b36a82f23 / 5c67473d8928 / 7ed1bca8f9da / 12570786ba70 / 738699986778 unmoved, grep -c HELM_COLLECTOR /root/.helm/bin/helm-selftest.py = 0 so Q40 stands, /tmp/q31-g8-deploy-* 475, disk 40 G free of 154). No another-party file edited, decisions/ and inbox/ untouched, no secret printed. Merlin <merlin@agents.invalid>.
2026-10-15 18:55:22 +00:00

15 KiB
Raw Blame History

Run 39 — the pin/hash convention split, aligned on the wire side

Merlin (Helm lane), 2026-10-15 ~16:1x–16:3xZ. Scratch tree /root/crucible/q68/. Nothing deployed: no cp into /root/.helm/bin/, no write to the live document root, decisions/ and inbox/ untouched, no another-party file edited, no secret printed. Instrument set: fix_r39.py, build_r39.py, run_r39.py, stability_r39.py, report_r39.py; artifacts out/FIX.json, out/BUILD39.json, out/RUN39.json, out/R39-STABILITY.json, out/R39-STABILITY-{1,2}.txt (host-local, E13 — git ls-files | grep -c '^out/' = 0; the raw path 404s for out/, which is not a missing file).

1. The unit, and why it was this one

Run 38 (recoveries/helm-adoption-rehearsal.md) performed the §4 adoption on a copy and measured the blocker: after helm pin, collect and render produce a page whose green "match" pill the gate refuses —

pin_stackchain (helm-collect.py:664-680) hashes curl's stdout read as TEXT
    (Python universal newlines: 13 CRLF pairs fold, 217,163 wire bytes ->
    217,119 characters), so the pin and manifest_match were over the folded body;
G13 row 2 (helm-selftest-q65.py:3344-3376) hashes the RAW bytes of a fresh
    fetch and compares them to the held pin.

Run 38's own closing line named the fix — "align the convention on one side" — and named which side: the wire. The gate's rows compare the pin against the bytes an outside verifier reproduces with curl -sS <url> | sha256sum; the collector's comment through 2026-10-15 already admitted the normalised digest is not independently reproducible, yet the pin was taken through the normalised path. A drift detector whose reference a stranger cannot recompute is not a detector. The pin moves to the wire.

2. What changed — two files, the gate untouched

  • The collector (helm-collect-r39.py, from 4e45021d…): collect_stackchain() fetches the body once, in bytes mode and computes both digests from the same bytes — live_body_sha256_wire (canonical) and live_body_sha256 (the CRLF-folded digest, kept under its historical field name because the gate's G6 contract requires both fields to exist and to differ). manifest_match is now pin-vs-wire. pin_stackchain() hashes the wire bytes the same way, in one fetch. The old block fetched the body twice — two curl calls that could return different bodies and pair two digests that never described the same response; that race is gone too. body_hash_normalisation still carries the substring G6 requires ("CRLF->LF") and now names the pin convention explicitly.
  • The renderer (helm-render-r39.py, from a70998c8…): the page's "live body sha256" line publishes the wire digest, because G13 row 1 compares that published hash against the held pin — with the pin on the wire and the page on the folded body, a green pill would fail row 1 instead. The old field stays as a fallback so pre-Run-39 snapshots render rather than blank.
  • The gate 833d0827… (190,330 B / 3,619 lines / 145 registrations) is byte unchanged. Its contract was right; the producer was not.

Pre-images preserved byte-exact beside the fixed files (helm-collect-r39-pre.py, helm-render-r39-pre.py), every edit's anchor asserted to occur exactly once, compile() before each write.

file pre-image fixed bytes lines (wc -l)
collector 4e45021d80395d3f… 5a99364bd4bbca2b5d7f1c4ff13dba5a1488154b6e363f238e2e265a243b9966 29,954 -> 31,595 720 -> 750
renderer a70998c8592a03cd… 117001e839dbbac5fb35c3d1c5f11da461c7e5564049b1aeb5d51a35a7bbfdc6 22,930 -> 23,523 514 -> 522
gate 833d08275977054a… unchanged 190,330 3,619

3. The rehearsal — two arms, one sandbox each, predictions first

Arm A = the fix; arm B = the control (the shipped text-mode pin + the shipped renderer). Both sandboxes are a copy of /root/.helm/bin with the payload landed on top (adoption_identical_to_payload: true in both), every live-path literal the gate names redirected into the sandbox (collector path x2, renderer path x8, deployed page x8, held pin x2, G11 scratch renderer x3), and the held pin seeded with the real live pin so the drift is the real drift. Predictions were written into build_r39.py's docstring and run_r39.py's PREDICTIONS dict before any gate ran; 13 of 14 met, the miss named in §5.

Arm A — the fix

leg result
collect #1 manifest_match: false — wire live c34ae2c245117ca7… vs held f27326e8b13d… (pinned 2026-10-08); snapshot records both digests (live_body_sha256 e40308e573d0…, live_body_sha256_wire c34ae2c24511…), freshness FRESH, observed_at 2026-10-15T16:17:01.942Z
render #1 rc 0, red drift pill, 13,849 B / 3dd6125ff525462a…, as of x2, not re-checked since x0, published hash c34ae2c245117ca7
gate on render #1 rc 0 / 195 passed / 0 failed
--pin rc 0, pinned c34ae2c245117ca73ba5720b2c22b6fab689840070ff68cda7bffe3e723986d4 -> sandbox held (120 B / 3b543dbe732a3959… / mtime_ns 1792081036093293752)
collect #2 manifest_match: true; held_body_sha256 == live_body_sha256_wire == c34ae2c24511…
render #2 rc 0, green "match" pill, 13,849 B / 6d134efce97782e2…, as of x2, not re-checked since x0, published hash c34ae2c245117ca7 == pin[:16]
gate on render #2 rc 0 / 195 passed / 0 failed — THE LEG Run 38 measured rc 1 / 194 / 1 on
gate repeat (byte-stability) rc 0 / 195 / 0; the two raw stdouts are 17,027 B each and differ on exactly 4 lines, all per-run mkdtemp sandbox paths (/tmp/tmp*, /tmp/q31-g8-deploy-*, sbA/helm-legs-*); a normalisation over those shapes gives residual 0, one normalised digest 49eb85e9a1461769…
G11-contract twin rc 1 / 194 / 1 — see §5, the sole failure is the twin's own artifact
drift probe sandbox pin mutated to 0*64 -> collect #3 manifest_match: false -> render #3 red drift pill -> gate rc 0 / 195 / 0; pin restored, content sha256 asserted equal to the post-pin value
outside verifier curl -sS --max-time 20 https://www.stackchainmagazine.net/ | sha256sum -> c34ae2c245117ca7…, 217,163 B: == snapshot live_body_sha256_wire, == held pin, != live_body_sha256 (e40308e5…)

Arm B — the control, the bug reproduced on this instrument

leg result
collect #1 manifest_match: false (normalised live e40308e5… vs held f27326e8…)
--pin pinned e40308e573d046d7d503acecbfbde82b8ccd10758a345b0440d55b796e2d821e — the CRLF-folded digest, != the wire digest
collect #2 manifest_match: true (both sides folded)
render #2 green "match" pill, published hash e40308e573d046d7
gate on render #2 rc 1 / 194 passed / 1 failed, sole failure G13 row 2: "an unqualified green pill is confirmed by a fresh fetch" — live_sha256=c34ae2c2… held_pin_digest=e40308e5… pin_matches_live_now=False

That is Run 38's 194/1, reproduced here with the same gate on the same host, differing only in the collector — the before/after pair the fix needed.

4. The incidental catch

The byte-stability script re-ran the parity gate after the drift probe, when the sandbox page was still render #3's red pill but the pin had been restored to the live digest. Both runs returned rc 1 / 194 / 1 with the sole failure G13 row 3: "a red pill is confirmed by a fresh fetch too" — unqualified_red_pill=True live_sha256=c34ae2c2… held_pin_digest=c34ae2c2… pin_matches_live_now=True. Row 3 was demonstrated in Run 35 on a constructed cell; here it fired on a real state transition the sandbox itself produced (re-pin after a drift render). The failure is the sandbox's page being one render behind its pin, not a defect in the fix.

5. The one prediction miss, and it is the twin's own artifact

A-P7 (the G11-contract twin at 195/0) measured 194/1. The sole failure is the G14 discriminator row: "the G14 predicate discriminates: the adoption payload's own stale output (G11, 306d4bee) v…". The twin redirects the pre-G14 renderer path (/root/crucible/q59/helm-render-g11.py) to the sandbox bin renderer — which is the payload renderer now — so the discriminator's negative leg (the OLD renderer's stale output must lack the hash-line qualifier) collapses by construction: both legs render with the fixed renderer and both carry the qualifier. Every G11 contract row itself passes. Run 38 logged the identical artifact on the identical twin ("The G11 twin's second failure is Merlin's own artifact: its negative leg renders the same scratch path the twin redirected"). The prediction was over-optimistic because it was carried from Run 38's docstring, where it was also not met. A twin that makes the two renderers identical cannot demonstrate a discriminator between them; the honest unit is the parity gate (195/0, §3) plus the control arm's 194/1 (§3), which between them isolate the fix.

6. Live, deployed, sandboxes, disk

Live delta 0 across every leg of both arms (bytes + sha256 + mtime_ns, censused before and after every leg): deployed page 9,865 B / 427b7b54c69b18d2… / mtime_ns 1791709034017837233; snapshot 14,039 B / aa842b0fd88037f8… / mtime_ns 1791479579475780303; held pin 120 B / 5b6d70716153c3f4… / mtime_ns 1791478687099553071; the five deployed bin digests unmoved (ee0b36a82f23…, 5c67473d8928…, 7ed1bca8f9da…, 12570786ba70…, 738699986778…). grep -c HELM_COLLECTOR /root/.helm/bin/helm-selftest.py = 0, so Q40 stands and none of Q31–Run 39 is in production. /tmp/q31-g8-deploy-* census 475 before and after — the q65 gate's Edit B holds. Sandbox dirs created by the runs and not cleaned by the gate: 0 of 0.

7. What this does to the adoption ask

The ask was: cp the payload files, without a --pin first, acceptance rc 0 / 195 / 0 on a red drift page — because a green pill could not ship green while the site serves CRLF. That constraint is gone. Arm A measured the full sequence with the re-pin: --pin -> collect -> render -> gate = rc 0 / 195 / 0 on a page carrying a green pill, and the pill's reference is now reproducible by any stranger with curl. Both adoption variants are green:

  • no --pin: red drift pill, honest, 195/0;
  • --pin first: green pill, 195/0 — the pin accepts the current live body as the drift reference, which is a policy choice, not a technical one.

Adoption sequence, updated (four files, one order): adopt renderer 117001e8… (23,523 B, superseding a70998c8…) -> adopt collector 5a99364b… (31,595 B, superseding 4e45021d…) + gate 833d0827… (190,330 B, unchanged) -> re-collect -> re-render -> deploy. The file list must be re-read at adoption time, never copied from an earlier block. The cp itself remains Alexander's under §4.

Backward compatibility, stated: a held pin written by the old convention (e.g. today's f27326e8…) simply does not equal the wire digest, so manifest_match reads False until a re-pin — the same state as today, since the live pin is stale anyway. Nothing is deleted; live_body_sha256 keeps its meaning and both digests remain in every snapshot.

8. Limits

  • The rehearsal's page is rendered from the sandbox snapshot while the gate's producer legs render from the live snapshot (the live snapshot cannot be written from here); the live fetch inside G13 rows 2 and 3 is a real network read of the snapshot's own URL.
  • The gate was run six times in arm A and twice in arm B; the 190 KB gate was not re-run for the parity rows beyond those, and the failing-row text is quoted from the recorded runs.
  • The wire convention assumes the served bytes are the bytes to pin. A site that re-encodes per response (gzip variants, chunk boundaries) would make even the wire digest unstable; today's body is byte-stable across every reading this lane has taken (217,163 B / c34ae2c2… since 2026-10-14).
  • A subject that reads the held pin and re-writes it through a different fetch path (e.g. a proxy that normalises newlines) would reintroduce a split; the gate's G13 rows are the detector for exactly that, and they now pass only when both sides agree.

9. Correction, Run 40 -- the byte-stability cell paired one run's bytes with

another run's diff

Returned by the control plane at 2026-10-15T17:41Z: the arm-A table's cell gate repeat (byte-stability) read "rc 0 / 195 / 0; the two raw stdouts are 17,027 B each and differ on exactly 4 lines". The byte count and the rc/tally belong to one run; the 4-line diff belongs to another. Re-measured from the artifacts on 2026-10-15 ~18:3xZ, not accepted from the reply:

  • The only raw stdout pair on disk is out/R39-STABILITY-{1,2}.txt: 17,318 B each, both rc 1 / 194 passed / 1 failed -- the section 4 incidental-catch re-run after the drift probe, not the acceptance repeat. Per-run stdout sha256 b93549cf1ef860bc... and 0dd6922a53a52d20..., both equal to the values out/R39-STABILITY.json records for those two runs; the diff is exactly 4 lines (1, 10, 209, 210), all per-run mkdtemp paths (/tmp/tmp*, /tmp/q31-g8-deploy-*, sbA/helm-legs-*); normalised residual 0, normalised digest 49eb85e9a1461769... -- every field re-read from the artifact and the two captures this run.
  • The 17,027 B / rc 0 / 195 / 0 figures are the five gate_parity* legs of out/RUN39.json -- arm A gate_parity_render1 (379cfc4e...), gate_parity_render2 (799e3e21...), gate_parity_render2_repeat (88fbede4...), gate_parity_render3 (f8df74cc...), and arm B's gate_parity_render1 (fc26907c...): every one 17,027 B / rc 0 / 195 / 0 with distinct digests. Four arm-A legs cover three distinct gate states plus render #2's own repeat, and the fifth is arm B's control leg. Their raw stdouts were not the pair that was diffed; the one same-configuration repeat on record (arm A render2 vs render2_repeat, 799e3e21... vs 88fbede4...) was not captured to disk, so the 4-line diff cannot be attributed to it from any artifact that exists.
  • No verdict, digest, row or count in sections 1-8 moves. The 4-line count, the residual 0 and the normalised digest are correct measurements of the 17,318 B pair; only the byte count and the rc/tally beside them belonged to another run. Section 4 already states the stability pair's rc 1 / 194 / 1 correctly -- the arm-A table cell is the sole mispairing.
  • Erratum form, per this lane's correction rule: the Run-39 commit message (28ca8e5) and the queue's Run-39 block carry the same sentence. A commit message cannot be edited, and the queue is append-only, so both stand and are corrected by this section. Nothing above this append was touched.