Files
troupe-standup/recoveries/helm-pin-pill-staleness.md
Merlin defe730677 queue+record+checkin+reply 56: reply 55's two-locus citation fix discharged
Both loci that cited out/R29-MEASURE.json are fixed -- the record's own :25 (the primary) and the candidate renderer's comment -- while the artifact sits at /root/crucible/q59/R29-MEASURE.json (5382 B / d6f142c0e7a7acca). One comment line, anchor unique, compile() before the write, the reply-55-verified pre-image preserved byte-exact at q59/helm-render-g11-pre30.py; post-fix renderer 22731 B / 306d4befee7c9874 / 512 lines, -4 B. Comment-only proved by the emitted bytes: fixed stale render 11819 B / 951a8a7645fe7ef4 byte-identical to Run 29's check-stale.html, fixed fresh 11624 B / 1eec213e9e363d36 identical to check-fresh.html and to the plain renderer at the same clock, plain stale control 11806 B / 3f48643d8e7bac77 -- so the pill's +13 B one-span difference is unchanged and the G11 gate's rows 1-3 stand by byte identity (the 161 KB gate was not re-run; the inference is labelled). Live delta 0 on bytes/sha256/mtime_ns for all four live objects across all four invocations; sandboxes 0; HELM_COLLECTOR still 0, so Q40 stands and nothing is in production. Adoption object superseded: renderer 306d4bee (22731 B) before the cp of collector 4e45021d + gate 125754b270bf. Reply 54's three corrections re-measured and upheld (496/512 by wc -l; twin line 2889; match_qualifier :327-330). Nothing deployed, no another-party file edited, decisions/ and inbox/ untouched, no secret printed.
2026-10-14 18:36:32 +00:00

20 KiB
Raw Permalink Blame History

The pin pill that outlived its reading

Helm lane · Run 29 · 2026-10-14 · Merlin. Scratch tree /root/crucible/q59/; nothing deployed, no cp into /root/.helm/bin/, no write to the live document root, no --deploy anywhere, decisions/ and inbox/ untouched, no another-party file edited, no secret printed. Clock: R29-MEASURE.json generated_at 2026-10-14T15:53:07+00:00, R29-GATE.json generated_at 2026-10-14T15:59:55+00:00 — both read from the artifacts, not approximated.

Unit taken. No unclaimed queue item was executable and none was invented: Q42 is zephyrus-sparkborn's (closed), Q52 Mercury's (closed), Q53 Mercury's (open), Q9/Q13/Q19 gated on Alexander, and the queue's own hygiene ledger records the Helm executable set empty. The unit is the half of Run 28's finding the fix does not reach — named by the control plane's own re-derivation of G10 (WORK-QUEUE.md, 2026-10-14, commit 7ec2172: "the match/drift pill still reads the frozen manifest_match field (renderer :323/:326), so a re-rendered page would show a green 'match' beside 'as of … not re-checked since' while the pin provably no longer matches the live body — internally inconsistent, and the false claim survives"). Re-measured here before any edit; confirmed.


1. The finding, measured

/root/crucible/q59/measure_r29.py → out/R29-MEASURE.json (generated_at 2026-10-14T15:53:07+00:00). Read-only; the only writes are its own scratch dir.

The pin, and the body it is pinned against, today.

object value
held pin /root/.helm/data/held/stackchain-home.html.sha256 120 B, sha256 5b6d70716153c3f4bd82045d5d71edf88f793a240ce1275c3dfca5759d0ee297, mtime_ns 1791478687099553071, ctime_ns 1791957230761646950
pin text f27326e8b13d1dec9eb158fe40e5091f79dcaf73b47312c4e7d3958ba1d95d18 stackchain-home.html pinned 2026-10-08T16:58:07.100Z
live body https://www.stackchainmagazine.net/ (fetched this run) HTTP 200 / 217,163 B / sha256 c34ae2c245117ca73ba5720b2c22b6fab689840070ff68cda7bffe3e723986d4
pin_matches_live_now false

What the renderer is handed. Snapshot /root/.helm/data/snapshot.json 14,039 B / sha256 aa842b0fd88037f83115b4a54d617f27666f4de06ef84f9a64c8fd0a5f04656d / mtime_ns 1791479579475780303, generated_at 2026-10-08T17:12:59.475Z; its stackchain_site block reads manifest_match: true, live_body_sha256: f27326e8b13d1dec…, evidence.observed_at: 2026-10-08T17:12:59.458Z, http_status: 200, rtt_seconds: 0.545. The collector's own threshold for this metric is 3600 s (STALENESS_THRESHOLDS_S["stackchain_site"], read from the collector module, not assumed).

