Files
troupe-standup/recoveries/helm-deployed-page-generation.md
Merlin 7af50b5bcd queue+record: G10 -- the deployed Helm page is a stale generation of the renderer, and the self-test cannot see it
The Helm's pinned site now reads HTTP 200 / 217,163 B / sha256 c34ae2c245117ca7...
while the held pin is f27326e8b13d1dec... pinned 2026-10-08T16:58:07.100Z, so
pin_matches_live_now is false. The deployed page /var/www/alexander-whitestone/helm/index.html
is 9,865 B / sha256 427b7b54c69b18d270288910fc810732463129f48ec4bdd7dbc75cdb22997ce4 / mtime
2026-10-11T08:57:14.017Z and renders a green "match" with no "as of" and no "not re-checked
since". The renderer carrying that fix is mtime 2026-10-11T11:12:43.756Z, 8,129.7 s later, and
the real snapshot through it (--out scratch --as-of 2026-10-14T00:00:00.000Z, rc 0) renders
11,798 B / sha256 f32191223931e74b... carrying "as of 2026-10-08T17:12:59.458Z . 5d 6h 47m ago
. not re-checked since". The fix exists, works, and was never deployed.

Why the self-test was green: helm-selftest.py:420-421 runs the qualifier check on a synthetic
fixture at a pinned clock (hr.SNAPSHOT = fp), and :422-425's "build_page() writes nothing"
only proves build_page did not overwrite production. Neither reads the deployed artifact --
the Q34/Q40 law in a new place: a gate that tests the producer cannot vouch for the artifact
in the document root.

The fix is gate G10, four checks, on Q45S10's c56230e5a075... (147,151 / 2,798 / 127):
helm-selftest-q58.py 153,469 B / sha256 3b98b62fa5c7ea626091f6d0f63566de79818ae414bcffe323dd1ddd53956158
/ 2,916 lines / 131 check(" registrations. Shipped (reads the live page): rc 1, 180 passed /
1 failed, stdout 15,055 B / e8304379529c4c83..., the one FAIL being "the deployed page carries
the manifest qualifier". Twin (the same gate with _g10_deployed repointed at a scratch
current-generation render, ast-asserted): rc 0, 181 passed / 0 failed, ALL GATES PASS, stdout
14,824 B / fe8c8c72825962d9.... Prediction stated before the run and met exactly -- a red row
that cannot go green would prove nothing.

Live state delta 0 across all four invocations on bytes + sha256 + mtime_ns: page 9,865 B /
427b7b54c69b18d2... / 1791709034017837233; snapshot 14,039 B / aa842b0fd880... /
1791479579475780303; held pin 120 B / 5b6d7071... / 1791478687099553071. Sandboxes 0/0 both
ways (shallow glob and recursive rglob). No --deploy anywhere in the G10 block. Deployed bin
unchanged, five digests re-read, grep -c HELM_COLLECTOR = 0 so Q40 stands and G10 is not in
production.

Two shipped runs are both 180/1 at 15,055 B while byte_stable_stdout reads false; the
difference is mkdtemp suffixes in the sandbox path the gate prints. The four G10 rows are
byte-identical across both runs, and normalising the four path patterns gives stdout sha256
1958d8212fa60e44... on both with residual differing lines 0.

Six defects of mine are logged first in the record: two claims in one cell (the first G10
check 3 asserted both the predicate's soundness and the finding, which would have made the
twin 180/1 and hidden the discriminator); a report filter written from memory
(startswith("PASS") never matches the two-space rendering -- the lane's own logged class
reproduced on a fresh artifact); a live-path assertion that fired on G5's read-only check and
then on G10's own comment; byte_stable_stdout: false reported as found with the cause
measured; ast.unparse's single quotes; and pathlib.Path in a gate that imports
from pathlib import Path.

No queue item was opened: the Helm executable set was empty at the start of the run (Q49's
"Status: open and unclaimed" at :6847 is stale -- this file's own hygiene ledger at :8026
records Q49 CLOSED), so the unit taken was the one defect the lane had measured and labelled
but never fixed. Owed to Alexander, both decisions rather than tasks: re-render and re-deploy
the Helm page, and the section-4 adoption cp of collector 4e45021d... + gate 3b98b62fa5c7ea62....

Append-only, prefix asserted on bytes before each write, trailing newline asserted on the
written files: queue 1,060,143 -> 1,065,465 B (+5,322, sha256 9f0a02b429eb7ed5...), check-in
58,536 -> 65,984 B (+7,448, sha256 0af2ecd2aaaee0f0...), record 9,953 B / sha256
bdd5fa1c03586c28.... No another-party file edited, decisions/ and inbox/ untouched, no secret
printed.
2026-10-14 13:27:05 +00:00

9.7 KiB
Raw Permalink Blame History

G10 — the deployed Helm page is a stale generation of the renderer, and the self-test cannot see it

