From 3c8623fda4f189fd28e56a027f74b680fd888aad Mon Sep 17 00:00:00 2001 From: Merlin Date: Sat, 10 Oct 2026 20:07:48 +0000 Subject: [PATCH] queue: Q24 the Q13 fix pack DONE (Merlin) - 25 clauses / 31 deltas staged unrun, 1 decided + 24 undecided, blast radius 20 of 125 emitted files; the finding is that the ledger build has no anchor mechanism for the prose panels, so every 'anchor it' clause naming a source in body/plain_summary cannot be staged as a link by any blocks.json field; Q25 proposed per Q7 (a prose-anchor convention, implemented in the build tree and left unpublished) --- WORK-QUEUE.md | 114 +++++ checkins/2026-10-10-merlin.md | 164 +++++++ docs/q13-fix-pack.md | 260 +++++++++++ docs/q13-fix-pack/apply_fix_pack.py | 643 ++++++++++++++++++++++++++++ 4 files changed, 1181 insertions(+) create mode 100644 docs/q13-fix-pack.md create mode 100644 docs/q13-fix-pack/apply_fix_pack.py diff --git a/WORK-QUEUE.md b/WORK-QUEUE.md index 76e8628..e619b18 100644 --- a/WORK-QUEUE.md +++ b/WORK-QUEUE.md @@ -2611,3 +2611,117 @@ is what has kept them waiting. **Status: open and unclaimed. Opened by the pipe 2026-10-10** because Q23 closed at `8b56abe` and the unclaimed executable set fell to one. Nominated to Merlin, who holds the four fix lists — but any member may claim it, and **declining it with a reason is a legitimate answer** (§3.7). + +**Status: DONE — `docs/q13-fix-pack.md`, Merlin, 2026-10-10. 25 clauses (31 deltas): 1 `DECIDED` +(`D1` `magazine-091`, publication blocked by its own decided note) and 24 `undecided-for-Alexander` +(2 of them conditional rows). Combined blast radius 20 of 125 emitted files, measured by a real scratch +build; per-clause radius 1 file for all 25. Staged file `docs/q13-fix-pack/apply_fix_pack.py` +(25,361 B / sha256 `2d39df3cdfe466b33e45b19b8741f43b536d93e7ccde90f871e50e5cd6ffd1cd`), committed +unrun — every clause exercised in check mode, none applied to the build tree.** +- **The build chain was measured end to end before anything was staged**, not assumed: + `blocks.json` 278,459 B / sha256 `63c9cee6103a6824c90c71440fa983d2f5592e380f9c3fb40f7ad366ae41723d`, + `build_blocks.py` 22,256 B / sha256 `97fedb5dd5e85d2b10071e8e4904b3a9b1573072149e722fbece93eaf0774672`, + a scratch build `Built 123 pages from 121 entries`, and `diff -rq` of that output against the build + tree's own `site-ledger` — **no output, 125 files each, byte-identical**. Then **every page fetched + HTTP 200 and byte-identical to the freshly emitted file**: the 20 entries the pack touches plus 5 + controls, 25 pages, ~18:5x UTC. Ledger index unchanged at 200 / 34,488 B / sha256 + `4f764ef4dfb9c08b2654ff0783550d20ad1bb7a31cb0c32070974a5fe451557e`. So + `blocks.json → build_blocks.py → site-ledger//index.html → the live page` is verified + byte-for-byte at all four legs. **Documented.** +- **Population rule followed exactly, with two deviations flagged in the table.** Rows are one per + clause a closed audit routed to Q13, deduped by (entry, current string), re-testing nothing: + `A1`–`A4` (Q12 §6.1 = Q21 finding 1, same clause), `B1`–`B4` (Q21 verification addendum's + *"quoted below"* pointer defect), `C1` (the `plebiolira → flav1000` ownership note), + `E1`–`E3` (Q23's 3-entry StackchainQuant fix set, not Q22's 1), `F1`–`F2` and `G1`–`G3` (Q22's + anchor-or-strike set), `H1`–`H2` (Q23 + the `Satskeeper` addendum), `D1` (the one decided clause). + Deviations, both disclosed in-row: `I1` `magazine-026` (Q12 §6.3, outside the stated rule which + names findings 1–2) and `I2` `magazine-070` (admitted by rule 3a per the pipe's Q23 correction). +- **The structural finding, and it is the item's real result: the ledger build has no anchor + mechanism for the prose panels.** `build_blocks.py` emits `` in exactly three places — + `contributor.handle` (line 310), `body_source_url` (323), `excerpts[].url` (333). `body`, + `plain_summary.text`, `body_provenance` and `uncertainty` go through `esc()` with no anchor + support. **So every "anchor it" clause naming a source printed inside prose — `StackchainQuant` + ×3, `AriZonanHODL` ×4, `Satskeeper`, the Lightning address — cannot be staged as a link by any + `blocks.json` field**, and is staged instead as a dated `uncertainty` append or a handle-form + wording change. Only the five `contributor-anchor` rows are real links. `contributor` also has + **one** `handle` slot, so `G1` anchors `BTC_Freeborn` and leaves `@AnthonyDessauer` unanchored on + a byline that names both. A prose-anchor convention would be a build change — Alexander's, not a + wizard's. +- **Four rows excluded with the reason stated, and one of them is a detector defect.** The + `Saylor` rows (`magazine-036/038/053/068`) fired only because `x.com/saylor/status/1578479561750110208` + is an excerpt URL on `event-2022-10-07-gui-s-2079-and-saylor-s-2080`; in the four pages the word is + *"Saylor Academy's free Bitcoin for Everyone course"* and *"Michael Saylor"* — a person and an + organisation named in the narrative, not a citation of the account. **No dead source was missed; the + residual sweep cannot tell the two apart.** `block-5` needs no action (resolves, prose already + discloses the capture date). The `x.com/i/…` addition is withdrawn per the pipe's 18:13Z addendum. +- **Proposed new values validated, not assumed.** `stackchainmagazine.net` detags to 5,024 chars with + `Skinner` **0** and the passage absent; `/a-conversation-with-burnchainers/` (200 / 155,367 B / + sha256 `33af2c4d78c7642db9a935cd9852ac22fffb151b443db3f49b097bc3964d9d8f`) carries `Skinner` **1** + and the passage **verbatim**; `github.com/plebiolira/stackchainsiggy_nostr` → **301**, + `Location: https://github.com/flav1000/stackchainsiggy_nostr`, and the flav1000 URL → 200 / 279,677 B. +- **The decided note conflict is surfaced, not resolved.** `decisions/2026-09-26.md` item 13 is + approved (2026-09-26T14:51:11.019Z) with `proposed_action` *publish the corrected wording* against a + `decided note` *add our note and escalate to the author and mag publisher*. Per the item's rule the + decided note controls, so `D1` is staged and not published. Nothing in `decisions/` was executed. + The pre-existing `EDITS/apply_editorial_fix_091.py` was re-run in check mode and still passes + (`entries: 121`, `note carried by entries: [91]`), so `D1` is that delta restated, not a second staging. +- **Own defects logged first, in the deliverable's §10.** (a) The first verifier searched each entry's + whole JSON region and reported `body` old-strings occurring **2×** on `event-2022-07-18`, because + `body` and `plain_summary.text` share the sentence; field-span scoping is what makes `H1`'s two + deltas land in two places. (b) The staged script's first version parsed arguments so `--decision`'s + value was read as the clause id (every apply failed with the usage message) and spliced + region-relative offsets into the full file text, producing `json.decoder.JSONDecodeError`. Both were + caught by the blast-radius run — the only step that would have caught them — and fixed before any + count was published. +- **Guard against an unapproved apply:** a clause marked `undecided-for-Alexander` refuses to apply + unless **both** `--force` and `--decision ` are passed, so no clause can be staged in + a form that applies without the approval being named. +- **Limits.** 25 clauses is not 121 entries: no rate is claimed, and the 96 untouched entries were not + swept. Every byte count and sha256 is a dated reading from this host at the printed clock. Whether + the proposed wording reads well in the archive's voice is an editorial call and was not made. + Evidence files `/root/crucible/q24/{LIVE-READS,TARGET-READS,CHECK-RUN,BLAST-RADIUS,BLAST-ALL, + CONTROL-091}.txt` plus `mkclauses.py`, `gen_apply.py`, `blast.py`, `blast_all.py`, `emit_pack.py`, + all re-runnable. +- **Nothing touched:** no live page, no rebuild, no publish, no third-party contact, no other party's + file, nothing in `decisions/`, and the build tree's `blocks.json` was read-only throughout. +- **Lane after Q24:** the unclaimed executable set is empty (Q20 active and zephyrus-sparkborn's, + Q9 blocked on the audio handover, Q13/Q19 gated on Alexander), so **Q7's never-dry obligation is + triggered** — see the proposal at the foot of this file. + +--- + +## Q25 — A prose-anchor convention for the ledger build *(archive-side; proposed 2026-10-10 by Merlin, per Q7's never-dry obligation — Q24 closed and the unclaimed executable set fell to zero)* + +Q24 staged 25 verified live-page fixes and found the thing that stops ten of them being links rather than +disclosures: `build_blocks.py` emits an `` in exactly three places — `contributor.handle` +(`render_entry` line 310), `body_source_url` (line 323) and `excerpts[].url` (line 333). Everything in +`body`, `plain_summary.text`, `body_provenance` and `uncertainty` goes through `esc()` with no anchor +support at all. So `StackchainQuant` on three entries, `AriZonanHODL` on four, `Satskeeper`, the +`bitcoinbekka@getalby.com` address — every one of them a source the archive names to the reader — **cannot +be linked by any `blocks.json` field**. Q24 staged them as dated `uncertainty` notes instead, which is +honest and is the only form the build supports today. + +**Do this:** design and implement a minimal prose-anchor convention in the build tree +(`/root/stackchain-legends-build/`), unpublished — a markup token `render_entry` expands into the same +`@` it already emits for `contributor.handle`, plus a +validator that fails the build if a token names a handle no entry cites. Then measure what it would buy: +a census of every handle-shaped token in the 121 entries' prose panels, how many resolve, how many are +already anchored elsewhere on the same page, and which of Q24's 25 clauses it would convert from a note +into a link. + +**Scope boundary:** the convention is **implemented and measured in the build tree only**. No rebuild of +the live site, no publish, no `blocks.json` edit, no third-party contact — publishing a build change is +Alexander's, exactly as Q13 states. This item produces a design, a diff, and a census; it does not ship one. + +**Done means:** a record naming the convention's exact syntax, the `render_entry` change as a diff against +the current `build_blocks.py` (sha256 `97fedb5dd5e85d2b10071e8e4904b3a9b1573072149e722fbece93eaf0774672`), +a build that still emits byte-identical output for the 121 entries when no token is present, the +handle-token census with resolve/404/suspended verdicts, and the list of Q24 clauses the convention would +upgrade. + +**Evidence:** the diff, the before/after emitted-tree digests proving a no-token build is unchanged, the +census table with a fetched URL per row, and the named Q24 clause ids. + +**Why it earns a slot:** Q24 closed with zero unclaimed executable items, which is Q7's trigger. This is +the only piece of work in the queue that would change what the archive can *say* rather than what it +already said — and it is derived from a finding that is already verified, not from a hypothesis. diff --git a/checkins/2026-10-10-merlin.md b/checkins/2026-10-10-merlin.md index 3a32ce0..1ab60f1 100644 --- a/checkins/2026-10-10-merlin.md +++ b/checkins/2026-10-10-merlin.md @@ -719,3 +719,167 @@ byte-checkable from outside this host. No secret is printed in this check-in or the long strings in them are X page titles, LNURL JSON, archive prose and sha256 digests. — Merlin + +--- + +# Run 5 — Q24, claimed and finished (the Q13 fix pack) + +> Screen. Every claim below carries a path, a hash, a URL, or a command with its output, or the word +> `unverified`. Nothing secret is printed anywhere in this check-in or in the files it lands. + +## Did + +Claimed and finished **Q24 — the Q13 fix pack: stage every verified live-page fix as one reviewable +approval**, the item the pipe opened at 18:0xZ, nominated to me, and the top unclaimed executable item +(Q20 is active and zephyrus-sparkborn's; Q9 blocked on the audio handover; Q13/Q19 gated on Alexander). + +Deliverables: `docs/q13-fix-pack.md` (**32,942 B**, sha256 +`77faaaa5f8939f5ecde7f46d7acfcd5b535321dbbc31f688a02ec66a14616b4e`) — the build-chain evidence, the +25-row clause table in the fixed column order `entry | live page | panel | current string | proposed string +| authority (artifact + clock) | decided / undecided | staged file`, the counts, the `decisions/` +reconciliation, the structural finding, the excluded rows, the alternative remedies, the staging record, +the method and the limits — and `docs/q13-fix-pack/apply_fix_pack.py` (**25,361 B**, sha256 +`2d39df3cdfe466b33e45b19b8741f43b536d93e7ccde90f871e50e5cd6ffd1cd`), the staged script, **committed unrun**. + +**Result: 25 clauses, 31 deltas — 1 `DECIDED` and 24 `undecided-for-Alexander` (2 of them conditional +rows).** Every clause is one explicit `old → new` delta against `/root/stackchain-legends-build/blocks.json`, +located by its entry's unique title and scoped to a single field's own JSON span. + +**The build chain was measured end to end before anything was staged.** `blocks.json` 278,459 B / sha256 +`63c9cee6103a6824c90c71440fa983d2f5592e380f9c3fb40f7ad366ae41723d`; `build_blocks.py` 22,256 B / sha256 +`97fedb5dd5e85d2b10071e8e4904b3a9b1573072149e722fbece93eaf0774672`; a build on a scratch copy at +`/root/crucible/q24/build/` → `Built 123 pages from 121 entries (121 cleared, 0 research-only)`, and +`diff -rq` of that output against the build tree's own `site-ledger` → **no output, 125 files each, +byte-identical**. Then **every page fetched HTTP 200 and byte-identical to the freshly emitted file** — the +20 entries the pack touches plus 5 controls, 25 pages (`/root/crucible/q24/LIVE-READS.txt`), e.g. +`magazine-091` 200 / 4,559 B / sha256 `8b00bbb09a13a47679a7082cf915b97d2c66f16dcbd5fdfe4fba945da5f14377`. +Ledger index unchanged at 200 / 34,488 B / sha256 `4f764ef4dfb9c08b2654ff0783550d20ad1bb7a31cb0c32070974a5fe451557e`. +So `blocks.json → build_blocks.py → site-ledger//index.html → the live page` holds byte-for-byte at +all four legs. + +**The structural finding, which is the item's real result: the ledger build has no anchor mechanism for the +prose panels.** `build_blocks.py` emits `` in exactly three places — `contributor.handle` +(`render_entry` line 310), `body_source_url` (323), `excerpts[].url` (333). `body`, `plain_summary.text`, +`body_provenance` and `uncertainty` are emitted through `esc()` with no anchor support. **Every "anchor it" +clause naming a source printed inside prose — `StackchainQuant` on three entries, `AriZonanHODL` on four, +`Satskeeper`, the Lightning address — cannot be staged as a link by any `blocks.json` field**, so those are +staged as dated `uncertainty` appends or handle-form wording changes instead. Only the five +`contributor-anchor` rows are real links. `contributor` also has **one** `handle` slot, so `G1` anchors +`BTC_Freeborn` and leaves `@AnthonyDessauer` unanchored on a byline naming both. + +**Blast radius, measured by a real scratch build, not inferred.** Per clause: **25 of 25 change exactly one +emitted file — the entry's own `index.html`**; no clause touches `blocks/index.html`, `sitemap.xml` or +`ledger.css`. Combined: **20 of 125** emitted files (`/root/crucible/q24/BLAST-RADIUS.txt`, +`BLAST-ALL.txt`). Spot checks on the applied scratch pages: `block-25` carries the +`a-conversation-with-burnchainers` permalink, no longer carries the bare site root as an anchor, and reads +*"quoted above"*; `magazine-091` no longer contains *"Comparing Strategy"*, contains *"Comparing Twenty +One"*, and carries the correction note; `magazine-039` carries `href="https://x.com/captainsiddh"` and the +move note; `magazine-070` carries `href="https://x.com/MidyReyes"`. + +**The proposed new values were validated, not assumed.** `https://www.stackchainmagazine.net` detags to +5,024 characters with `Skinner` **0** times and the passage absent; +`https://www.stackchainmagazine.net/a-conversation-with-burnchainers/` (200 / 155,367 B / sha256 +`33af2c4d78c7642db9a935cd9852ac22fffb151b443db3f49b097bc3964d9d8f`) carries `Skinner` **1** time and the +passage **verbatim**; `https://github.com/plebiolira/stackchainsiggy_nostr` → **301**, +`Location: https://github.com/flav1000/stackchainsiggy_nostr`, and the flav1000 URL → **200 / 279,677 B** +(`/root/crucible/q24/TARGET-READS.txt`). + +**Four candidate rows excluded with the reason stated, one of them a detector defect.** The `Saylor` rows +(`magazine-036/038/053/068`) fired only because `https://x.com/saylor/status/1578479561750110208` is an +excerpt URL on `event-2022-10-07-gui-s-2079-and-saylor-s-2080`; in the four pages the word is *"Saylor +Academy's free Bitcoin for Everyone course"* and *"Michael Saylor and Nayib Bukele"* — a person and an +organisation named in the narrative, not a citation of the account. **No dead source was missed; the +residual sweep cannot tell the two apart.** `block-5` needs no action. The `x.com/i/…` addition is +withdrawn per the pipe's 18:13Z addendum. + +## Blocked + +Nothing blocked the run. One thing is **deliberately unresolved rather than executed**: `decisions/2026-09-26.md` +item 13 (`magazine-091`) is approved at 2026-09-26T14:51:11.019Z with `proposed_action` *"publish the +corrected wording to the live magazine-091 page"* against a `decided note` *"Do we even have authority to fix +the publication? Add our note and escalate it to the author and mag publisher."* Per the item's own rule the +**decided note controls**, so `D1` is staged and not published. Nothing in `decisions/` was executed. + +Limits, stated in the deliverable: + +- **25 clauses is not 121 entries.** No rate is claimed and the 96 untouched entries were not swept. +- **The staged script is committed unrun.** Every clause was exercised in **check mode** (locator, field + span, and `old` occurring exactly once inside it) and **none was applied to the build tree's + `blocks.json`**. The scratch applications used for blast radius ran against copies under + `/root/crucible/q24/blast/`. `/root/crucible/q24/CHECK-RUN.txt` holds all 25 runs with exit codes. +- **Every byte count and sha256 is a dated reading** from this host at the printed clock. The site root is + byte-stable at 215,8xx B but has carried two digests today; X pages carry a per-request CSP nonce. +- **Whether the proposed wording reads well in the archive's voice is an editorial call and was not made.** + The note texts are drafts in the archive's register, staged for you to accept, edit or reject. + +## Next + +**Q25 — a prose-anchor convention for the ledger build**, proposed at the foot of `WORK-QUEUE.md` because +Q24 closed and the unclaimed executable set fell to **zero**, which is Q7's never-dry trigger. It is the +direct successor to this run's structural finding: design and implement the convention in the build tree +**unpublished**, prove a no-token build still emits byte-identical output for all 121 entries, and census +which handle-shaped tokens in the prose panels it would anchor and which of Q24's 25 clauses it would +upgrade from a note into a link. Scope boundary stated in the item so it cannot drift into a publish. + +**Q13 stays gated on you.** Every clause in the pack is either a live-page text change or a link change, and +the one decided clause is blocked by its own decided note. Nothing executed on `decisions/`. + +## Ask + +Three decisions, none of them blockers: + +1. **Release `D1` (`magazine-091`) or hold it?** The approval and the note disagree, and the note is the + one that controls. If you want the wording published, say so against the note; if you want the courtesy + note and the escalation first, `D1` stays staged. Either way it is one rebuild of one page. +2. **The 24 undecided clauses — approve as a pack, or clause by clause?** The pack exists so the answer can + be one line. `A1`–`A4` and `B1`–`B4` are the lowest-risk eight (a URL that does not contain its quote, + and a direction word pointing the wrong way, on four pages); `E1`–`E3` change what three entries assert + about a source that no longer exists and are the ones worth reading in full. +3. **Build the prose-anchor convention (Q25), or leave the archive's unlinked-prose style as-is?** Until it + exists, ten of these clauses can only ever be disclosures rather than links. + +## Run detail — Q24 + +**Clock and host.** All fetches from this host, 2026-10-10 ~18:40–19:1x UTC. Working tree at +`/root/crucible/q24/` (scratch build at `build/`, 25 live pages at `live/`, four target bodies at +`targets/`, per-clause scratch applies at `blast//`, the combined one at `blast/ALL/`). Repo at +`/root/troupe-standup`, branch `main`, HEAD before this run `afc6ec3`, working tree clean. + +**Scripts, all re-runnable.** `mkclauses.py` (the clause table and its field-span verifier), +`gen_apply.py` (the staged-script generator), `blast.py` (per-clause radius), `blast_all.py` (combined +radius + applied-page spot checks), `fetch_live.py`, `fetch_targets.py`, `run_checks.py`, +`emit_pack.py` (writes the deliverable), plus `explore*.py`, `rawfmt.py`, `contexts*.py`, `dbg.py`. + +**Counts, recomputed not accepted.** 121 entries; entry titles unique across all of them (checked — that is +what makes the title locator safe); the bare site root appears as an excerpt URL exactly **4** times in the +whole file, so the `A` group is a complete census of that defect, not a sample. 25 clauses / 31 deltas; +kinds 6 `uncertainty-append`, 5 `excerpt-url`, 5 `contributor-anchor`, 4 `direction-word`, +4 `prose-handle-form`, 1 `body-clause`. Verdicts 1 decided / 23 undecided / 2 undecided-conditional. +25/25 check-mode runs exit 0. Blast radius 1 file × 25; combined 20 of 125. + +**Four of my own defects, logged first in the deliverable's §10.** (a) The first verifier searched each +entry's whole JSON region and reported `body` old-strings occurring **2×** on `event-2022-07-18`, because +`body` and `plain_summary.text` share the sentence — field-span scoping is what makes `H1`'s two deltas +land in two places and nothing else. (b) The staged script's first version parsed arguments so +`--decision`'s value was read as the clause id, and every apply failed with the usage message. (c) The same +version spliced **region-relative** offsets into the **full file** text, producing invalid JSON +(`json.decoder.JSONDecodeError: Expecting ',' delimiter`). (b) and (c) were caught by the blast-radius run, +which is the only step that would have caught them, and both were fixed before any count was published. +(d) `magazine-070` was missing from the first live-fetch list, which surfaced as an empty cell in the +emitted table rather than as a silent gap. + +**The control that mattered.** The build tree's pre-existing `EDITS/apply_editorial_fix_091.py` was re-run +in check mode and still passes against today's `blocks.json` (`entries: 121`, `note carried by entries: +[91]`, body now reads *"Comparing Twenty One, …"*, `CHECK ONLY — blocks.json not written`, +`/root/crucible/q24/CONTROL-091.txt`). So `D1` is that delta restated in the pack's format, not a second +staging, and the pack's one decided clause is the one the archive already agreed to. + +**Files written this run:** + +- `docs/q13-fix-pack.md` (new — the deliverable) +- `docs/q13-fix-pack/apply_fix_pack.py` (new — the staged script, committed unrun) +- `WORK-QUEUE.md` (Q24 status block → DONE; Q25 proposed per Q7) +- `checkins/2026-10-10-merlin.md` (this section) + +**Landing evidence.** The commit, push and anonymous read-back are recorded in the block appended after +this run commits. diff --git a/docs/q13-fix-pack.md b/docs/q13-fix-pack.md new file mode 100644 index 0000000..6327e0d --- /dev/null +++ b/docs/q13-fix-pack.md @@ -0,0 +1,260 @@ +# The Q13 fix pack — every verified live-page fix, staged as one reviewable approval + +**Run by Merlin, 2026-10-10, from this host.** Deliverable for queue item **Q24**. Nothing in this run +applied a fix, rebuilt the ledger, published a page, touched a live page, edited another party's file, or +executed anything in `decisions/`. The only writes were to this file and to the staged script. + +**Result: 25 clauses — 1 `DECIDED` (with a conflict that blocks publication) and 24 +`undecided-for-Alexander`.** Combined blast radius, measured by a real scratch build: **20 of 125 emitted +files** change if all 25 were applied. Every clause is one explicit `old → new` delta against +`/root/stackchain-legends-build/blocks.json`, located by its entry's unique title and scoped to a single +field's own JSON span, so a string shared by `body` and `plain_summary.text` cannot be double-edited. + +## 1. The build chain, measured end to end before anything was staged + +`blocks.json` is the build source; `build_blocks.py` emits the pages; the live site serves a copy of that +output. All three legs were checked on this run, not assumed: + +``` +$ sha256sum /root/stackchain-legends-build/blocks.json +63c9cee6103a6824c90c71440fa983d2f5592e380f9c3fb40f7ad366ae41723d blocks.json (278,459 B) +$ sha256sum /root/stackchain-legends-build/build_blocks.py +97fedb5dd5e85d2b10071e8e4904b3a9b1573072149e722fbece93eaf0774672 build_blocks.py (22,256 B) + +$ python3 build_blocks.py # run on a scratch COPY at /root/crucible/q24/build/ +Built 123 pages from 121 entries (121 cleared, 0 research-only) + +$ diff -rq /root/crucible/q24/build/site-ledger /root/stackchain-legends-build/site-ledger +(no output — 125 files each, byte-identical) +``` + +The build tree's own emitted output is therefore current with its source, which is what makes a +blast-radius measurement on a scratch build meaningful. Then the live pages: + +**Every page fetched: HTTP 200 and byte-identical to the freshly emitted file.** That is the 20 entries +this pack touches plus 5 controls (`event-2022-07-18-anthony-enters-at-16-names-the-hill-at-24`, +`magazine-036/038/053/068`) — 25 pages, `cmp`/sha256, 2026-10-10 ~18:5x UTC. The ledger index is unchanged at **200 / 34,488 B / sha256 +`4f764ef4dfb9c08b2654ff0783550d20ad1bb7a31cb0c32070974a5fe451557e`** — the same figure every earlier run +today recorded. Full per-page list in `/root/crucible/q24/LIVE-READS.txt`. + +So the chain is `blocks.json → build_blocks.py → site-ledger//index.html → the live page`, verified +byte-for-byte at all four legs. **Documented.** + +## 2. Shared strings, quoted once + +`UNC_MAG` — the `uncertainty` value carried by all 91 magazine entries, verbatim: + +> `Archive summary of a published article, not the article itself. Quotes are short verbatim excerpts; read the original for full context.` + +Three entries have their own `uncertainty` text and are quoted in full in their rows. `magazine-091`'s +is `UNC_MAG`. + +## 3. The clause table + +Fixed column order, as the item specifies: `entry | live page | panel | current string | proposed string | +authority (artifact + clock) | decided / undecided | staged file`. `\|` inside a cell is an escaped pipe. + +| entry | live page | panel | current string | proposed string | authority (artifact + clock) | decided / undecided | staged file | +|---|---|---|---|---|---|---|---| +| `A1` | block-25 | `https://alexanderwhitestone.com/stackchain/block-25/` (200 / 3,582 B / sha256 `b0bdc88b6fe2…`) | From the sources | `"https://www.stackchainmagazine.net"` | `"https://www.stackchainmagazine.net/a-conversation-with-burnchainers/"` | recoveries/evidence-tier-audit.md §6.1 (2026-10-10 ~09:3xZ) + recoveries/excerpt-binding-audit.md finding 1 (2026-10-10T13:26:16Z) | undecided-for-Alexander | `docs/q13-fix-pack/apply_fix_pack.py` (clause `A1`; blast radius 1 file: `block-25/index.html`) | +| `A2` | block-88 | `https://alexanderwhitestone.com/stackchain/block-88/` (200 / 3,315 B / sha256 `de910b3d698c…`) | From the sources | `"https://www.stackchainmagazine.net"` | `"https://www.stackchainmagazine.net/a-conversation-with-burnchainers/"` | recoveries/evidence-tier-audit.md §6.1 (2026-10-10 ~09:3xZ) + recoveries/excerpt-binding-audit.md finding 1 (2026-10-10T13:26:16Z) | undecided-for-Alexander | `docs/q13-fix-pack/apply_fix_pack.py` (clause `A2`; blast radius 1 file: `block-88/index.html`) | +| `A3` | block-89 | `https://alexanderwhitestone.com/stackchain/block-89/` (200 / 3,181 B / sha256 `3534dce9ea99…`) | From the sources | `"https://www.stackchainmagazine.net"` | `"https://www.stackchainmagazine.net/a-conversation-with-burnchainers/"` | recoveries/evidence-tier-audit.md §6.1 (2026-10-10 ~09:3xZ) + recoveries/excerpt-binding-audit.md finding 1 (2026-10-10T13:26:16Z) | undecided-for-Alexander | `docs/q13-fix-pack/apply_fix_pack.py` (clause `A3`; blast radius 1 file: `block-89/index.html`) | +| `A4` | block-90 | `https://alexanderwhitestone.com/stackchain/block-90/` (200 / 3,408 B / sha256 `2575fa6e52e6…`) | From the sources | `"https://www.stackchainmagazine.net"` | `"https://www.stackchainmagazine.net/a-conversation-with-burnchainers/"` | recoveries/evidence-tier-audit.md §6.1 (2026-10-10 ~09:3xZ) + recoveries/excerpt-binding-audit.md finding 1 (2026-10-10T13:26:16Z) | undecided-for-Alexander | `docs/q13-fix-pack/apply_fix_pack.py` (clause `A4`; blast radius 1 file: `block-90/index.html`) | +| `B1` | block-25 | `https://alexanderwhitestone.com/stackchain/block-25/` (200 / 3,582 B / sha256 `b0bdc88b6fe2…`) | Provenance | `quoted below` | `quoted above` | WORK-QUEUE.md §Q21 verification addendum, control plane, 2026-10-10 (offsets quoted there) | undecided-for-Alexander | `docs/q13-fix-pack/apply_fix_pack.py` (clause `B1`; blast radius 1 file: `block-25/index.html`) | +| `B2` | block-88 | `https://alexanderwhitestone.com/stackchain/block-88/` (200 / 3,315 B / sha256 `de910b3d698c…`) | Provenance | `quoted below` | `quoted above` | WORK-QUEUE.md §Q21 verification addendum, control plane, 2026-10-10 (offsets quoted there) | undecided-for-Alexander | `docs/q13-fix-pack/apply_fix_pack.py` (clause `B2`; blast radius 1 file: `block-88/index.html`) | +| `B3` | block-89 | `https://alexanderwhitestone.com/stackchain/block-89/` (200 / 3,181 B / sha256 `3534dce9ea99…`) | Provenance | `quoted below` | `quoted above` | WORK-QUEUE.md §Q21 verification addendum, control plane, 2026-10-10 (offsets quoted there) | undecided-for-Alexander | `docs/q13-fix-pack/apply_fix_pack.py` (clause `B3`; blast radius 1 file: `block-89/index.html`) | +| `B4` | block-90 | `https://alexanderwhitestone.com/stackchain/block-90/` (200 / 3,408 B / sha256 `2575fa6e52e6…`) | Provenance | `quoted below` | `quoted above` | WORK-QUEUE.md §Q21 verification addendum, control plane, 2026-10-10 (offsets quoted there) | undecided-for-Alexander | `docs/q13-fix-pack/apply_fix_pack.py` (clause `B4`; blast radius 1 file: `block-90/index.html`) | +| `C1` | event-2023-05-00-siggy-reaches-nostr | `https://alexanderwhitestone.com/stackchain/event-2023-05-00-siggy-reaches-nostr/` (200 / 3,393 B / sha256 `b7fb05fee1b7…`) | From the sources | `"https://github.com/plebiolira/stackchainsiggy_nostr"` | `"https://github.com/flav1000/stackchainsiggy_nostr"` | recoveries/excerpt-binding-audit.md fetch note (301 observed) + recoveries/summary-fidelity-nonmagazine.md ev-017 row 1 + recoveries/ledger-audit-pass2.md row 19; 301 re-observed this run | undecided-for-Alexander | `docs/q13-fix-pack/apply_fix_pack.py` (clause `C1`; blast radius 1 file: `event-2023-05-00-siggy-reaches-nostr/index.html`) | +| `D1` | magazine-091 | `https://alexanderwhitestone.com/stackchain/magazine-091/` (200 / 4,559 B / sha256 `8b00bbb09a13…`) | What happened + Uncertainty | body: `Comparing Strategy, Twenty One, Nakamoto, Metaplanet, Coinsilium and Smarter Web returns and drawdowns against Bitcoin, they find none consistently outperform,`
uncertainty: `Archive summary of a published article, not the article itself. Quotes are short verbatim excerpts; read the original for full context.` | body: `Comparing Twenty One, Nakamoto, Metaplanet, Coinsilium and Smarter Web against Bitcoin — with earlier pioneers such as Strategy set aside as not a fair comparison — they find none consistently outperform,`
uncertainty: `Archive summary of a published article, not the article itself. Quotes are short verbatim excerpts; read the original for full context. Correction 2026-09-26: this summary previously listed Strategy among the firms compared. The article sets Strategy aside (“not a fair comp for these newcomers”) and excludes it from the comparison; the listing was the archive’s error, not the article’s. Strategy has been removed from the compared set.` | decisions/2026-09-26.md item 13, approved 2026-09-26T14:51:11.019Z; staged record /root/stackchain-legends-build/EDITS/2026-09-26-magazine-091-correction.md | DECIDED (approved) — decided note conflicts with proposed_action; see §5 | `docs/q13-fix-pack/apply_fix_pack.py` (clause `D1`; blast radius 1 file: `magazine-091/index.html`) | +| `E1` | event-2022-08-18-the-bitcoin-beach-campaign-closes-and-hodlon | `https://alexanderwhitestone.com/stackchain/event-2022-08-18-the-bitcoin-beach-campaign-closes-and-hodlon/` (200 / 4,397 B / sha256 `3b3002d4d5fc…`) | Uncertainty | `Event status as recorded by the history research: sourced. The date basis is the reported event date, not necessarily a verified post timestamp.` | `Event status as recorded by the history research: sourced. The date basis is the reported event date, not necessarily a verified post timestamp.` + ` Note 2026-10-10: the named warrant “StackchainQuant” returns HTTP 404 at https://x.com/StackchainQuant (X: “User Profile Not Found”). The report it attributes is separately cited and live; the account itself is unreachable as of this date.` | recoveries/named-source-liveness-unlinked.md finding 1 (2026-10-10T17:49:31Z) + recoveries/named-source-liveness.md row 6 (2026-10-10T15:39:39Z) | undecided-for-Alexander | `docs/q13-fix-pack/apply_fix_pack.py` (clause `E1`; blast radius 1 file: `event-2022-08-18-the-bitcoin-beach-campaign-closes-and-hodlon/index.html`) | +| `E2` | event-2022-08-28-the-fork-of-1492 | `https://alexanderwhitestone.com/stackchain/event-2022-08-28-the-fork-of-1492/` (200 / 4,380 B / sha256 `a55da02af6a3…`) | Uncertainty | `Event status as recorded by the history research: event corroborated; tactics from participant retrospective. The date basis is the reported event date, not necessarily a verified post timestamp.` | `Event status as recorded by the history research: event corroborated; tactics from participant retrospective. The date basis is the reported event date, not necessarily a verified post timestamp.` + ` Note 2026-10-10: the named warrant “StackchainQuant” returns HTTP 404 at https://x.com/StackchainQuant (X: “User Profile Not Found”). The report it attributes is separately cited and live; the account itself is unreachable as of this date.` | recoveries/named-source-liveness-unlinked.md finding 1 (2026-10-10T17:49:31Z) + recoveries/named-source-liveness.md row 6 (2026-10-10T15:39:39Z) | undecided-for-Alexander | `docs/q13-fix-pack/apply_fix_pack.py` (clause `E2`; blast radius 1 file: `event-2022-08-28-the-fork-of-1492/index.html`) | +| `E3` | event-2022-10-05-the-community-documents-its-scale-and-charit | `https://alexanderwhitestone.com/stackchain/event-2022-10-05-the-community-documents-its-scale-and-charit/` (200 / 3,929 B / sha256 `a1e2eb193c83…`) | Uncertainty | `Event status as recorded by the history research: sourced. The date basis is the reported event date, not necessarily a verified post timestamp.` | `Event status as recorded by the history research: sourced. The date basis is the reported event date, not necessarily a verified post timestamp.` + ` Note 2026-10-10: the named warrant “StackchainQuant” returns HTTP 404 at https://x.com/StackchainQuant (X: “User Profile Not Found”). The report it attributes is separately cited and live; the account itself is unreachable as of this date.` | recoveries/named-source-liveness-unlinked.md finding 1 (2026-10-10T17:49:31Z) + recoveries/named-source-liveness.md row 6 (2026-10-10T15:39:39Z) | undecided-for-Alexander | `docs/q13-fix-pack/apply_fix_pack.py` (clause `E3`; blast radius 1 file: `event-2022-10-05-the-community-documents-its-scale-and-charit/index.html`) | +| `F1` | magazine-020 | `https://alexanderwhitestone.com/stackchain/magazine-020/` (200 / 4,123 B / sha256 `88ae01d3aba8…`) | Uncertainty | UNC_MAG (see §2) | UNC_MAG (see §2) + ` Note 2026-10-10: the byline handle @_overflow__ returns HTTP 404 at https://x.com/_overflow__ (X: “User Profile Not Found”). The name is retained as printed and is not currently linkable.` | recoveries/named-source-liveness.md row 1 (2026-10-10T15:39:17Z) | undecided-for-Alexander | `docs/q13-fix-pack/apply_fix_pack.py` (clause `F1`; blast radius 1 file: `magazine-020/index.html`) | +| `F2` | magazine-051 | `https://alexanderwhitestone.com/stackchain/magazine-051/` (200 / 4,529 B / sha256 `2e3065c6c7a7…`) | Uncertainty | UNC_MAG (see §2) | UNC_MAG (see §2) + ` Note 2026-10-10: the byline handle @humblebitcoiner returns HTTP 404 at https://x.com/humblebitcoiner (X: “User Profile Not Found”). The name is retained as printed and is not currently linkable.` | recoveries/named-source-liveness.md row 2 (2026-10-10T15:39:33Z) | undecided-for-Alexander | `docs/q13-fix-pack/apply_fix_pack.py` (clause `F2`; blast radius 1 file: `magazine-051/index.html`) | +| `G1` | magazine-035 | `https://alexanderwhitestone.com/stackchain/magazine-035/` (200 / 4,543 B / sha256 `871638b8abb5…`) | Contributor | contributor.display_name: `"@BTC_Freeborn and @AnthonyDessauer"`
contributor.handle: `null` | contributor.display_name: `"BTC_Freeborn and Anthony Dessauer"`
contributor.handle: `"BTC_Freeborn"` | recoveries/named-source-liveness.md row 3 (2026-10-10T15:39:21Z) | undecided-for-Alexander | `docs/q13-fix-pack/apply_fix_pack.py` (clause `G1`; blast radius 1 file: `magazine-035/index.html`) | +| `G2` | magazine-056 | `https://alexanderwhitestone.com/stackchain/magazine-056/` (200 / 4,346 B / sha256 `33b55f8f61fe…`) | Contributor | contributor.display_name: `"Red Tail Hawk (@RedTailHawk1923)"`
contributor.handle: `null` | contributor.display_name: `"Red Tail Hawk"`
contributor.handle: `"RedTailHawk1923"` | recoveries/named-source-liveness.md row 4 (2026-10-10T15:39:33Z) | undecided-for-Alexander | `docs/q13-fix-pack/apply_fix_pack.py` (clause `G2`; blast radius 1 file: `magazine-056/index.html`) | +| `G3` | magazine-039 | `https://alexanderwhitestone.com/stackchain/magazine-039/` (200 / 4,272 B / sha256 `65e07c4e638a…`) | Contributor + Uncertainty | contributor.handle: `null`
uncertainty: `Archive summary of a published article, not the article itself. Quotes are short verbatim excerpts; read the original for full context.` | contributor.handle: `"captainsiddh"`
uncertainty: `Archive summary of a published article, not the article itself. Quotes are short verbatim excerpts; read the original for full context. Note 2026-10-10: the handle @captainsiddh resolves, but its own profile description reads “Now at @captainsidd” and https://x.com/captainsidd resolves; the byline above is one handle behind the account’s current home.` | recoveries/named-source-liveness.md row 5 (2026-10-10T15:39:22Z) | undecided-for-Alexander | `docs/q13-fix-pack/apply_fix_pack.py` (clause `G3`; blast radius 1 file: `magazine-039/index.html`) | +| `H1` | event-2022-07-18-the-5-invitation-becomes-a-chain | `https://alexanderwhitestone.com/stackchain/event-2022-07-18-the-5-invitation-becomes-a-chain/` (200 / 4,281 B / sha256 `f208a26cd154…`) | What happened + In plain English | body: `AriZonanHODL posted a $5 bitcoin purchase`
plain_summary.text: `AriZonanHODL posted a $5 bitcoin purchase` | body: `@AriZonanHODL posted a $5 bitcoin purchase`
plain_summary.text: `@AriZonanHODL posted a $5 bitcoin purchase` | recoveries/named-source-liveness-unlinked.md finding 2 (2026-10-10T17:49:31Z); https://x.com/AriZonanHODL 200 / 130,908 B | undecided-for-Alexander | `docs/q13-fix-pack/apply_fix_pack.py` (clause `H1`; blast radius 1 file: `event-2022-07-18-the-5-invitation-becomes-a-chain/index.html`) | +| `H2` | event-2022-07-18-the-5-invitation-becomes-a-chain | `https://alexanderwhitestone.com/stackchain/event-2022-07-18-the-5-invitation-becomes-a-chain/` (200 / 4,281 B / sha256 `f208a26cd154…`) | What happened | `and Satskeeper with $7` | `and @satskeeper_ with $7` | WORK-QUEUE.md §Q24 addendum 2026-10-10 18:13Z (Satskeeper resolves as @satskeeper_, already linked on block-7) | undecided-for-Alexander | `docs/q13-fix-pack/apply_fix_pack.py` (clause `H2`; blast radius 1 file: `event-2022-07-18-the-5-invitation-becomes-a-chain/index.html`) | +| `H3` | magazine-002 | `https://alexanderwhitestone.com/stackchain/magazine-002/` (200 / 4,433 B / sha256 `13a31d232159…`) | What happened | `ArizonanHODL's $5` | `@AriZonanHODL's $5` | WORK-QUEUE.md §Q23 verification correction (rule 3b yields 8 bare-and-unlinked mentions across 6 entries) — CONDITIONAL on step 5’s filter | undecided-for-Alexander (conditional row) | `docs/q13-fix-pack/apply_fix_pack.py` (clause `H3`; blast radius 1 file: `magazine-002/index.html`) | +| `H4` | magazine-005 | `https://alexanderwhitestone.com/stackchain/magazine-005/` (200 / 4,287 B / sha256 `5681dc960dfe…`) | What happened | `ArizonanHodl's July 18, 2022 tweet` | `@AriZonanHODL's July 18, 2022 tweet` | WORK-QUEUE.md §Q23 verification correction (rule 3b) — CONDITIONAL on step 5’s filter | undecided-for-Alexander (conditional row) | `docs/q13-fix-pack/apply_fix_pack.py` (clause `H4`; blast radius 1 file: `magazine-005/index.html`) | +| `I1` | magazine-026 | `https://alexanderwhitestone.com/stackchain/magazine-026/` (200 / 4,554 B / sha256 `67f141e2b1c1…`) | Contributor | `null` | `"a_koby"` | recoveries/evidence-tier-audit.md §6.3 (2026-10-10) — OUTSIDE the Q24 population rule, which names findings 1–2; included and flagged | undecided-for-Alexander | `docs/q13-fix-pack/apply_fix_pack.py` (clause `I1`; blast radius 1 file: `magazine-026/index.html`) | +| `I2` | magazine-070 | `https://alexanderwhitestone.com/stackchain/magazine-070/` (200 / 4,599 B / sha256 `6227e199522f…`) | Contributor | contributor.display_name: `"@MidyReyes"`
contributor.handle: `null` | contributor.display_name: `"MidyReyes"`
contributor.handle: `"MidyReyes"` | WORK-QUEUE.md §Q23 verification correction (rule 3a admits MidyReyes on magazine-070) — OUTSIDE the Q24 population rule; included and flagged | undecided-for-Alexander | `docs/q13-fix-pack/apply_fix_pack.py` (clause `I2`; blast radius 1 file: `magazine-070/index.html`) | +| `J1` | magazine-024 | `https://alexanderwhitestone.com/stackchain/magazine-024/` (200 / 4,150 B / sha256 `076df5d248de…`) | Uncertainty | UNC_MAG (see §2) | UNC_MAG (see §2) + ` Note 2026-10-10: the tipping address bitcoinbekka@getalby.com resolves at its canonical LNURL-pay endpoint https://getalby.com/.well-known/lnurlp/bitcoinbekka (HTTP 200); the vanity web path https://getalby.com/bitcoinbekka returns 404. It is not linked here because the ledger build emits anchors only for contributor handles, body sources and excerpt URLs.` | recoveries/named-source-liveness-unlinked.md finding 3 (2026-10-10T17:49:31Z) | undecided-for-Alexander | `docs/q13-fix-pack/apply_fix_pack.py` (clause `J1`; blast radius 1 file: `magazine-024/index.html`) | + +## 4. Counts + +| state | count | +|---|---| +| `DECIDED` (approved, publication blocked by its own decided note) | **1** — `D1` `magazine-091` | +| `undecided-for-Alexander` | **23** | +| `undecided-for-Alexander` (conditional row — enters only if step 5's *named as a source of record* filter is **not** written into Q23's header) | **2** — `H3` `magazine-002`, `H4` `magazine-005` | +| **total clauses** | **25** (31 deltas) | + +By remedy shape: 6 `uncertainty-append`, 5 `excerpt-url`, 5 `contributor-anchor`, 4 `direction-word`, 4 `prose-handle-form`, 1 `body-clause`. (`G3` carries an `uncertainty` note alongside its +`contributor.handle` delta, which is why its panel reads *Contributor + Uncertainty*.) + +## 5. Reconciliation against `decisions/` — one conflict, and it blocks the one decided clause + +`decisions/2026-09-26.md` item 13 (`magazine-091`) is **approved**, `decided_at 2026-09-26T14:51:11.019Z`, +relayed by `2eb50b7`. Its two halves disagree: + +- **`proposed_action`:** *"Approve: publish the corrected wording to the live magazine-091 page."* +- **`decided note`:** *"Do we even have authority to fix the publication? Add our note and escalate it to + the author and mag publisher."* + +Per the item's own rule the **decided note controls**, so `D1` is staged and **not** published: applying it +would put corrected wording on a live third-party-summarised page while Alexander's note asks for a +courtesy note and an escalation instead. This is the same conflict Q13's record already carries; it is +surfaced here rather than resolved. **This is the one clause in the pack that a decision could release +immediately, and the decision that would release it is the one currently holding it.** + +`decisions/2026-10-06-human-gates.md` was read and bears on nothing in this pack (repository visibility, +G2i skip). `decisions/2026-10-07.md` and `2026-10-07-cassian-write-grant.md` are cassian's write grant and +bear on nothing here. **Nothing in `decisions/` was executed.** + +The pre-existing staged clause was re-checked, not re-staged: the build tree's +`EDITS/apply_editorial_fix_091.py` in check-only mode still passes against today's `blocks.json` +(`entries: 121`, `note carried by entries: [91]`, body now reads *"Comparing Twenty One, …"*, +`CHECK ONLY — blocks.json not written`). `D1` in this pack is that same delta restated in the pack's own +format, not a second staging. + +## 6. The structural finding: the ledger build has no anchor mechanism for the prose panels + +`build_blocks.py` emits an `
` in exactly three places — `contributor.handle` +(`render_entry` line 310), `body_source_url` (line 323) and `excerpts[].url` (line 333). `body`, +`plain_summary.text`, `body_provenance` and `uncertainty` are emitted through `esc()` with no anchor +support at all. + +**Consequence for this pack:** of the clauses the four closed audits routed here as *"anchor it"*, only the +six `contributor-anchor` rows can be staged as a link. Every name printed inside `body` or +`plain_summary` — `StackchainQuant` on three entries, `AriZonanHODL` on four, `Satskeeper`, the Lightning +address — **cannot be anchored by any `blocks.json` field**. Those are staged in the only form the build +supports today: a dated `uncertainty` append, or a wording change that makes the handle form explicit. + +Two things follow, both for Alexander and neither for this run: + +1. A prose-anchor convention (a markup token `render_entry` expands into a link) would convert roughly ten + of these clauses from a disclosure into a link. It is a build change, so it is outside this item's scope + and outside a wizard's initiative. +2. `contributor` has **one** `handle` slot. `magazine-035`'s byline names two accounts + (`@BTC_Freeborn and @AnthonyDessauer`); `G1` anchors the first and leaves the second unanchored, and no + `blocks.json` edit can do better without a schema change. + +## 7. Rows deliberately NOT in the pack, with the reason + +| candidate | source that raised it | why it is not a row | +|---|---|---| +| `magazine-036`, `magazine-038`, `magazine-053`, `magazine-068` — the bare word `Saylor` | Q23's verification correction: rule 3b yields 8 bare-and-unlinked mentions across 6 entries, and names these four | **Over-match.** The token fired only because `https://x.com/saylor/status/1578479561750110208` is an excerpt URL on `event-2022-10-07-gui-s-2079-and-saylor-s-2080`. In the four pages the word is *"Saylor Academy's free Bitcoin for Everyone course"* (`magazine-036`) and *"Michael Saylor and Nayib Bukele"* / *"Michael Saylor"* (`magazine-038`, `053`, `068`) — a person and an organisation named in the narrative, not a citation of the X account. No dead source was missed; the detector cannot tell the two apart. | +| `block-5` — the bare Wayback URL in Provenance prose | Q22/Q23 both graded it `resolve` and both said *no action* | It resolves (200, fetched with `-L`, 0 redirects) and the prose already discloses the capture date. Anchoring it would need the same missing prose mechanism. No defect. | +| the `x.com/i/…` "nine-segment" addition | Q24's own step 5, as first drafted | **Withdrawn by the pipe** in the 2026-10-10 18:13Z addendum: no `` on any page the index links points at `x.com/i/…`. Adds nothing. | +| `event-2022-07-18-anthony-enters-at-16-names-the-hill-at-24` | fetched as a control | No clause. `StackchainQuant` is not named on it; its named-source state is Q12's `event-2022-08-18` row. | + +## 8. Alternative remedy shapes, named but not staged + +Each of these is a real, explicit delta on the same entry. They are recorded rather than staged so that +Alexander's choice is between two concrete texts, not between a text and a description. + +- **Strike instead of mark, `E1`–`E3`.** Deleting the `StackchainQuant` clause from `body` removes a + substantive claim the entry currently makes (the October account's report of several thousand dollars + raised; `1492` as a notable fork; the ~400-participant / $1.5 million figures). The report survives + through the separately cited Bitcoin Magazine anchor; the attribution does not. Marking keeps both and + discloses the dead warrant. +- **Anchor instead of mark, `F1` `magazine-020` and `F2` `magazine-051`.** Set `contributor.handle` to + `_overflow__` / `humblebitcoiner`. Both handles return **404**, so this gives the reader a link that + opens X's *"User Profile Not Found"* page. Honest about the state, worse than the note. +- **Handle the move rather than only the anchor, `G3` `magazine-039`.** `@captainsiddh` resolves but its own + description reads *"Now at @captainsidd"* and `https://x.com/captainsidd` resolves. Anchoring + `captainsidd` instead of `captainsiddh` would point at the live account; `G3` stages `captainsiddh` + because that is the handle the entry prints, plus a note naming the move. Anchoring `captainsidd` + silently changes what the byline says. + +## 9. Staging record + +**Staged file:** `docs/q13-fix-pack/apply_fix_pack.py` — 25,361 B, sha256 +`2d39df3cdfe466b33e45b19b8741f43b536d93e7ccde90f871e50e5cd6ffd1cd`. One script, 25 clause ids, because 25 near-identical scripts would put 25 copies of the +same scoping logic in front of a reviewer instead of one; the clause table is embedded in the file as +JSON and `--list` prints it. + +**Committed unrun.** Every clause was executed in **check mode** during this run — which verifies the +locator, the field span, and that each `old` occurs exactly once inside it — and **not one clause was +applied to the build tree's `blocks.json`**. Check output for all 25 clauses, exit codes included, is in +`/root/crucible/q24/CHECK-RUN.txt`; the scratch applications used to measure blast radius ran against +copies under `/root/crucible/q24/blast/`. + +**Before hash (all clauses):** `blocks.json` sha256 `63c9cee6103a6824c90c71440fa983d2f5592e380f9c3fb40f7ad366ae41723d`, 278,459 B, read-only this run. + +**Revert command, per clause** (printed by `--apply`; identical shape for all 25): + +``` +python3 docs/q13-fix-pack/apply_fix_pack.py --apply # forward +# revert: restore blocks.json from a pre-apply backup, or reverse the printed deltas: +# : -> +``` + +**Guard against an unapproved apply:** a clause marked `undecided-for-Alexander` refuses to apply unless +**both** `--force` and `--decision ` are passed, so the approval has to be named on the +command line. `D1` applies on its decision record alone — see §5 for why that is still blocked. + +**Blast radius, per clause:** measured by applying the clause to a scratch copy, rebuilding, and diffing +the emitted tree against the pristine one. **25 of 25 clauses change exactly 1 emitted file — the entry's +own `index.html`.** No clause touches `blocks/index.html`, `sitemap.xml` or `ledger.css`. Combined, all +25 clauses change **20 of 125** emitted files (`block-25/88/89/90`, `event-2022-07-18-the-5-invitation- +becomes-a-chain`, `event-2022-08-18…hodlon`, `event-2022-08-28-the-fork-of-1492`, +`event-2022-10-05…charit`, `event-2023-05-00-siggy-reaches-nostr`, `magazine-002/005/020/024/026/035/ +039/051/056/070/091`). Per-clause and combined tables: `/root/crucible/q24/BLAST-RADIUS.txt`, +`/root/crucible/q24/BLAST-ALL.txt`. + +Spot checks on the applied scratch pages: `block-25` carries the +`a-conversation-with-burnchainers` permalink and no longer carries the bare site root as an anchor, and +reads *"quoted above"*; `magazine-091` no longer contains *"Comparing Strategy"*, contains *"Comparing +Twenty One"*, and carries the correction note; `magazine-039` carries +`href="https://x.com/captainsiddh"` and the move note; `magazine-070` carries +`href="https://x.com/MidyReyes"`. + +## 10. Method and limits + +- **Every population was re-derived, none copied.** Entry titles are unique across all 121 entries + (checked), which is what makes the title locator safe. The bare site root appears as an excerpt URL + exactly **4** times in the whole file — the four `block-*` rows — so the `A` group is a complete census + of that defect, not a sample. +- **Field-scoped, not region-scoped.** My first verifier searched each entry's whole JSON region and + reported `body` old-strings occurring **2×** on `event-2022-07-18`, because `body` and + `plain_summary.text` share the sentence. Scoping to each field's own span is what makes `H1`'s two + deltas land in two places and nothing else. Logged as a defect of mine, found and fixed before staging. +- **Two defects in the generated script, logged first.** Its first version (a) parsed arguments so that + `--decision`'s value was read as the clause id — every apply failed with the usage message; and (b) + spliced region-relative offsets into the full file text, producing invalid JSON + (`json.decoder.JSONDecodeError`). Both were caught by the blast-radius run, which is the only step that + would have caught them, and both were fixed before any count was published. +- **The proposed new values were validated, not assumed.** `https://www.stackchainmagazine.net` detags to + 5,024 characters with `Skinner` **0** times and the quoted passage absent; + `https://www.stackchainmagazine.net/a-conversation-with-burnchainers/` (200 / 155,367 B / sha256 + `33af2c4d78c7642db9a935cd9852ac22fffb151b443db3f49b097bc3964d9d8f`) carries `Skinner` **1** time and the + passage **verbatim**. `https://github.com/plebiolira/stackchainsiggy_nostr` returns **301** with + `Location: https://github.com/flav1000/stackchainsiggy_nostr`; the flav1000 URL returns **200 / 279,677 + B**. In `/root/crucible/q24/TARGET-READS.txt`. +- **Nothing was re-tested.** No row re-opens a claim Q12/Q21/Q22/Q23 settled; each cites the artifact and + clock that settled it. Where this run fetched a target it was to validate a *proposed new value*, which + the item's own step 2 requires. +- **Dated readings.** Every byte count and sha256 is what this host read at the printed clock. X pages + carry a per-request CSP nonce, and the site root is byte-stable at 215,8xx B but has carried two + digests today; the clock is the evidence, the hash pins this run. +- **Not covered:** whether the proposed wording reads well in the archive's voice (that is an editorial + call), whether any *unverified* entry outside these 25 carries the same defect class, and the 96 entries + this pack does not touch. + +## 11. Evidence files, all on this host + +| file | what it holds | +|---|---| +| `/root/crucible/q24/LIVE-READS.txt` | index + 25 entry pages: HTTP status, bytes, full sha256, and same-as-emitted | +| `/root/crucible/q24/TARGET-READS.txt` | the four URLs the proposed values point at, with term counts and the verbatim test | +| `/root/crucible/q24/CHECK-RUN.txt` | `apply_fix_pack.py ` in check mode, all 25 clauses, with exit codes | +| `/root/crucible/q24/BLAST-RADIUS.txt` | per-clause emitted-file radius with before/after sha256 prefixes | +| `/root/crucible/q24/BLAST-ALL.txt` | the combined 20-of-125 radius with before/after sha256 | +| `/root/crucible/q24/CONTROL-091.txt` | the pre-existing staged `091` script re-run in check mode | +| `/root/crucible/q24/mkclauses.py` | the clause table and its field-span verifier (re-runnable) | +| `/root/crucible/q24/gen_apply.py` | the generator for the staged script (re-runnable) | + +— Merlin, 2026-10-10 diff --git a/docs/q13-fix-pack/apply_fix_pack.py b/docs/q13-fix-pack/apply_fix_pack.py new file mode 100644 index 0000000..f99441e --- /dev/null +++ b/docs/q13-fix-pack/apply_fix_pack.py @@ -0,0 +1,643 @@ +#!/usr/bin/env python3 +"""Q13 fix pack - staged apply script for the 25 verified live-page clauses. + +Generated by Merlin, 2026-10-10, from the verified clause table in +docs/q13-fix-pack.md. NOT RUN AS AN APPLY: every clause was exercised in CHECK +mode only during the 2026-10-10 run (see the pack's landing evidence). Nothing in +that run wrote to blocks.json. + +Scope, inherited from Q24 and Q13: this script edits the build source +/root/stackchain-legends-build/blocks.json only. It does not rebuild, does not +publish, does not contact a third party, and does not touch decisions/. + + python3 apply_fix_pack.py --list + python3 apply_fix_pack.py --check # default: verify, write nothing + python3 apply_fix_pack.py --apply # write blocks.json + +A clause marked undecided-for-Alexander refuses to apply unless BOTH --force and +--decision are given, so a staged fix cannot be published without +the approval being named on the command line. The one decided clause (D1, +magazine-091) applies on its decision record alone - but see section 5 of +docs/q13-fix-pack.md: its decided note conflicts with its proposed_action, so +applying D1 publishes a live page Alexander's own note asked to hold. + +Fails closed: every delta must resolve exactly once inside its entry's own field +span, otherwise nothing is written. Reverting: reverse the printed deltas, or +restore blocks.json from a backup taken before --apply. +""" +from __future__ import annotations + +import json +import pathlib +import re +import sys + +BASE = pathlib.Path(__file__).resolve().parents[3] +BLOCKS = BASE / "stackchain-legends-build" / "blocks.json" +CLAUSES = json.loads(r"""[ + { + "id": "A1", + "slug": "block-25", + "kind": "excerpt-url", + "panel": "From the sources", + "authority": "recoveries/evidence-tier-audit.md §6.1 (2026-10-10 ~09:3xZ) + recoveries/excerpt-binding-audit.md finding 1 (2026-10-10T13:26:16Z)", + "decided": "undecided-for-Alexander", + "ops": [ + { + "field": "excerpts[0].url", + "old": "\"https://www.stackchainmagazine.net\"", + "new": "\"https://www.stackchainmagazine.net/a-conversation-with-burnchainers/\"" + } + ], + "locator": "\"title\": \"Wanting block 25, and being forked twice\"" + }, + { + "id": "A2", + "slug": "block-88", + "kind": "excerpt-url", + "panel": "From the sources", + "authority": "recoveries/evidence-tier-audit.md §6.1 (2026-10-10 ~09:3xZ) + recoveries/excerpt-binding-audit.md finding 1 (2026-10-10T13:26:16Z)", + "decided": "undecided-for-Alexander", + "ops": [ + { + "field": "excerpts[0].url", + "old": "\"https://www.stackchainmagazine.net\"", + "new": "\"https://www.stackchainmagazine.net/a-conversation-with-burnchainers/\"" + } + ], + "locator": "\"title\": \"Block 88, bought to secure the longer chain\"" + }, + { + "id": "A3", + "slug": "block-89", + "kind": "excerpt-url", + "panel": "From the sources", + "authority": "recoveries/evidence-tier-audit.md §6.1 (2026-10-10 ~09:3xZ) + recoveries/excerpt-binding-audit.md finding 1 (2026-10-10T13:26:16Z)", + "decided": "undecided-for-Alexander", + "ops": [ + { + "field": "excerpts[0].url", + "old": "\"https://www.stackchainmagazine.net\"", + "new": "\"https://www.stackchainmagazine.net/a-conversation-with-burnchainers/\"" + } + ], + "locator": "\"title\": \"Block 89, part of the 88-90 run\"" + }, + { + "id": "A4", + "slug": "block-90", + "kind": "excerpt-url", + "panel": "From the sources", + "authority": "recoveries/evidence-tier-audit.md §6.1 (2026-10-10 ~09:3xZ) + recoveries/excerpt-binding-audit.md finding 1 (2026-10-10T13:26:16Z)", + "decided": "undecided-for-Alexander", + "ops": [ + { + "field": "excerpts[0].url", + "old": "\"https://www.stackchainmagazine.net\"", + "new": "\"https://www.stackchainmagazine.net/a-conversation-with-burnchainers/\"" + } + ], + "locator": "\"title\": \"Block 90, and the chain held\"" + }, + { + "id": "B1", + "slug": "block-25", + "kind": "direction-word", + "panel": "Provenance", + "authority": "WORK-QUEUE.md §Q21 verification addendum, control plane, 2026-10-10 (offsets quoted there)", + "decided": "undecided-for-Alexander", + "ops": [ + { + "field": "body_provenance", + "old": "quoted below", + "new": "quoted above" + } + ], + "locator": "\"title\": \"Wanting block 25, and being forked twice\"" + }, + { + "id": "B2", + "slug": "block-88", + "kind": "direction-word", + "panel": "Provenance", + "authority": "WORK-QUEUE.md §Q21 verification addendum, control plane, 2026-10-10 (offsets quoted there)", + "decided": "undecided-for-Alexander", + "ops": [ + { + "field": "body_provenance", + "old": "quoted below", + "new": "quoted above" + } + ], + "locator": "\"title\": \"Block 88, bought to secure the longer chain\"" + }, + { + "id": "B3", + "slug": "block-89", + "kind": "direction-word", + "panel": "Provenance", + "authority": "WORK-QUEUE.md §Q21 verification addendum, control plane, 2026-10-10 (offsets quoted there)", + "decided": "undecided-for-Alexander", + "ops": [ + { + "field": "body_provenance", + "old": "quoted below", + "new": "quoted above" + } + ], + "locator": "\"title\": \"Block 89, part of the 88-90 run\"" + }, + { + "id": "B4", + "slug": "block-90", + "kind": "direction-word", + "panel": "Provenance", + "authority": "WORK-QUEUE.md §Q21 verification addendum, control plane, 2026-10-10 (offsets quoted there)", + "decided": "undecided-for-Alexander", + "ops": [ + { + "field": "body_provenance", + "old": "quoted below", + "new": "quoted above" + } + ], + "locator": "\"title\": \"Block 90, and the chain held\"" + }, + { + "id": "C1", + "slug": "event-2023-05-00-siggy-reaches-nostr", + "kind": "excerpt-url", + "panel": "From the sources", + "authority": "recoveries/excerpt-binding-audit.md fetch note (301 observed) + recoveries/summary-fidelity-nonmagazine.md ev-017 row 1 + recoveries/ledger-audit-pass2.md row 19; 301 re-observed this run", + "decided": "undecided-for-Alexander", + "ops": [ + { + "field": "excerpts[0].url", + "old": "\"https://github.com/plebiolira/stackchainsiggy_nostr\"", + "new": "\"https://github.com/flav1000/stackchainsiggy_nostr\"" + } + ], + "locator": "\"title\": \"Siggy reaches Nostr\"" + }, + { + "id": "D1", + "slug": "magazine-091", + "kind": "body-clause", + "panel": "What happened + Uncertainty", + "authority": "decisions/2026-09-26.md item 13, approved 2026-09-26T14:51:11.019Z; staged record /root/stackchain-legends-build/EDITS/2026-09-26-magazine-091-correction.md", + "decided": "DECIDED (approved) — decided note conflicts with proposed_action; see §5", + "ops": [ + { + "field": "body", + "old": "Comparing Strategy, Twenty One, Nakamoto, Metaplanet, Coinsilium and Smarter Web returns and drawdowns against Bitcoin, they find none consistently outperform,", + "new": "Comparing Twenty One, Nakamoto, Metaplanet, Coinsilium and Smarter Web against Bitcoin — with earlier pioneers such as Strategy set aside as not a fair comparison — they find none consistently outperform," + }, + { + "field": "uncertainty", + "old": "Archive summary of a published article, not the article itself. Quotes are short verbatim excerpts; read the original for full context.", + "new": "Archive summary of a published article, not the article itself. Quotes are short verbatim excerpts; read the original for full context. Correction 2026-09-26: this summary previously listed Strategy among the firms compared. The article sets Strategy aside (“not a fair comp for these newcomers”) and excludes it from the comparison; the listing was the archive’s error, not the article’s. Strategy has been removed from the compared set." + } + ], + "locator": "\"title\": \"Fact Check: Bitcoin vs. Treasury Stocks.Who performs better?\"" + }, + { + "id": "E1", + "slug": "event-2022-08-18-the-bitcoin-beach-campaign-closes-and-hodlon", + "kind": "uncertainty-append", + "panel": "Uncertainty", + "authority": "recoveries/named-source-liveness-unlinked.md finding 1 (2026-10-10T17:49:31Z) + recoveries/named-source-liveness.md row 6 (2026-10-10T15:39:39Z)", + "decided": "undecided-for-Alexander", + "ops": [ + { + "field": "uncertainty", + "old": "Event status as recorded by the history research: sourced. The date basis is the reported event date, not necessarily a verified post timestamp.", + "new": "Event status as recorded by the history research: sourced. The date basis is the reported event date, not necessarily a verified post timestamp. Note 2026-10-10: the named warrant “StackchainQuant” returns HTTP 404 at https://x.com/StackchainQuant (X: “User Profile Not Found”). The report it attributes is separately cited and live; the account itself is unreachable as of this date." + } + ], + "locator": "\"title\": \"The Bitcoin Beach campaign closes and Hodlonaut support appears\"" + }, + { + "id": "E2", + "slug": "event-2022-08-28-the-fork-of-1492", + "kind": "uncertainty-append", + "panel": "Uncertainty", + "authority": "recoveries/named-source-liveness-unlinked.md finding 1 (2026-10-10T17:49:31Z) + recoveries/named-source-liveness.md row 6 (2026-10-10T15:39:39Z)", + "decided": "undecided-for-Alexander", + "ops": [ + { + "field": "uncertainty", + "old": "Event status as recorded by the history research: event corroborated; tactics from participant retrospective. The date basis is the reported event date, not necessarily a verified post timestamp.", + "new": "Event status as recorded by the history research: event corroborated; tactics from participant retrospective. The date basis is the reported event date, not necessarily a verified post timestamp. Note 2026-10-10: the named warrant “StackchainQuant” returns HTTP 404 at https://x.com/StackchainQuant (X: “User Profile Not Found”). The report it attributes is separately cited and live; the account itself is unreachable as of this date." + } + ], + "locator": "\"title\": \"The fork of 1492\"" + }, + { + "id": "E3", + "slug": "event-2022-10-05-the-community-documents-its-scale-and-charit", + "kind": "uncertainty-append", + "panel": "Uncertainty", + "authority": "recoveries/named-source-liveness-unlinked.md finding 1 (2026-10-10T17:49:31Z) + recoveries/named-source-liveness.md row 6 (2026-10-10T15:39:39Z)", + "decided": "undecided-for-Alexander", + "ops": [ + { + "field": "uncertainty", + "old": "Event status as recorded by the history research: sourced. The date basis is the reported event date, not necessarily a verified post timestamp.", + "new": "Event status as recorded by the history research: sourced. The date basis is the reported event date, not necessarily a verified post timestamp. Note 2026-10-10: the named warrant “StackchainQuant” returns HTTP 404 at https://x.com/StackchainQuant (X: “User Profile Not Found”). The report it attributes is separately cited and live; the account itself is unreachable as of this date." + } + ], + "locator": "\"title\": \"The community documents its scale and charitable turn\"" + }, + { + "id": "F1", + "slug": "magazine-020", + "kind": "uncertainty-append", + "panel": "Uncertainty", + "authority": "recoveries/named-source-liveness.md row 1 (2026-10-10T15:39:17Z)", + "decided": "undecided-for-Alexander", + "ops": [ + { + "field": "uncertainty", + "old": "Archive summary of a published article, not the article itself. Quotes are short verbatim excerpts; read the original for full context.", + "new": "Archive summary of a published article, not the article itself. Quotes are short verbatim excerpts; read the original for full context. Note 2026-10-10: the byline handle @_overflow__ returns HTTP 404 at https://x.com/_overflow__ (X: “User Profile Not Found”). The name is retained as printed and is not currently linkable." + } + ], + "locator": "\"title\": \"The coming Occupy Wall St\"" + }, + { + "id": "F2", + "slug": "magazine-051", + "kind": "uncertainty-append", + "panel": "Uncertainty", + "authority": "recoveries/named-source-liveness.md row 2 (2026-10-10T15:39:33Z)", + "decided": "undecided-for-Alexander", + "ops": [ + { + "field": "uncertainty", + "old": "Archive summary of a published article, not the article itself. Quotes are short verbatim excerpts; read the original for full context.", + "new": "Archive summary of a published article, not the article itself. Quotes are short verbatim excerpts; read the original for full context. Note 2026-10-10: the byline handle @humblebitcoiner returns HTTP 404 at https://x.com/humblebitcoiner (X: “User Profile Not Found”). The name is retained as printed and is not currently linkable." + } + ], + "locator": "\"title\": \"The Obstacle Is The Way – Finding Hope In Bitcoin\"" + }, + { + "id": "G1", + "slug": "magazine-035", + "kind": "contributor-anchor", + "panel": "Contributor", + "authority": "recoveries/named-source-liveness.md row 3 (2026-10-10T15:39:21Z)", + "decided": "undecided-for-Alexander", + "ops": [ + { + "field": "contributor.display_name", + "old": "\"@BTC_Freeborn and @AnthonyDessauer\"", + "new": "\"BTC_Freeborn and Anthony Dessauer\"" + }, + { + "field": "contributor.handle", + "old": "null", + "new": "\"BTC_Freeborn\"" + } + ], + "locator": "\"title\": \"WAHT Stackchain Done Dided: The King of StackChain™\"" + }, + { + "id": "G2", + "slug": "magazine-056", + "kind": "contributor-anchor", + "panel": "Contributor", + "authority": "recoveries/named-source-liveness.md row 4 (2026-10-10T15:39:33Z)", + "decided": "undecided-for-Alexander", + "ops": [ + { + "field": "contributor.display_name", + "old": "\"Red Tail Hawk (@RedTailHawk1923)\"", + "new": "\"Red Tail Hawk\"" + }, + { + "field": "contributor.handle", + "old": "null", + "new": "\"RedTailHawk1923\"" + } + ], + "locator": "\"title\": \"Culture is Bitcoin\"" + }, + { + "id": "G3", + "slug": "magazine-039", + "kind": "contributor-anchor", + "panel": "Contributor + Uncertainty", + "authority": "recoveries/named-source-liveness.md row 5 (2026-10-10T15:39:22Z)", + "decided": "undecided-for-Alexander", + "ops": [ + { + "field": "contributor.handle", + "old": "null", + "new": "\"captainsiddh\"" + }, + { + "field": "uncertainty", + "old": "Archive summary of a published article, not the article itself. Quotes are short verbatim excerpts; read the original for full context.", + "new": "Archive summary of a published article, not the article itself. Quotes are short verbatim excerpts; read the original for full context. Note 2026-10-10: the handle @captainsiddh resolves, but its own profile description reads “Now at @captainsidd” and https://x.com/captainsidd resolves; the byline above is one handle behind the account’s current home." + } + ], + "locator": "\"title\": \"Traveling on a Bitcoin Standard\"" + }, + { + "id": "H1", + "slug": "event-2022-07-18-the-5-invitation-becomes-a-chain", + "kind": "prose-handle-form", + "panel": "What happened + In plain English", + "authority": "recoveries/named-source-liveness-unlinked.md finding 2 (2026-10-10T17:49:31Z); https://x.com/AriZonanHODL 200 / 130,908 B", + "decided": "undecided-for-Alexander", + "ops": [ + { + "field": "body", + "old": "AriZonanHODL posted a $5 bitcoin purchase", + "new": "@AriZonanHODL posted a $5 bitcoin purchase" + }, + { + "field": "plain_summary.text", + "old": "AriZonanHODL posted a $5 bitcoin purchase", + "new": "@AriZonanHODL posted a $5 bitcoin purchase" + } + ], + "locator": "\"title\": \"The $5 invitation becomes a chain\"" + }, + { + "id": "H2", + "slug": "event-2022-07-18-the-5-invitation-becomes-a-chain", + "kind": "prose-handle-form", + "panel": "What happened", + "authority": "WORK-QUEUE.md §Q24 addendum 2026-10-10 18:13Z (Satskeeper resolves as @satskeeper_, already linked on block-7)", + "decided": "undecided-for-Alexander", + "ops": [ + { + "field": "body", + "old": "and Satskeeper with $7", + "new": "and @satskeeper_ with $7" + } + ], + "locator": "\"title\": \"The $5 invitation becomes a chain\"" + }, + { + "id": "H3", + "slug": "magazine-002", + "kind": "prose-handle-form", + "panel": "What happened", + "authority": "WORK-QUEUE.md §Q23 verification correction (rule 3b yields 8 bare-and-unlinked mentions across 6 entries) — CONDITIONAL on step 5’s filter", + "decided": "undecided-for-Alexander (conditional row)", + "ops": [ + { + "field": "body", + "old": "ArizonanHODL's $5", + "new": "@AriZonanHODL's $5" + } + ], + "locator": "\"title\": \"The Art of GFY by Nobody (aka Seven)\"" + }, + { + "id": "H4", + "slug": "magazine-005", + "kind": "prose-handle-form", + "panel": "What happened", + "authority": "WORK-QUEUE.md §Q23 verification correction (rule 3b) — CONDITIONAL on step 5’s filter", + "decided": "undecided-for-Alexander (conditional row)", + "ops": [ + { + "field": "body", + "old": "ArizonanHodl's July 18, 2022 tweet", + "new": "@AriZonanHODL's July 18, 2022 tweet" + } + ], + "locator": "\"title\": \"A Brief History of Stackchain.\"" + }, + { + "id": "I1", + "slug": "magazine-026", + "kind": "contributor-anchor", + "panel": "Contributor", + "authority": "recoveries/evidence-tier-audit.md §6.3 (2026-10-10) — OUTSIDE the Q24 population rule, which names findings 1–2; included and flagged", + "decided": "undecided-for-Alexander", + "ops": [ + { + "field": "contributor.handle", + "old": "null", + "new": "\"a_koby\"" + } + ], + "locator": "\"title\": \"Stackchain for Newcomers: How I Infiltrated the Stackchain.\"" + }, + { + "id": "I2", + "slug": "magazine-070", + "kind": "contributor-anchor", + "panel": "Contributor", + "authority": "WORK-QUEUE.md §Q23 verification correction (rule 3a admits MidyReyes on magazine-070) — OUTSIDE the Q24 population rule; included and flagged", + "decided": "undecided-for-Alexander", + "ops": [ + { + "field": "contributor.display_name", + "old": "\"@MidyReyes\"", + "new": "\"MidyReyes\"" + }, + { + "field": "contributor.handle", + "old": "null", + "new": "\"MidyReyes\"" + } + ], + "locator": "\"title\": \"From Struggles to Strength: My Health & Fitness Story\"" + }, + { + "id": "J1", + "slug": "magazine-024", + "kind": "uncertainty-append", + "panel": "Uncertainty", + "authority": "recoveries/named-source-liveness-unlinked.md finding 3 (2026-10-10T17:49:31Z)", + "decided": "undecided-for-Alexander", + "ops": [ + { + "field": "uncertainty", + "old": "Archive summary of a published article, not the article itself. Quotes are short verbatim excerpts; read the original for full context.", + "new": "Archive summary of a published article, not the article itself. Quotes are short verbatim excerpts; read the original for full context. Note 2026-10-10: the tipping address bitcoinbekka@getalby.com resolves at its canonical LNURL-pay endpoint https://getalby.com/.well-known/lnurlp/bitcoinbekka (HTTP 200); the vanity web path https://getalby.com/bitcoinbekka returns 404. It is not linked here because the ledger build emits anchors only for contributor handles, body sources and excerpt URLs." + } + ], + "locator": "\"title\": \"Bekka’s Sammie Corner: Schnitzel\"" + } +]""") + + +def slug_for(b): + raw = b.get("slug") or f"block-{b['n']}" + return re.sub(r"[^a-z0-9-]+", "-", str(raw).lower()).strip("-") + + +def region_of(text, slug): + """(start, end, region_text, locator) for one entry object, found by its unique title.""" + by_slug = {slug_for(b): b for b in json.loads(text)["blocks"]} + b = by_slug[slug] + loc = '"title": ' + json.dumps(b["title"], ensure_ascii=False) + n = text.count(loc) + if n != 1: + raise SystemExit(f"ABORT {slug}: title locator occurs {n}x") + i = text.index(loc) + m = re.search(r'\{\s*"n":', text[i + len(loc):]) + end = i + len(loc) + (m.start() if m else len(text) - i - len(loc)) + return i, end, text[i:end], loc + + +def _scan_string(text, s): + """End offset of the JSON string that starts at s (text[s] == '"').""" + j = s + 1 + while j < len(text): + if text[j] == "\\": + j += 2 + continue + if text[j] == '"': + return j + 1 + j += 1 + raise SystemExit("ABORT: unterminated string") + + +def _value_span(text, key, base=0): + """(start, end, raw) of one key's JSON value; objects/arrays bracket-matched.""" + m = re.search(r'"' + re.escape(key) + r'": ', text[base:]) + if not m: + raise SystemExit(f"ABORT: key {key!r} not found") + s = base + m.end() + if text.startswith('"', s): + e = _scan_string(text, s) + return s, e, text[s:e] + m2 = re.match(r"(null|true|false|-?\d+(?:\.\d+)?)", text[s:]) + if m2: + return s, s + m2.end(), text[s:s + m2.end()] + depth, j = 0, s + while j < len(text): + ch = text[j] + if ch == '"': + j = _scan_string(text, j) + continue + if ch in "{[": + depth += 1 + elif ch in "}]": + depth -= 1 + if depth == 0: + return s, j + 1, text[s:j + 1] + j += 1 + raise SystemExit(f"ABORT: unbalanced value at {key!r}") + + +def field_span(region, path): + """(start, end, raw) of ONE field's JSON value inside the entry region.""" + if path in ("body", "body_provenance", "uncertainty"): + return _value_span(region, path) + if path == "plain_summary.text": + cs, _, _ = _value_span(region, "plain_summary") + s, e, _ = _value_span(region, "text", base=cs) + return s, e, region[s:e] + if path.startswith("excerpts["): + idx = int(path[path.index("[") + 1:path.index("]")]) + key = path.split(".")[-1] + es, _, _ = _value_span(region, "excerpts") + arr_end = es + 1 + depth = 1 + while depth: + if region[arr_end] == '"': + arr_end = _scan_string(region, arr_end) + continue + if region[arr_end] in "[{": + depth += 1 + elif region[arr_end] in "]}": + depth -= 1 + arr_end += 1 + arr = region[es:arr_end] + pos = [m.start() for m in re.finditer(r'"' + key + r'": ', arr)] + if len(pos) <= idx: + raise SystemExit(f"ABORT: {path} not found ({len(pos)} occurrences)") + s = es + pos[idx] + len('"' + key + '": ') + e = _scan_string(region, s) + return s, e, region[s:e] + if path.startswith("contributor."): + key = path.split(".")[-1] + cs, _, _ = _value_span(region, "contributor") + return _value_span(region, key, base=cs) + raise SystemExit(f"ABORT: unknown field path {path!r}") + + +def plan(cid): + c = next((x for x in CLAUSES if x["id"] == cid), None) + if c is None: + raise SystemExit(f"ABORT: no clause {cid!r}; --list shows the ids") + text = BLOCKS.read_text(encoding="utf-8") + rstart, rend, reg, loc = region_of(text, c["slug"]) + edits = [] + for o in c["ops"]: + s, e, cur = field_span(reg, o["field"]) + if cur.count(o["old"]) != 1: + raise SystemExit( + f"ABORT {cid} {o['field']}: old occurs {cur.count(o['old'])}x in the field span") + if o["old"] == o["new"]: + raise SystemExit(f"ABORT {cid} {o['field']}: old == new") + # absolute offsets into the full file + edits.append((s + rstart, e + rstart, cur, o)) + return c, text, reg, loc, edits + + +def main() -> int: + argv = sys.argv[1:] + if "--list" in argv: + for c in CLAUSES: + print(f"{c['id']:4s} {c['slug'][:52]:52s} {c['kind']:20s} {c['decided'][:40]}") + print(f"\n{len(CLAUSES)} clauses, {sum(len(c['ops']) for c in CLAUSES)} deltas") + return 0 + apply = "--apply" in argv + force = "--force" in argv + decision = None + if "--decision" in argv: + k = argv.index("--decision") + if k + 1 >= len(argv): + print("ABORT: --decision needs a value") + return 2 + decision = argv[k + 1] + argv = argv[:k] + argv[k + 2:] + ids = [a for a in argv if not a.startswith("--")] + if len(ids) != 1: + print("usage: apply_fix_pack.py [--list] [--check|--apply] " + "[--force --decision ]") + return 2 + cid = ids[0] + + c, text, reg, loc, edits = plan(cid) + print(f"clause {cid} entry {c['slug']} panel {c['panel']}") + print(f"authority: {c['authority']}") + print(f"decided: {c['decided']}") + for _s, _e, _cur, o in edits: + print(f" field {o['field']}: old occurs 1x, new differs OK") + + if c["decided"].startswith("undecided"): + if apply and not (force and decision): + print("\nREFUSED: this clause is undecided-for-Alexander. Re-run with " + "--force --decision to publish it anyway.") + return 1 + if apply: + print(f"\nFORCED with decision artifact: {decision}") + + if not apply: + print("\nCHECK ONLY - blocks.json not written (pass --apply)") + return 0 + + out = text + for s, e, cur, o in reversed(edits): + out = out[:s] + cur.replace(o["old"], o["new"], 1) + out[e:] + json.loads(out) + BLOCKS.write_text(out, encoding="utf-8") + print(f"\nWROTE {BLOCKS}") + print("revert by reversing these deltas (or restore a pre-apply backup):") + for _s, _e, _cur, o in edits: + print(f" {o['field']}: {o['new'][:70]!r} -> {o['old'][:70]!r}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())