The renderer's own output, at two pinned clocks, before any edit. The deployed renderer /root/.helm/bin/helm-render.py 21,766 B / sha256 5c67473d89283c3d4434928dc96d45ff48a41d8f443ecc3c98e9236be8b597f9 / mtime_ns 1791717163756240575, run through its own CLI:

$ python3 /root/.helm/bin/helm-render.py --out <scratch> --as-of 2026-10-14T16:00:00.000Z
rendered -> … (11806 bytes)
render clock 2026-10-14T16:00:00+00:00  snapshot age 5d 22h 47m
verdict at render: 0 green / 6 not green  (5 stale, 1 unreachable)
  stackchain_site    state=OK           freshness@render=STALE

and the emitted cell, verbatim:

<div class="kv">live body sha256 <span class="mono">f27326e8b13d1dec…</span></div>
<div class="kv">held copy <span style="color:#22c55e">match</span> (pinned vs live)</div>
<div class="kv dim">as of 2026-10-08T17:12:59.458Z · 5d 22h 47m ago · not re-checked since</div>

So a render today prints green match beside not re-checked since, in a page whose own verdict pill reads 0 green / 6 not green — while the pin is, measured this run, false. The frozen manifest_match field is not merely old; it asserts an equality the live body no longer satisfies.

The control plane's line numbers check out and are cited with their content, not from memory: helm-render.py:323 is match_color = "#22c55e" if site.get("manifest_match") else "#ef4444" and :326 is match_label = "match" if site.get("manifest_match") else "drift" — both read the frozen field, while :327-329 builds the qualifier from evidence.observed_at and the freshness re-derived at render. The comment above them states the rule ("Say so, with its clock, rather than presenting it as a live tamper signal — the renderer cannot re-fetch") and the colour asserts the opposite of it.

The deployed page /var/www/alexander-whitestone/helm/index.html (9,865 B / sha256 427b7b54c69b18d270288910fc810732463129f48ec4bdd7dbc75cdb22997ce4 / mtime 2026-10-11T08:57:14.017Z, mtime_ns 1791709034017837233) carries neither the qualifier nor the pill's condition — as of false, not re-checked since false — which is Run 28's G10 row and stays red. The defect measured here is the one a re-render would publish, i.e. the one that stands between G10's remedy and an honest page.

2. Why the self-test could not see it

G10's four checks read the qualifier; none reads the pill. G5's manifest checks run on a synthetic fixture at a pinned clock. Nothing in the lineage asserted that the pill's verdict is a function of the reading's freshness — the exact shape of the Q34/Q40 law this lane keeps meeting: a gate that tests the producer cannot vouch for the claim in the artifact.

3. The fix

/root/crucible/q59/patch_render_r29.py → helm-render-g11.py. The deployed renderer is copied byte-for-byte (sha256sum asserted equal before the edit; the plain copy helm-render-plain.py is kept for the discriminator), then one edit: the pill's colour and label become a function of site_fresh alongside the frozen field.

    if match_ok and site_fresh == "FRESH":
        match_color, match_label = "#22c55e", "match"
    elif match_ok:
        match_color, match_label = STATE_COLORS["STALE"], "match (unverified)"
    else:
        match_color, match_label = "#ef4444", "drift"
bytes sha256 lines
deployed renderer (pre-image) 21,766 5c67473d89283c3d4434928dc96d45ff48a41d8f443ecc3c98e9236be8b597f9 497
q59/helm-render-plain.py (control) 21,766 identical 497
q59/helm-render-g11.py 22,735 1e82a1818c57cc9796855ac8ab9de006a3647748596126482ced5df3b5ad3f7a 513

Delta +969 B / +16 lines (the edit and its comment). Asserted by the patcher: the anchor line occurs exactly once; compile() on the source before any write; the trailing newline survives; SNAPSHOT, OUT_DEFAULT, if is_prod and not deploy: and STATE_COLORS["STALE"] each occur exactly once afterwards, so the destination gate and the snapshot binding are untouched.

The emitted bytes, both branches (out/R29-FINAL.json):

render renderer clock bytes sha256
stale branch unpatched --as-of 2026-10-14T16:00:00.000Z 11,806 3f48643d8e7bac77020674198e6be78a35f27a0fec71b400d718b52b88ca4ad7
stale branch fixed same 11,819 951a8a7645fe7ef42247de97d37ab10c99f89a9c05bff3fe8f36177456936485
fresh branch unpatched --as-of 2026-10-08T18:00:00.000Z 11,624 1eec213e9e363d36135123892d385e5961d170aa80fd7f60ed6aadca28360e3d
fresh branch fixed same 11,624 identical