Merlin · Helm lane · Run 28 · 2026-10-14T13:0x–13:2xZ · this host Scratch tree /root/crucible/q58/. Nothing deployed: no cp into /root/.helm/bin/, no write to the live document root, no --deploy anywhere in the G10 block. decisions/ and inbox/ untouched, no another-party file edited, no secret printed.


1. The finding, measured

The Helm's stackchain_site card publishes a tamper/drift signal: the live body's sha256 compared against a pinned copy. A pin comparison is a point-in-time reading. The renderer was fixed (2026-10-11) to say so, on its own clock. The fix was never deployed, and the page the public reads still carries the defect the fix removed.

# what measured
1 live pinned site GET https://www.stackchainmagazine.net/ → HTTP 200 / 217,163 B / sha256 c34ae2c245117ca73ba5720b2c22b6fab689840070ff68cda7bffe3e723986d4 (UA curl/8.5.0)
2 the held pin /root/.helm/data/held/stackchain-home.html.sha256 → 120 B / sha256 5b6d70716153c3f4bd82045d5d71edf88f793a240ce1275c3dfca5759d0ee297, text f27326e8b13d1dec… stackchain-home.html pinned 2026-10-08T16:58:07.100Z
3 pin vs live, now pin_matches_live_now: false — f27326e8… ≠ c34ae2c2…
4 what the snapshot claims manifest_match: true, live_body_sha256: f27326e8…, observed_at: 2026-10-08T17:12:59.458Z (snapshot 14,039 B / aa842b0fd880…)
5 the deployed page /var/www/alexander-whitestone/helm/index.html → 9,865 B / sha256 427b7b54c69b18d270288910fc810732463129f48ec4bdd7dbc75cdb22997ce4 / mtime 2026-10-11T08:57:14.017Z; renders <span style="color:#22c55e">match</span>; as_of=False, not_rechecked=False
6 the renderer that carries the fix helm-render.py 21,766 B / sha256 5c67473d89283c3d4434928dc96d45ff48a41d8f443ecc3c98e9236be8b597f9 / mtime 2026-10-11T11:12:43.756Z — 8,129.7 s later than the deployed page
7 the real snapshot through the real renderer python3 /root/.helm/bin/helm-render.py --out <scratch>/helm-scratch.html --as-of 2026-10-14T00:00:00.000Z → rc 0, 11,798 B / sha256 f32191223931e74b421c1412833d6f225385f01d6ab2ff6dd4a38b414f25bccc, carrying as of 2026-10-08T17:12:59.458Z · 5d 6h 47m ago · not re-checked since

So the live page states, in green, that a pin matches — against a pin that measurably does not match — with no clock and no disclaimer. The fix exists in the source tree, works, and was never rendered into the document root.

Why the self-test was green

  • helm-selftest.py:420-421 — check("G5", "the manifest claim carries its own clock and says it was not re-checked", "not re-checked since" in html and "as of 2026-10-08T17:12:59.000Z" in html) — runs on a synthetic fixture (fp) at a pinned clock, with hr.SNAPSHOT = fp.
  • helm-selftest.py:422-425 — check("G5", "build_page() writes nothing to the live document root", Path("/var/www/…/index.html").read_bytes() != html.encode("utf-8")) — proves only that build_page did not overwrite production. It never compares the deployed page against the current renderer.

A gate that tests the producer cannot vouch for the artifact in the document root. That is the class, and it is the same law Q40 and Q34 were filed under, in a new place: the fix's own test passes while the fix is absent from production.

2. The fix — gate G10, four checks

On the Q45S10 gate c56230e5a075c5a12fd2f0d336dcac71c1078cb692a9e6c995780098a051f13e (147,151 B / 2,798 lines / 127 registrations), inserted before the report block, anchor # ---------------------------------------------------------------- report asserted count == 1.

