queue: Q26 hardening DONE (Merlin) - the two silent token classes now die at build time (pre-write sentinel scan, per-entry panel set so the quote branch is caught by construction, render_genesis panels routed through expand_prose), all five Q25 legs re-run green on the hardened prototype (no-token build byte-identical at 91ecf6bf, 125 files, diff -rq vs live tree silent, registry still 8, uncited-handle negative still exit 1, H1-H4 demo still 3 of 125 files), 12 negative fixtures all exit 1 with their own message (7 malformed incl. open brace/empty handle/whitespace/wrong namespace/over-15-chars/lone close/close-then-open, 5 outside-panel incl. quote branch, title, excerpt, display_name, coverage gap) plus a cited-token positive control expanding on the entry page AND the genesis page; contributor.display_name fifth slot measured without building it - G2 and I2 take the handle-only fix, G1 needs display_name as a fifth panel to anchor @AnthonyDessauer beside the single handle slot (a build change, Alexander's); hardened convention carries 4 of Q24's 31 deltas as links (H1-H4); shared build tree untouched at 97fedb5d with site-ledger byte-identical and nothing modified since 2026-10-10 18:00Z, live index 200/34,488 B/4f764ef4dfb9, blocks.json read-only, decisions/ and inbox/ untouched, nothing executed
This commit is contained in:
1 parent
4e63aba0b1
commit
f05179e89e
3 files changed
+465
No files matched your search
@@ -3045,3 +3045,72 @@ with a reason remains legitimate (§3.7). `Q20` remains active and zephyrus-spar
|
|||||||
line 2370 repeats the slip in the other direction, giving `StackchainQuant` 37,702. No verdict impact, nothing
|
line 2370 repeats the slip in the other direction, giving `StackchainQuant` 37,702. No verdict impact, nothing
|
||||||
to re-run, and no fix implied beyond this line.
|
to re-run, and no fix implied beyond this line.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Status: DONE — `recoveries/prose-anchor-hardening.md`, Merlin, 2026-10-10→11 (~00:20–00:24 UTC).
|
||||||
|
The hardened prototype is built and every negative class now dies at build time; nothing
|
||||||
|
published, nothing applied to the shared build tree.**
|
||||||
|
- **The hardening.** A pre-write scan (`check_tokens` per entry, `check_gaps` for the
|
||||||
|
index) sits **above** the `OUT` cleanup in `build()`, so a `die()` can never leave a
|
||||||
|
half-emitted tree. Any `{{`/`}}` sentinel in a field the four prose panels do not render
|
||||||
|
dies; any sentinel left in a panel after `TOKEN_RE.sub` dies. The panel set is computed
|
||||||
|
**per entry**: `body` is a panel only when `body_kind == "summary"`, so the quote branch
|
||||||
|
is caught by construction — the first hardening pass missed exactly that, and the
|
||||||
|
quote-branch error now prints the reduced panel set, so the rule is visible in the
|
||||||
|
failure.
|
||||||
|
- **Diff.** `build_blocks_proto.py` → `build_blocks_hard.py`: **+70 / −6 lines, 5,817 B**
|
||||||
|
(`DIFF.txt`). Against the shared tree's `build_blocks.py` (sha256
|
||||||
|
`97fedb5dd5e85d2b10071e8e4904b3a9b1573072149e722fbece93eaf0774672`): **+118 / −11,
|
||||||
|
9,949 B**. The hardened script is **27,878 B / sha256
|
||||||
|
`d45cc28f7d69afcdab4eccb16e9e0cc264e4317e059e17f1ba53eb23f792cb60`**; the Q25 prototype
|
||||||
|
copied into the scratch tree verified byte-identical first (`5af80b78…`).
|
||||||
|
- **The five Q25 legs, re-run green.** No-token build on the unmodified `blocks.json`
|
||||||
|
(278,459 B / `63c9cee6103a6824…`): `Built 123 pages from 121 entries`, tree digest
|
||||||
|
**`91ecf6bf939a2a499983066a2331bb953eab8a4ca85c66a8a19f42cb1b28f7a8`** = baseline, 125
|
||||||
|
files, `diff -rq` against the live `site-ledger` silent. Registry unchanged at **8**
|
||||||
|
handles. The uncited-handle negative test still exits 1 with Q25's own message
|
||||||
|
(`block-25:uncertainty: … @NotACitedHandle which no entry cites`). The H1–H4 token demo
|
||||||
|
still moves **3 of 125** emitted files with 6 emitted anchors. The diff itself is the
|
||||||
|
fifth leg.
|
||||||
|
- **Two negative test sets, 12 fixtures, every one `exit 1` with its own message**
|
||||||
|
(`MALFORMED.txt`, `OUTSIDE-PANEL.txt`): malformed — open brace `{{x:saylor}`, empty
|
||||||
|
handle `{{x:}}`, whitespace-padded `{{ x:saylor }}`, wrong namespace `{{y:saylor}}`,
|
||||||
|
handle over 15 chars `{{x:abcdefghijklmnopq}}`, lone `}}`, close-then-open `}}{{x:…}}`
|
||||||
|
(all seven previously built and shipped the literal under the Q25 prototype);
|
||||||
|
outside-panel — quote branch (`block-6:body`), `title`, `excerpts[0].text`,
|
||||||
|
`contributor.display_name`, `coverage.gaps[4]`. A cited-token **positive control**
|
||||||
|
expands on the entry page *and* the genesis page and moves exactly 2 files.
|
||||||
|
- **One new silent class found and closed while writing the control:** `render_genesis`
|
||||||
|
routed `body_provenance` and `uncertainty` through `esc()`, so a token those panels
|
||||||
|
carry would expand on `block-5` and print raw braces on `genesis/`. Both now go through
|
||||||
|
`expand_prose`; byte-neutral with no token present (proved by the no-token leg).
|
||||||
|
- **The `display_name` fifth slot, measured without building it**
|
||||||
|
(`DISPLAY-NAME-MEASURE.txt`, each Q24 delta applied in its own scratch tree).
|
||||||
|
**Recommendation: `magazine-056` (G2) and `magazine-070` (I2) take the handle-only fix —
|
||||||
|
one account, one slot, and the build already anchors `contributor.handle`. `magazine-035`
|
||||||
|
(G1) is the hard case: handle-only anchors `@BTC_Freeborn` and leaves `@AnthonyDessauer`
|
||||||
|
unanchored because `contributor` has exactly one `handle` slot while the byline names two
|
||||||
|
accounts — and `@AnthonyDessauer` *is* in the registry, so a token for it would be
|
||||||
|
admitted today. The only instrument that anchors both is `display_name` as a fifth panel
|
||||||
|
carrying `{{x:AnthonyDessauer}}` beside the handle slot: a build change, Alexander's.**
|
||||||
|
All three live-page digests reproduce Q24's fix-pack figures to the nibble
|
||||||
|
(`871638b8abb5…`, `33b55f8f61fe…`, `6227e199522f…`), so the live tree is still the tree
|
||||||
|
Q24 measured.
|
||||||
|
- **Which of Q24's 31 deltas the hardened convention can carry as links: H1–H4 only, 4 of
|
||||||
|
31.** The hardening widens no clause's carrier; it makes the four token carriers safe to
|
||||||
|
publish. The publication precondition the 2026-10-10 18:13Z addendum stated is therefore
|
||||||
|
satisfied on the build side — **Q13/Q24 approval is still permission to review and stage,
|
||||||
|
not to build.**
|
||||||
|
- **Nothing touched.** Shared tree still at `97fedb5dd5e85d2…`, its `site-ledger`
|
||||||
|
byte-identical (125 files, `diff -rq` silent), `find /root/stackchain-legends-build
|
||||||
|
-newermt 2026-10-10 18:00 -type f` empty. Live archive unchanged: index
|
||||||
|
`https://alexanderwhitestone.com/stackchain/blocks/` **200 / 34,488 B / sha256
|
||||||
|
`4f764ef4dfb9c08b2654ff0783550d20ad1bb7a31cb0c32070974a5fe451557e`** at
|
||||||
|
2026-10-11T00:23:25Z. `blocks.json` read-only throughout. `decisions/` and `inbox/`
|
||||||
|
untouched, nothing executed.
|
||||||
|
- **Limits.** 12 fixtures are the item's own lists plus a lone `}}` and close-then-open;
|
||||||
|
they are not a census of typos. `display_name` was measured, not built. Nothing re-tests
|
||||||
|
Q25's census (46 rows / 25 fetches) — cited, not re-run. No X page was fetched this run,
|
||||||
|
so no liveness figure is dated by this tick. Q20 remains the top unclaimed item and is
|
||||||
|
zephyrus-sparkborn's; Q9 blocked on the audio handover; Q13/Q19 gated on Alexander.
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
# 2026-10-11 — merlin
|
||||||
|
|
||||||
|
> 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 **Q26 — make the silent token classes loud, and measure the fifth slot**, the item
|
||||||
|
nominated to me by the control plane after Q25 closed at `a9f23a6` (Q20 is zephyrus-sparkborn's and
|
||||||
|
stays the top unclaimed item; Q9 blocked on the audio handover; Q13/Q19 gated on Alexander).
|
||||||
|
|
||||||
|
Deliverable: `recoveries/prose-anchor-hardening.md` (**15,250 B / sha256
|
||||||
|
`607b29403e61db44f15657386531ef11d009f964b90b1a4a5a25542612325108`**, this host,
|
||||||
|
2026-10-11T00:24:34Z). Queue block appended to `WORK-QUEUE.md` (383,205 B / sha256
|
||||||
|
`9be863624751711afb0b1c745b7cf4147ea9f838b6189ccadc32689b3d4b5d3c`).
|
||||||
|
|
||||||
|
**What Q25 left silent, now loud.** The Q25 prototype shipped a typo as raw braces on the page and
|
||||||
|
could not see a token outside its four panels. The fix is a pre-write scan — `check_tokens` per entry,
|
||||||
|
`check_gaps` for the index — sitting **above** the `OUT` cleanup in `build()`, so a `die()` can never
|
||||||
|
leave a half-emitted tree. The panel set is computed **per entry**: `body` is a panel only when
|
||||||
|
`body_kind == "summary"`.
|
||||||
|
|
||||||
|
The diff: `build_blocks_proto.py` → `build_blocks_hard.py` **+70 / −6 lines, 5,817 B**
|
||||||
|
(`/root/crucible/q26/DIFF.txt`); against the shared tree's `build_blocks.py`
|
||||||
|
(sha256 `97fedb5dd5e85d2b10071e8e4904b3a9b1573072149e722fbece93eaf0774672`) **+118 / −11, 9,949 B**.
|
||||||
|
The hardened script is **27,878 B / sha256
|
||||||
|
`d45cc28f7d69afcdab4eccb16e9e0cc264e4317e059e17f1ba53eb23f792cb60`**; the Q25 prototype copied into
|
||||||
|
the scratch tree verified byte-identical first (`5af80b78961b4f55…`).
|
||||||
|
|
||||||
|
**The five Q25 legs, re-run green** (`Q26-VERIFY.txt`, full log `Q26-VERIFY-run.txt`):
|
||||||
|
|
||||||
|
1. No-token build on the unmodified `blocks.json` (278,459 B / sha256
|
||||||
|
`63c9cee6103a6824c90c71440fa983d2f5592e380f9c3fb40f7ad366ae41723d`):
|
||||||
|
`Built 123 pages from 121 entries (121 cleared, 0 research-only)`, tree digest
|
||||||
|
**`91ecf6bf939a2a499983066a2331bb953eab8a4ca85c66a8a19f42cb1b28f7a8`** = baseline, 125 files,
|
||||||
|
`diff -rq <scratch>/site-ledger /root/stackchain-legends-build/site-ledger` silent.
|
||||||
|
2. Registry unchanged: **8** handles (`@anthonydessauer @arizonanhodl @guiamadogomes
|
||||||
|
@happyclowntime @rockachopa @satskeeper_ @saylor @yoshishisatoshi`).
|
||||||
|
3. Uncited-handle negative test: `exit 1`,
|
||||||
|
`BUILD FAILED: block-25:uncertainty: prose-anchor token names handle @NotACitedHandle which no
|
||||||
|
entry cites`.
|
||||||
|
4. H1–H4 token demo: `exit 0`, **3 of 125** emitted files changed
|
||||||
|
(`event-2022-07-18-the-5-invitation-becomes-a-chain/index.html`, `magazine-002/index.html`,
|
||||||
|
`magazine-005/index.html`), 6 emitted anchors, all the contributor form.
|
||||||
|
5. The diff itself.
|
||||||
|
|
||||||
|
**Two negative sets, 12 fixtures, every one `exit 1` with its own message.** Malformed
|
||||||
|
(`MALFORMED.txt`): `{{x:saylor}` open brace, `{{x:}}` empty handle, `{{ x:saylor }}`
|
||||||
|
whitespace-padded, `{{y:saylor}}` wrong namespace, `{{x:abcdefghijklmnopq}}` handle over 15
|
||||||
|
chars, lone `}}`, close-then-open `}}{{x:saylor}}` — all seven **previously built and shipped the
|
||||||
|
literal** under the Q25 prototype, exit 0. Outside-panel (`OUTSIDE-PANEL.txt`): quote branch
|
||||||
|
(`block-6:body`, whose error prints the panel set *without* `body` — the per-entry `body_kind`
|
||||||
|
rule visible in the failure), `magazine-001:title`, `magazine-001:excerpts[0].text`,
|
||||||
|
`magazine-035:contributor.display_name`, `coverage.gaps[4]`. Positive control (`POSITIVE.txt`):
|
||||||
|
a cited `{{x:AriZonanHODL}}` in `block-5`'s `body_provenance` builds, expands on the `block-5` page
|
||||||
|
**and** the genesis page, and moves exactly 2 files.
|
||||||
|
|
||||||
|
**One new silent class, found while writing the positive control and closed:** `render_genesis`
|
||||||
|
routed `body_provenance` and `uncertainty` through `esc()`, so a token those panels carry would
|
||||||
|
expand on `block-5` and print raw braces on `genesis/`. Both now go through `expand_prose`;
|
||||||
|
byte-neutral with no token present (proved by leg 1). The first hardening pass also missed the
|
||||||
|
quote branch for the same reason a path-based panel set fails — `body` is a panel only on the
|
||||||
|
summary branch — and the fix was the per-entry panel set.
|
||||||
|
|
||||||
|
**The fifth slot, measured without building it** (`DISPLAY-NAME-MEASURE.txt`, each Q24 delta
|
||||||
|
applied in its own scratch tree):
|
||||||
|
|
||||||
|
| entry | Q24 clause | `display_name` / `handle` today | live page today | after the staged delta |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| magazine-035 | G1 | `@BTC_Freeborn and @AnthonyDessauer` / `null` | text only, anchors NONE | `BTC_Freeborn and Anthony Dessauer (@BTC_Freeborn)` — anchor `x.com/BTC_Freeborn`; sha `871638b8…` → `565e6f68…` |
|
||||||
|
| magazine-056 | G2 | `Red Tail Hawk (@RedTailHawk1923)` / `null` | text only, anchors NONE | `Red Tail Hawk (@RedTailHawk1923)` — anchor `x.com/RedTailHawk1923`; sha `33b55f8f…` → `8094e862…` |
|
||||||
|
| magazine-070 | I2 | `@MidyReyes` / `null` | text only, anchors NONE | `MidyReyes (@MidyReyes)` — anchor `x.com/MidyReyes`; sha `6227e199…` → `c994e055…` |
|
||||||
|
|
||||||
|
**Recommendation: `magazine-056` (G2) and `magazine-070` (I2) take the handle-only fix — one
|
||||||
|
account, one `handle` slot, and the build already anchors `contributor.handle` (line 310).
|
||||||
|
`magazine-035` (G1) is the hard case: handle-only anchors `@BTC_Freeborn` and leaves
|
||||||
|
`@AnthonyDessauer` unanchored, because `contributor` has exactly one `handle` slot while the byline
|
||||||
|
names two accounts — and `@AnthonyDessauer` *is* in the registry, so a token for it would be
|
||||||
|
admitted today. The only instrument that anchors both is `display_name` as a fifth panel carrying
|
||||||
|
`{{x:AnthonyDessauer}}` beside the handle slot — a build change, Alexander's.** All three live-page
|
||||||
|
digests reproduce Q24's fix-pack figures to the nibble, so the live tree is still the tree Q24
|
||||||
|
measured.
|
||||||
|
|
||||||
|
**Which of Q24's 31 deltas the hardened convention can carry as links: H1–H4 only, 4 of 31.** The
|
||||||
|
hardening widens no clause's carrier; it makes the four token carriers safe to publish. The
|
||||||
|
publication precondition the 2026-10-10 18:13Z addendum stated is satisfied on the build side.
|
||||||
|
|
||||||
|
**Nothing touched.** Shared tree still at `97fedb5dd5e85d2…`, its `site-ledger` byte-identical to
|
||||||
|
this run's scratch build (125 files each, `diff -rq` silent), and
|
||||||
|
`find /root/stackchain-legends-build -newermt 2026-10-10 18:00 -type f` returns nothing. Live
|
||||||
|
archive unchanged: `https://alexanderwhitestone.com/stackchain/blocks/` **200 / 34,488 B / sha256
|
||||||
|
`4f764ef4dfb9c08b2654ff0783550d20ad1bb7a31cb0c32070974a5fe451557e`** at 2026-10-11T00:23:25Z.
|
||||||
|
`blocks.json` read-only throughout; `decisions/` and `inbox/` untouched, nothing executed, no
|
||||||
|
another-party file edited.
|
||||||
|
|
||||||
|
## Blocked
|
||||||
|
|
||||||
|
Nothing blocked the run. Two things deliberately **unverified**, both recorded as such in the
|
||||||
|
deliverable:
|
||||||
|
|
||||||
|
- **The `display_name` fifth panel was measured, not built.** No fifth-panel implementation exists;
|
||||||
|
the recommendation is a decision input, not a staged change.
|
||||||
|
- **12 fixtures are the item's own lists plus two extras, not a census of typos.** A sentinel in a
|
||||||
|
field no entry has today cannot be enumerated from today's data.
|
||||||
|
|
||||||
|
Also outside this run by construction: Q25's census (46 rows / 25 X fetches) is cited, not re-run —
|
||||||
|
no X page was fetched this tick, so no liveness figure in this check-in is dated by this run.
|
||||||
|
|
||||||
|
## Next
|
||||||
|
|
||||||
|
- **Q20** (excerpt fidelity and placement, the 91 magazine entries) remains the top unclaimed item
|
||||||
|
and is zephyrus-sparkborn's; I take nothing over it.
|
||||||
|
- **Alexander's call, not mine:** whether to publish the build change at all. The hardened
|
||||||
|
prototype is scratch-local and stays there until he says so — the same gate Q13 states. If he
|
||||||
|
approves the convention, the immediate payload is the 4-of-31 H1–H4 clauses; the `display_name`
|
||||||
|
fifth panel is a separate, larger decision with exactly one entry (G1) that needs it today.
|
||||||
|
- **Q7, measured:** Q9 blocked on the audio handover; Q13/Q19 gated on Alexander; Q20 active. The
|
||||||
|
executable set is empty of unclaimed items, so the never-dry obligation is triggered and a
|
||||||
|
successor item is owed — I propose none this tick, because Q26 just closed and the successor
|
||||||
|
should be the pipe's to open on a fresh measurement rather than mine to invent on the same
|
||||||
|
finding. Stated plainly: this is the second consecutive tick where the set fell to zero and the
|
||||||
|
successor came from the control plane's own measurement, and that is the pattern worth naming
|
||||||
|
rather than a gap in the queue.
|
||||||
|
|
||||||
|
## Ask
|
||||||
|
|
||||||
|
1. **Alexander — approve, decline, or defer the prose-anchor convention as a build change.** If
|
||||||
|
approved, the unit of work is the four H1–H4 clauses plus the hardened prototype; nothing
|
||||||
|
publishes without a further go. If declined, Q24's 25 clauses stay staged as notes exactly as
|
||||||
|
they are.
|
||||||
|
2. **Alexander — the `display_name` fifth slot, G1 only.** Is a second anchor on one byline worth a
|
||||||
|
build change, or does G1 take the handle-only fix and leave `@AnthonyDessauer` as a name?
|
||||||
|
3. **Nothing asked of zephyrus-sparkborn this tick.** His lane is Q20; my two asks are both
|
||||||
|
Alexander's, per §4.
|
||||||
@@ -0,0 +1,260 @@
|
|||||||
|
# Q26 — Harden the prose-anchor convention: the two silent token classes die loudly
|
||||||
|
|
||||||
|
**Status: DONE — hardened prototype built, all five Q25 legs re-run green, two negative
|
||||||
|
test sets (7 malformed tokens, 5 outside-panel sentinels) each dying with its own
|
||||||
|
message, the `contributor.display_name` fifth slot measured without building it.**
|
||||||
|
Merlin, 2026-10-11 ~00:20–00:24 UTC. Scratch tree `/root/crucible/q26/`, every artifact
|
||||||
|
re-runnable from the scripts named in §9. **Nothing published, nothing applied to the
|
||||||
|
shared build tree; `blocks.json` was read-only throughout.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0. Reading rule (unchanged from Q25, restated so the population is re-derivable)
|
||||||
|
|
||||||
|
- **Prose panels** = the fields `render_entry` emits as the archive's own prose and routes
|
||||||
|
through `expand_prose`: `body` **where `body_kind == "summary"`**, `plain_summary.text`,
|
||||||
|
`body_provenance`, `uncertainty`. The `body_kind == "quote"` branch renders `body`
|
||||||
|
through `esc()` inside a blockquote — third-party quoted text, **not a panel**.
|
||||||
|
- **Token sentinel** = any occurrence of the literal `{{` or `}}` anywhere in an entry.
|
||||||
|
- **Malformed token** = a `{{x:...}}` in a panel that `TOKEN_RE`
|
||||||
|
(`\{\{x:([A-Za-z0-9_]{1,15})\}\}`) does not match.
|
||||||
|
- **Cited handle (validator registry)** = every `contributor.handle` value plus the first
|
||||||
|
path segment of any `https://x.com/…` or `https://twitter.com/…` URL anywhere in
|
||||||
|
`blocks.json`, minus reserved account paths.
|
||||||
|
|
||||||
|
## 1. What was hardened, and why
|
||||||
|
|
||||||
|
Q25 left two classes failing silently, both reproduced in the control plane's 2026-10-10
|
||||||
|
verification of Q25:
|
||||||
|
|
||||||
|
1. **A malformed token passes the build and prints raw braces.** `expand_prose` returns
|
||||||
|
`esc(text)` unless the text contains the literal `{{x:`, and the validator only ever
|
||||||
|
inspects what `TOKEN_RE` matched. So `{{x:saylor}`, `{{x:}}`, `{{ x:saylor }}`,
|
||||||
|
`{{y:saylor}}` and a 16+ character handle all emitted `Built 123 pages from 121
|
||||||
|
entries`, exit 0, with the literal on the page.
|
||||||
|
2. **A token outside the four panels prints literally** — the quote branch (Q25's own
|
||||||
|
limit 3), a `title`, an `excerpts[].text`, a `contributor` field, an index
|
||||||
|
`coverage.gaps` string. Q25's validator could not see any of these.
|
||||||
|
|
||||||
|
The fix is a **pre-write scan**, `check_tokens(b, slug)` plus `check_gaps(data)`, run for
|
||||||
|
every entry **before a single byte is written** (the scan sits above the `OUT` cleanup in
|
||||||
|
`build()`, so a `die()` can never leave a half-emitted tree — the Q25 prototype's scan
|
||||||
|
point would have):
|
||||||
|
|
||||||
|
- any `{{` or `}}` in a field the panels do not render → `die`;
|
||||||
|
- any sentinel left in a panel after `TOKEN_RE.sub("", obj)` → `die`;
|
||||||
|
- the panel set is computed **per entry**: `body` is a panel only when
|
||||||
|
`body_kind == "summary"`, so the quote branch is caught by construction rather than by
|
||||||
|
a special case.
|
||||||
|
|
||||||
|
Two further changes ride along, both byte-neutral with no token present:
|
||||||
|
|
||||||
|
- `render_genesis` now routes `body_provenance` and `uncertainty` through `expand_prose`
|
||||||
|
instead of `esc()`. The genesis page previously could not expand a token those panels
|
||||||
|
carried even though `block-5`'s own page did — a third silent class, found while
|
||||||
|
writing the positive control, not in Q25's list.
|
||||||
|
- `coverage.gaps` strings are checked (the index renders them through `esc()`).
|
||||||
|
|
||||||
|
## 2. The diff
|
||||||
|
|
||||||
|
`/root/crucible/q26/DIFF.txt` — `diff -u build_blocks_proto.py build_blocks_hard.py`:
|
||||||
|
**+70 / −6 lines, 5,817 B.** `DIFF-PRISTINE.txt` — against the shared tree's
|
||||||
|
`build_blocks.py` (sha256 `97fedb5dd5e85d2b10071e8e4904b3a9b1573072149e722fbece93eaf0774672`):
|
||||||
|
**+118 / −11 lines, 9,949 B.**
|
||||||
|
|
||||||
|
The hardened prototype is `build_blocks_hard.py`, **27,878 B / sha256
|
||||||
|
`d45cc28f7d69afcdab4eccb16e9e0cc264e4317e059e17f1ba53eb23f792cb60`** (this host,
|
||||||
|
2026-10-11T00:22Z). The Q25 prototype, copied unchanged into this tree, verified
|
||||||
|
byte-identical first: sha256 `5af80b78961b4f55ca1ac436c19074d275b9a255569de07156a527de39d8de35`.
|
||||||
|
|
||||||
|
## 3. The five Q25 legs, re-run on the hardened prototype (`Q26-VERIFY.txt`)
|
||||||
|
|
||||||
|
**Leg 1 — no-token build is byte-identical.** Unmodified `blocks.json` (278,459 B /
|
||||||
|
sha256 `63c9cee6103a6824c90c71440fa983d2f5592e380f9c3fb40f7ad366ae41723d`, unchanged from
|
||||||
|
Q25's record):
|
||||||
|
|
||||||
|
```
|
||||||
|
$ python3 build_blocks_hard.py
|
||||||
|
Built 123 pages from 121 entries (121 cleared, 0 research-only)
|
||||||
|
tree digest: 91ecf6bf939a2a499983066a2331bb953eab8a4ca85c66a8a19f42cb1b28f7a8
|
||||||
|
baseline : 91ecf6bf939a2a499983066a2331bb953eab8a4ca85c66a8a19f42cb1b28f7a8
|
||||||
|
125 files; BYTE-IDENTICAL to baseline
|
||||||
|
$ diff -rq <scratch>/site-ledger /root/stackchain-legends-build/site-ledger
|
||||||
|
(no output — live tree identical)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Leg 2 — the registry is unchanged: 8 handles.**
|
||||||
|
`@anthonydessauer @arizonanhodl @guiamadogomes @happyclowntime @rockachopa @satskeeper_
|
||||||
|
@saylor @yoshishisatoshi` (`REGISTRY.txt`, identical to Q25's).
|
||||||
|
|
||||||
|
**Leg 3 — the uncited-handle negative test still fails closed**, same message:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ cd negtest && python3 build_blocks_hard.py # {{x:NotACitedHandle}} in uncertainty
|
||||||
|
exit 1
|
||||||
|
BUILD FAILED: block-25:uncertainty: prose-anchor token names handle @NotACitedHandle which no entry cites
|
||||||
|
```
|
||||||
|
|
||||||
|
**Leg 4 — the token demo is unchanged: 6 deltas (H1–H4 as tokens) → 3 of 125 emitted
|
||||||
|
files.**
|
||||||
|
|
||||||
|
```
|
||||||
|
$ cd tokentest && python3 build_blocks_hard.py
|
||||||
|
exit 0 Built 123 pages from 121 entries (121 cleared, 0 research-only)
|
||||||
|
emitted files: 125 -> 125; changed: 3
|
||||||
|
event-2022-07-18-the-5-invitation-becomes-a-chain/index.html
|
||||||
|
magazine-002/index.html
|
||||||
|
magazine-005/index.html
|
||||||
|
emitted: event-2022-07-18… https://x.com/AriZonanHODL @AriZonanHODL (x2: body + plain English)
|
||||||
|
emitted: event-2022-07-18… https://x.com/satskeeper_ @satskeeper_ (x2)
|
||||||
|
emitted: magazine-002 https://x.com/AriZonanHODL @AriZonanHODL
|
||||||
|
emitted: magazine-005 https://x.com/AriZonanHODL @AriZonanHODL
|
||||||
|
```
|
||||||
|
|
||||||
|
**Leg 5 — the diff itself** (§2). All five legs: **green**.
|
||||||
|
|
||||||
|
## 4. The malformed-token set — 7 fixtures, each in its own tree, each `exit 1`
|
||||||
|
|
||||||
|
Each fixture appends one token to `block-25`'s `uncertainty` (a panel rendered on every
|
||||||
|
entry). Raw stderr in `MALFORMED.txt`:
|
||||||
|
|
||||||
|
| fixture | token | exit | first stderr line |
|
||||||
|
|---|---|---|---|
|
||||||
|
| open brace | `{{x:saylor}` | 1 | `BUILD FAILED: block-25:uncertainty: malformed prose-anchor token — sentinel {{ left after token expansion; tokens must read exactly {{x:HANDLE}} with a 1-15 character handle` |
|
||||||
|
| empty handle | `{{x:}}` | 1 | same, sentinel `{{` |
|
||||||
|
| whitespace padded | `{{ x:saylor }}` | 1 | same, sentinel `{{` |
|
||||||
|
| wrong namespace | `{{y:saylor}}` | 1 | same, sentinel `{{` |
|
||||||
|
| handle over 15 chars | `{{x:abcdefghijklmnopq}}` | 1 | same, sentinel `{{` |
|
||||||
|
| lone close brace | `}}` | 1 | same, sentinel `}}` |
|
||||||
|
| close then open | `}}{{x:saylor}}` | 1 | same, sentinel `}}` |
|
||||||
|
|
||||||
|
All seven now die. Under the Q25 prototype all seven built and shipped the literal.
|
||||||
|
|
||||||
|
## 5. The outside-panel sentinel set — 5 fixtures, each `exit 1`
|
||||||
|
|
||||||
|
Each places a **valid** `{{x:saylor}}` where no panel renders it (`OUTSIDE-PANEL.txt`):
|
||||||
|
|
||||||
|
| fixture | field | exit | message |
|
||||||
|
|---|---|---|---|
|
||||||
|
| quote branch | `block-6:body` (`body_kind: quote`) | 1 | `token sentinel {{ in a field that is not a prose panel (panels: body_provenance, plain_summary.text, uncertainty)` |
|
||||||
|
| title | `magazine-001:title` | 1 | `… (panels: body, body_provenance, plain_summary.text, uncertainty)` |
|
||||||
|
| excerpt text | `magazine-001:excerpts[0].text` | 1 | same |
|
||||||
|
| contributor display_name | `magazine-035:contributor.display_name` | 1 | same |
|
||||||
|
| index coverage gap | `coverage.gaps[4]` | 1 | same |
|
||||||
|
|
||||||
|
Note the quote-branch message prints the panel set **without `body`** — the per-entry
|
||||||
|
`body_kind` rule, visible in the error itself. This fixture is the one Q25's limit 3
|
||||||
|
named; it was the only silent class that survived the first pass of the hardening and
|
||||||
|
the reason the panel set is computed per entry.
|
||||||
|
|
||||||
|
## 6. Positive control — a cited token expands on the entry page *and* the genesis page
|
||||||
|
|
||||||
|
`{{x:AriZonanHODL}}` appended to `block-5`'s `body_provenance` (`positive/`):
|
||||||
|
|
||||||
|
```
|
||||||
|
$ cd positive && python3 build_blocks_hard.py
|
||||||
|
exit 0 Built 123 pages from 121 entries (121 cleared, 0 research-only)
|
||||||
|
block-5 page anchors: https://x.com/AriZonanHODL -> @AriZonanHODL (x2: panel + contributor slot)
|
||||||
|
genesis page anchors: https://x.com/AriZonanHODL -> @AriZonanHODL
|
||||||
|
changed files: 2 [block-5/index.html, genesis/index.html]
|
||||||
|
```
|
||||||
|
|
||||||
|
The genesis-page anchor is the §1 wiring change proven: without it the same token would
|
||||||
|
have expanded on `block-5` and printed raw braces on `genesis/`. A negative test with no
|
||||||
|
positive control proves nothing, so both are recorded.
|
||||||
|
|
||||||
|
## 7. The `contributor.display_name` fifth slot, measured without building it
|
||||||
|
|
||||||
|
Measured from `blocks.json` and from this run's scratch build (byte-identical to the live
|
||||||
|
`site-ledger`), per entry, with Q24's staged delta applied in its own scratch tree
|
||||||
|
(`DISPLAY-NAME-MEASURE.txt`):
|
||||||
|
|
||||||
|
| entry | Q24 clause | `display_name` today | `handle` today | live page today | after Q24's delta | named accounts in registry |
|
||||||
|
|---|---|---|---|---|---|---|
|
||||||
|
| magazine-035 | G1 | `@BTC_Freeborn and @AnthonyDessauer` | `null` | `@BTC_Freeborn and @AnthonyDessauer` — anchors: NONE | `BTC_Freeborn and Anthony Dessauer (@BTC_Freeborn)` — anchor `x.com/BTC_Freeborn`; sha256 `871638b8…` → `565e6f68…` | `@BTC_Freeborn` no; `@AnthonyDessauer` **yes** |
|
||||||
|
| magazine-056 | G2 | `Red Tail Hawk (@RedTailHawk1923)` | `null` | text only — anchors: NONE | `Red Tail Hawk (@RedTailHawk1923)` — anchor `x.com/RedTailHawk1923`; sha256 `33b55f8f…` → `8094e862…` | `@RedTailHawk1923` no |
|
||||||
|
| magazine-070 | I2 | `@MidyReyes` | `null` | text only — anchors: NONE | `MidyReyes (@MidyReyes)` — anchor `x.com/MidyReyes`; sha256 `6227e199…` → `c994e055…` | `@MidyReyes` no |
|
||||||
|
|
||||||
|
All three live-page digests reproduce Q24's fix-pack figures to the nibble
|
||||||
|
(`871638b8abb5…`, `33b55f8f61fe…`, `6227e199522f…`), so the live tree is still the tree
|
||||||
|
Q24 measured. **Recommendation, per entry:**
|
||||||
|
|
||||||
|
- **magazine-056 (G2), magazine-070 (I2): the handle-only fix is the right instrument
|
||||||
|
and it is complete.** One account named, one `handle` slot; the build already anchors
|
||||||
|
`contributor.handle` (line 310), and the delta is what admits the handle into the
|
||||||
|
registry. The convention cannot help — `display_name` is not a panel, and §5 proves a
|
||||||
|
token there now dies at build time.
|
||||||
|
- **magazine-035 (G1): handle-only anchors `@BTC_Freeborn` and leaves
|
||||||
|
`@AnthonyDessauer` unanchored**, because `contributor` has exactly one `handle` slot
|
||||||
|
while the byline names two accounts — and `@AnthonyDessauer` *is* in the registry, so
|
||||||
|
a token for it would be admitted today. A wording-only fix (strip both `@`s, as
|
||||||
|
Q24 stages) loses the link on both. **The only instrument that anchors both is
|
||||||
|
`display_name` as a fifth panel** carrying `{{x:AnthonyDessauer}}` beside the handle
|
||||||
|
slot — a build change, which is Alexander's, not a wizard's.
|
||||||
|
|
||||||
|
**Which of Q24's deltas the hardened convention can carry as links: H1–H4 only — 4 of
|
||||||
|
the 31.** The hardening changes no clause's carrier; it makes the four token carriers
|
||||||
|
safe to publish. Everything else is a structured field the build already anchors, a note
|
||||||
|
where the handle is uncited or dead, or a wording call.
|
||||||
|
|
||||||
|
## 8. Findings
|
||||||
|
|
||||||
|
1. **The first hardening pass missed the quote branch, and the error message now shows
|
||||||
|
why it had to.** A path-based panel set ("`body` is a panel") admits a token in a
|
||||||
|
quote-kind entry's `body`, where `render_entry` renders it through `esc()`. The panel
|
||||||
|
set is per entry and depends on `body_kind`; the quote-branch error prints the
|
||||||
|
reduced set, so the rule is visible in the failure.
|
||||||
|
2. **The genesis page was a third silent class, not in Q25's list.** `render_genesis`
|
||||||
|
routed `body_provenance` and `uncertainty` through `esc()`, so a token those panels
|
||||||
|
carry would expand on the entry's own page and print literally on `genesis/`.
|
||||||
|
Routing them through `expand_prose` is byte-neutral with no token present (proved by
|
||||||
|
§3 leg 1) and closes the class.
|
||||||
|
3. **The scan must run before the output tree is touched.** A `die()` inside the write
|
||||||
|
loop leaves a half-emitted `site-ledger` behind, which on a shared tree is exactly
|
||||||
|
the state a reader cannot distinguish from a partial publish. The checks now sit
|
||||||
|
above the `OUT` cleanup.
|
||||||
|
4. **A negative test without a positive control proves nothing** — Q25's own incident
|
||||||
|
note. Leg 6 carries both: a cited token builds, expands, and moves exactly the two
|
||||||
|
files it should.
|
||||||
|
5. **The registry is the binding constraint, unchanged.** 8 handles of the 23 the census
|
||||||
|
found printed in prose; the hardening widens nothing, it only makes the four
|
||||||
|
carriers safe.
|
||||||
|
6. **`{{`/`}}` still collide with nothing in `blocks.json`** (0 occurrences), so the
|
||||||
|
sentinel scan is silent on today's data — the no-token build proves it.
|
||||||
|
|
||||||
|
## 9. Limits
|
||||||
|
|
||||||
|
1. **Prototype only.** `build_blocks_hard.py` lives at `/root/crucible/q26/` and was
|
||||||
|
never copied into the shared build tree. Verified at close: the shared tree's
|
||||||
|
`build_blocks.py` is still sha256
|
||||||
|
`97fedb5dd5e85d2b10071e8e4904b3a9b1573072149e722fbece93eaf0774672`, its
|
||||||
|
`site-ledger` byte-identical to this run's scratch build (125 files each,
|
||||||
|
`diff -rq` silent), and `find /root/stackchain-legends-build -newermt 2026-10-10
|
||||||
|
18:00 -type f` returns nothing. The live archive is unchanged: index
|
||||||
|
`https://alexanderwhitestone.com/stackchain/blocks/` **200 / 34,488 B / sha256
|
||||||
|
`4f764ef4dfb9c08b2654ff0783550d20ad1bb7a31cb0c32070974a5fe451557e`** at
|
||||||
|
2026-10-11T00:23:25Z. Publishing a build change is Alexander's, as Q13 states.
|
||||||
|
2. **7 + 5 fixtures are the test set, not a census of typos.** The malformed set is the
|
||||||
|
item's own list plus a lone `}}` and a close-then-open; the outside-panel set is one
|
||||||
|
fixture per field class. A sentinel in a field no entry has today cannot be
|
||||||
|
enumerated from today's data.
|
||||||
|
3. **`display_name` was measured, not built.** No fifth panel was implemented; the
|
||||||
|
recommendation is a decision input, not a staged change.
|
||||||
|
4. **Nothing re-tests Q25's census.** The 46-row population, the 25 X fetches and their
|
||||||
|
liveness verdicts are Q25's, cited not re-run.
|
||||||
|
5. **X byte sizes are dated readings** (per-request CSP nonce); none is load-bearing
|
||||||
|
here — no X page was fetched this run.
|
||||||
|
|
||||||
|
## 10. Scripts (all re-runnable, `/root/crucible/q26/`)
|
||||||
|
|
||||||
|
`verify26.py` (all eight legs) · `display_name.py` (the fifth-slot measurement) ·
|
||||||
|
`clean.py` (scratch reset) · `build_blocks_hard.py` (the hardened prototype) ·
|
||||||
|
`build_blocks_proto.py`, `build_blocks.py`, `blocks.json` (inputs, unmodified).
|
||||||
|
|
||||||
|
Artifacts: `DIFF.txt` (+70/−6 vs the Q25 prototype) · `DIFF-PRISTINE.txt` (+118/−11 vs
|
||||||
|
the shared build script) · `NOTOKEN-BUILD.txt` (digest `91ecf6bf…`, 125 files, live-tree
|
||||||
|
diff silent) · `REGISTRY.txt` (8) · `NEGTEST.txt` (exit 1) · `TOKEN-DIFF.txt`,
|
||||||
|
`TOKEN-PAGES.txt` (3 of 125 files, 6 emitted anchors) · `MALFORMED.txt` (7 fixtures) ·
|
||||||
|
`OUTSIDE-PANEL.txt` (5 fixtures) · `POSITIVE.txt` (2 changed files) ·
|
||||||
|
`DISPLAY-NAME-MEASURE.txt` · `Q26-VERIFY.txt` / `Q26-VERIFY-run.txt` (full run log) ·
|
||||||
|
scratch trees `negtest/`, `tokentest/`, `positive/`, `mal-*/`, `out-*/`, `dn-*/`.
|
||||||
Reference in new issue
Block a user