The stale branch moves +13 B in exactly one span — first differing character at offset 8910, held copy <span style="color:#22c55e">match</span> → held copy <span style="color:#f59e0b">match (unverified)</span> (q59/diffcheck.py). The fresh branch is byte-identical, so the edit conditions the pill and does not touch the page's legitimate case.

4. The gate

/root/crucible/q59/make_gate_q59.py → helm-selftest-q59.py, four checks on Q45S10 + G10's gate (q58/helm-selftest-q58.py 153,469 B / 3b98b62fa5c7… / 2,916 lines / 131 registrations, digest re-asserted before the copy):

gate bytes sha256 lines registrations
q58 (pre-image) 153,469 3b98b62fa5c7ea626091f6d0f63566de79818ae414bcffe323dd1ddd53956158 2,916 131
q59/helm-selftest-q59.py (G11) 161,122 125754b270bf6b0f55911f407fa9c36c6161b45a71e646fa36a70db096d7de93 3,053 135
q59/helm-selftest-q59-twin.py 161,124 2b0d38f3d7a9a2117ca7540eb21ba92cbfccfcb55190318638bf2135f4c7847b 3,053 135

+7,653 B / +137 lines / +4 registrations. The four rows:

  1. The contract, on real data — the real snapshot through the fixed renderer's own CLI at a pinned stale clock: rc 0, the unqualified green match absent, the amber match (unverified) present, not re-checked since present.
  2. The fresh branch is not over-refused — the same renderer at --as-of 2026-10-08T18:00:00.000Z (site FRESH: threshold 3600 s, reading 47 m old): the pill is still the green match, not re-checked since absent.
  3. The discriminator, at renderer level — the unpatched renderer, the same snapshot, the same pinned stale clock, still prints the unqualified green match. Rows 1–2 therefore measure the edit, not the clock.
  4. Read-only — bytes + sha256 + mtime_ns on four live objects (deployed page, snapshot, held pin, deployed renderer) around all three legs. Unlike G10's row this is four, because the renderer reads three of them; the live paths in this block are census targets, and row 4 is the assertion that none moved.

No check() was removed or renamed, so G10's four rows and every pre-existing row are untouched; 131 → 135 is purely additive.

5. The matrix

/root/crucible/q59/run_gate_q59.py → out/R29-GATE.json (generated_at 2026-10-14T15:59:55+00:00), every invocation with HELM_COLLECTOR=/root/crucible/q48/helm-collect.py (subject 4e45021d…, 29,954 B / 720 lines) and a live census before and after.

Prediction, stated before the run: shipped rc 1 / 184 passed / 1 failed, the one failure being G10's deployed-page row; twin rc 1 / 183 passed / 2 failed, that row plus G11 row 1. Met exactly.

cell rc suite stdout the G11 rows
shipped ×1 1 184 passed / 1 failed 15,667 B 4 × PASS
shipped ×2 1 184 passed / 1 failed 15,667 B 4 × PASS, identical to run 1
twin 1 183 passed / 2 failed 15,786 B row 1 FAIL (rc=0 out_bytes=11806 out_sha256=3f48643d8e7bac77 unqualified_green=…), rows 2–4 PASS

The twin's only edit is asserted to be one line (make_twin_q59.py: differing line 2888, _g11_renderer repointed at the plain copy; both files 3,053 lines). The G11 rows are byte-identical across the two shipped runs; the two stdouts differ on 4 lines, all of them per-run mkdtemp tokens, and after normalising the three shapes actually present (/tmp/tmp*, /tmp/q31-g8-deploy-*, helm-legs-*) they are byte-identical — sha256 ea12512ef4f653ab85d75d64d626cace849fac3e0313ad4c6a0466b08aa95a16, residual differing lines 0 (q59/normstdout.py).

Live delta {} on bytes / sha256 / mtime_ns and ctime_ns for all four objects across all three invocations. Sandboxes 0/0 both ways (shallow glob and recursive rglob) before and after each.

6. Live, deployed, sandboxes, disk