gate bytes sha256 lines check("
helm-selftest-q58.py 153,469 3b98b62fa5c7ea626091f6d0f63566de79818ae414bcffe323dd1ddd53956158 2,916 131 (127 + 4)

No --deploy anywhere in the G10 block (g10_block_has_deploy_flag: false). Every render goes to --out inside the gate's own scratch dir.

  1. The contract, on real data — the real snapshot through the real renderer's CLI at a pinned clock carries the qualifier. PASS
  2. The deployed artifact — the page the public reads carries it. FAIL
  3. The discriminator — the predicate returns true on a current-generation render and false on a body with no qualifier. PASS
  4. Read-only — bytes + sha256 + mtime_ns identical around the gate. PASS

The discriminator, and the prediction stated before the run

run_twin_q58.py builds a twin: the shipped gate with _g10_deployed repointed at a scratch path holding a current-generation render (ast-asserted, and the G10 block contains no live-path token in any non-comment line).

gate reads rc verdict G10 check 2
shipped the live page 1 180 passed, 1 failed FAIL
twin a scratch current-generation render 0 181 passed, 0 failed PASS

Predicted before the run: "shipped 180/1, the one FAIL is G10 check 2; twin 181/0, check 2 flips to PASS, every other row identical." Met exactly. A red row that cannot go green would prove nothing; this one goes green on the artifact it exists to check.

  • shipped: stdout 15,055 B / sha256 e8304379529c4c8336a69e013d0eed464b7eddfe65cbf1895e6e41344a11b4de, stderr 1,791 B
  • twin: stdout 14,824 B / sha256 fe8c8c72825962d9ff08df7963be4b6cefa727958339b2a3bcd57482e3052d25, stderr 1,810 B
  • the twin's stand-in is 11,798 B / f32191223931e74b…, renderer rc 0, carries the qualifier

Live state, across all four gate invocations

live_page_moved: false on every one (bytes + sha256 + mtime_ns): 9,865 B / 427b7b54c69b18d2… / mtime_ns 1791709034017837233. Snapshot 14,039 B / aa842b0fd880… / mtime_ns 1791479579475780303 and held pin 120 B / 5b6d7071… / 1791478687099553071 unmoved. Sandboxes 0/0 both ways (shallow glob and recursive rglob) before and after.

3. Byte-stability, measured not asserted

Two shipped runs: both 180 passed / 1 failed at 15,055 B, and byte_stable_stdout read false. Not smoothed over — the difference is mkdtemp suffixes in the sandbox path the gate prints (/tmp/tmp8wl8eb44 vs /tmp/tmpsp0jt2_3; helm-legs-gb5_6rox vs helm-legs-5rzmq0xa; helm-q39-dlb_ysgs vs helm-q39-3i6olv_3; q31-g8-deploy-9mcza30j vs q31-g8-deploy-v7_v9zrw). Normalising those four patterns:

  • the four G10 rows are byte-identical across both runs (G10 block byte-identical: True)
  • normalised stdout sha256 1958d8212fa60e4458ba85b86221e459dcd2f7eaffc0351668e6b74f31e77856 on both runs, identical: True, residual differing lines: 0

The sandbox path's effect on stdout is the lane's logged full-path-delta class; the G10 rows themselves are stable, which is the part any reader would re-derive.

4. Deployed bin, unchanged

/root/.helm/bin/ re-read after every run — helm-collect.py 27,907 B / ee0b36a82f23…, helm-render.py 21,766 B / 5c67473d8928…, helm-selftest.py 33,647 B / 7ed1bca8f9da…, helm-validate.py 3,556 B / 12570786ba70…, summarize.py 1,463 B / 738699986778…. grep -c HELM_COLLECTOR on the deployed self-test = 0 — Q40 stands, so G10 is not in production and nothing from Q31 to Q45S10 is either. Disk 154G total / 110G used / 45G avail / 72%.

5. Defects of mine, logged first

  1. Two claims in one cell. The first G10 check 3 asserted the predicate discriminates and the deployed page fails it. That would have made the twin 180/1 rather than 181/0, hiding the discriminator behind the finding. Split: check 3 is now a pure predicate test (green in both gates), check 2 is the finding. This is the class the lane returns to counterparties for.
  2. A filter written from memory. summarise_r28.py extracted report rows with startswith("PASS") / startswith("FAIL"), which never matches the two-space rendering — PASS count: 0, FAIL lines: []. The lane's own logged defect class, reproduced by its author on a fresh artifact. Fixed with .strip().startswith.
  3. A live-path assertion that fired twice on the wrong thing. First on G5's pre-existing read-only check at :422-425, then on G10's own explanatory comment. Scoped to the G10 block and to non-comment lines.
  4. byte_stable_stdout: false reported as found, with the cause measured by normalisation rather than argued away.
  5. ast.unparse renders single quotes; the first twin assertion expected double and failed on a correct edit.
  6. import pathlib is not in the gate — it uses from pathlib import Path (:154). The first patch draft wrote pathlib.Path and would have raised NameError; caught by an import census before the run, not after.

6. What is owed, and to whom

  • Alexander, under §4: (a) re-render and re-deploy the Helm page — the command is in §1 row 7 — because until then the public page publishes a green "match" against a pin that measurably does not match; (b) the §4 adoption cp, now collector 4e45021d80395d3f03fc58d7e99610e3c1c0d2fcf9c85f1db50b16cfd2043c6c
    • gate 3b98b62fa5c7ea626091f6d0f63566de79818ae414bcffe323dd1ddd53956158. Neither is a task; both are decisions, and this record does not execute them.
  • Nothing is owed to the pipe from this run, and no queue item was opened: the Helm lane's executable set was empty at the start of the run (Q49's Status: open and unclaimed at WORK-QUEUE.md:6847 is stale — the pipe's own hygiene ledger at :8026 records Q49 CLOSED), so the unit taken was the one defect the lane had measured and labelled but never fixed.