Unchanged and re-read at the end of the run (out/R29-FINAL.json): snapshot 14,039 B / aa842b0fd88037f8… / mtime_ns 1791479579475780303 / ctime_ns 1791957230760646867; held pin 120 B / 5b6d70716153c3f4… / mtime_ns 1791478687099553071 / ctime_ns 1791957230761646950; deployed page 9,865 B / 427b7b54c69b18d2… / mtime_ns 1791709034017837233; deployed renderer 21,766 B / 5c67473d89283c3d…. Deployed /root/.helm/bin/ untouched — five digests re-read: helm-collect.py ee0b36a82f23…, helm-render.py 5c67473d8928…, helm-selftest.py 7ed1bca8f9da…, helm-validate.py 12570786ba70…, summarize.py 738699986778… — and grep -c HELM_COLLECTOR /root/.helm/bin/helm-selftest.py = 0, so Q40 stands and neither G10 nor G11 is in production. Sandboxes 0 in the q59 tree. Disk 45 G free of 154 (72 %), df -h /.

7. Limits, stated

  • The renderer still cannot re-fetch (its own docstring: "zero external requests"), so the fix marks a stale reading unverified rather than re-measuring it. The pill's honesty is bounded by the collector's clock; only a re-collect can make the claim current again.
  • The snapshot's frozen manifest_match: true is still wrong about today. Nothing in the renderer can correct a field; that is the collector's, and it is the re-collect in the sequence below.
  • The deployed page remains the old generation, so G10's red row is unchanged and the public page still carries neither the qualifier nor the conditioned pill.
  • The label match (unverified) is the renderer's own vocabulary — it adds no new term beyond the not re-checked since line already on the page.

8. Defects of mine, logged first

  1. The stdout normalisation in run_gate_q59.py used two of the three mkdtemp shapes and reported stdout_identical_after_sandbox_normalisation: false with sha d9adcf2b…; the missed shape was /tmp/q31-g8-deploy-*. Corrected in final_r29.py, which re-writes the two fields in out/R29-GATE.json in place and records the correction beside them: true, sha ea12512ef4f653ab85d75d64d626cace849fac3e0313ad4c6a0466b08aa95a16, residual 0. The lane's own pattern-written-from-memory class, reproduced on a fresh artifact.
  2. Nothing else. No figure, digest, line count, registration count or verdict in §1–§7 depends on the defect above; the prediction in §5 was written before the run and met.

9. Adoption — Alexander's, and now a sequence rather than a decision

The §4 cp pair has moved: collector 4e45021d80395d3f… + gate 125754b270bf6b0f55911f407fa9c36c6161b45a71e646fa36a70db096d7de93 (161,122 B / 3,053 lines / 135 registrations), superseding 3b98b62fa5c7… (G10), c56230e5a075… (Q45S10), cd43284ff009… (Q45S9), 78b290ecf2d9… (Q52), e4967da9… (Q51), a951dbe8… (Q50). And the renderer must be adopted before any re-render: helm-render-g11.py 1e82a1818c57… (22,735 B) replacing the deployed 5c67473d8928… (21,766 B). Without it, the re-render Run 28 asked for would deploy the internally inconsistent page this record measures. The order is therefore: adopt renderer → adopt collector + gate → re-collect → re-render → deploy, with the manifest_match pin refresh falling out of the re-collect. Beside it, unchanged: the Q13 publication call, Q9's checksum handover, Q19's onboarding.


Run 30 — reply 55's two-locus citation fix, discharged (Merlin, 2026-10-14)

Reply 55 (replies/2026-10-14-to-merlin-55.md, Timmy, 2026-10-14T16:22Z) found the out/R29-MEASURE.json path in two loci — the record's own :25 and the candidate renderer's comment — while the artifact sits at /root/crucible/q59/R29-MEASURE.json. Both are now fixed; the record's line stands above, superseded here, per the append-only rule. Everything below is measured this run on this host.

1. The artifact, re-measured rather than relayed

/root/crucible/q59/R29-MEASURE.json — 5,382 B / sha256 d6f142c0e7a7acca651c3fb040d486d5860e8d4f5aa8273e835854bc9b678cf1, generated_at 2026-10-14T15:53:07+00:00. q59/out/ holds exactly four files — R29-GATE.json, R29-FINAL.json, R29-READBACK.json, R29-READBACK-FINAL.json — so the cited out/ prefix resolves to a directory that does not contain the measurement. The record's :25 and the renderer's comment were both wrong in the same way; the renderer's was the one this lane flagged, and the record's is the one a reader of the evidence chain follows first, which is why reply 55 called it the primary.

2. The renderer edit — one comment line, and its proof is the emitted bytes

The pre-fix renderer was asserted equal to the artifact reply 55 verified before the edit (sha256 1e82a1818c57cc9796855ac8ab9de006a3647748596126482ced5df3b5ad3f7a), and preserved byte-exact as q59/helm-render-g11-pre30.py. One line changed (grep -c "out/R29-MEASURE.json" on the post-fix file returns 0; the anchor occurred exactly once before the write; compile() ran before any byte was written):

-    # (/root/crucible/q59/measure_r29.py -> out/R29-MEASURE.json): the pin is
+    # (/root/crucible/q59/measure_r29.py -> R29-MEASURE.json): the pin is
bytes sha256 lines (wc -l)
pre-fix (reply 55's artifact, preserved at q59/helm-render-g11-pre30.py) 22,735 1e82a1818c57cc97… 512
post-fix q59/helm-render-g11.py 22,731 306d4befee7c9874edc6a7467945420e94aa6a0f0445e1955641f07bc046c8a4 512

−4 B — out/ removed — and the source diff is exactly two lines (one -, one +).

Comment-only, proved by the output and not by the diff. Both branches re-rendered at their Run-29 clocks (--as-of 2026-10-14T16:00:00.000Z stale, --as-of 2026-10-08T18:00:00.000Z fresh), each rc 0:

render bytes sha256 equals
fixed, stale clock 11,819 951a8a7645fe7ef42247de97d37ab10c99f89a9c05bff3fe8f36177456936485 Run 29's check-stale.html, byte-identical
fixed, fresh clock 11,624 1eec213e9e363d36135123892d385e5961d170aa80fd7f60ed6aadca28360e3d Run 29's check-fresh.html and the plain renderer at the same clock, byte-identical
plain, stale clock (control) 11,806 3f48643d8e7bac77020674198e6be78a35f27a0fec71b400d718b52b88ca4ad7 Run 29's render-stale.html

The stale branch still moves +13 B in exactly one span against the plain control, so the pill behaviour the G11 gate's rows 1–3 assert is unchanged: rows 1–3 compare emitted bytes at pinned clocks, and those bytes are byte-identical to the verified ones. The gate itself was not re-run (161 KB, standing rule), and that inference is stated as an inference.

Live state, delta 0 on bytes/sha256/mtime_ns for all four objects (deployed page 427b7b54c69b18d2… / 9,865 B, snapshot aa842b0fd880… / 14,039 B, held pin 5b6d70716153c3… / 120 B, deployed renderer 5c67473d89283c3d… / 21,766 B) across all four invocations; sandboxes 0; grep -c HELM_COLLECTOR /root/.helm/bin/helm-selftest.py = 0, so Q40 stands and nothing is in production; disk 41 G free of 154 (74 % used). Instrument: q59/fix_citation_r30.py, artifact out/R30-CITATION-FIX.json.

3. The adoption object, superseded

§3's table (:117) and §9's sequence (:247) carry 1e82a1818c57… / 22,735 B. Both stand, and both are superseded by 306d4befee7c9874edc6a7467945420e94aa6a0f0445e1955641f07bc046c8a4 / 22,731 B. The §4 sequence is unchanged in order — adopt renderer 306d4bee… → adopt collector 4e45021d… + gate 125754b270bf… → re-collect → re-render → deploy — and the gate's digest is unaffected: the gate file itself did not move, and its G11 rows assert the emitted bytes this run re-measured as identical.

4. Reply 54's three corrections, accepted and re-measured on this instrument

Logged in the pipe's own order, each verified here rather than relayed:

  • (C1) The renderer line counts are 496 and 512, not 497 and 513. wc -l gives 496 on the plain copy and 512 on the pre-fix candidate; the record's figures are len(text.split("\n")) parts counts in a table whose gate counts are newline counts — the unit class this lane logged in Run 24, on a fresh artifact. The +16-line delta holds either way (512 − 496 = 16), so nothing downstream moved.
  • (C2) The twin's differing line is 2889, not 2888. diff q59/helm-selftest-q59.py q59/helm-selftest-q59-twin.py → 2889c2889; §6 (:189) says 2888, the 0-based index. The one-line-different claim itself holds.
  • (C3) The qualifier assignment closes at :330, not :329. grep -n match_qualifier /root/.helm/bin/helm-render.py → the site assignment opens at :327 and its closing paren is :330; §0's :77 citation says :327-329.

5. The fresh-branch rule, adopted

Reply 55's caveat is accepted as a standing rule for this record: a fresh page's bytes depend on the render clock and on how the fresh reading was made, so the durable claim is the invariant — the fixed renderer is byte-identical to the plain one when the reading is fresh — and never the number. §3's fresh-branch figures (11,624 B / 1eec213e…) are a dated reading at --as-of 2026-10-08T18:00:00.000Z; this run's fresh renders reproduce them exactly at that clock, which is the reproducibility claim, not a claim that a fresh page is always those bytes.

— Merlin (Run 30), 2026-10-14