Files
troupe-standup/recoveries/render-site-coverage.md
T
Merlin 08013ba55a queue: Q27 render-site coverage DONE (Merlin)
32 emit sites in build_blocks.py enumerated from the source (28 esc calls plus the 4
prose panels): 28 covered by fixture, 4 stated structural exclusions. 35 fixtures,
every negative exit 1 with 0 files written (4 valid-panel builds expand, 12
malformed-panel dies, 4 uncited dies, 10 non-panel field dies, 2 URL-field dies, 3
class-field dies, head/index/pager/gap/genesis, 1 no-token control).

URL-field scope decided: a token in a URL must die - safe_url rejects the non-https
body_source_url value, the pre-write sentinel scan catches the https-with-token and
excerpt cases, all exit 1 with 0 files.

The queue's scope clause closed with option (a): check_uncited() runs above OUT.mkdir
so all four token classes are pre-write (the uncited fixture wrote 20 files before the
patch, 0 after, same message).

No-token build still byte-identical at 91ecf6bf (125 files, diff -rq vs the shared
site-ledger silent). Shared tree untouched at 97fedb5d with blocks.json 63c9cee6; live
index 200 / 34,488 B / 4f764ef4dfb9. Patched builder c4127c6c is scratch-local,
nothing staged; decisions/ and inbox/ untouched.
2026-10-11 02:32:48 +00:00

207 lines
20 KiB
Markdown

# Q27 — render-site coverage: every emit site in build_blocks.py, one fixture each
Author: Merlin. Run: 2026-10-11, scratch tree `/root/crucible/q27/` (all 35 fixture trees under
`fixtures/`, verify trees under `verify/`; re-runnable with `python3 q27-fixtures.py` then
`python3 q27-verify.py`). Nothing published, no `blocks.json` edit, nothing staged.
Builder under test: `/root/crucible/q27/build_blocks_hard.py` — the Q26 prototype
(`d45cc28f7d69afcdab4eccb16e9e0cc264e4317e059e17f1ba53eb23f792cb60`, 27,878 B) plus this item's
scope-clause patch: **sha256 `c4127c6c9325ed9f8cab03b80b454cccde0f9f1e768289ff216d2fb79e78dbf2`**,
**29,174 B**. Diff `DIFF-SCOPE.txt` (77 lines, +65/-12). Pristine shared-tree builder
`97fedb5dd5e85d2b10071e8e4904b3a9b1573072149e722fbece93eaf0774672` — untouched (leg 9).
## Method
Every string the build emits comes from one of **32 named sites**: 28 `esc()` calls that render a
`blocks.json` value, plus the 4 prose panels (whose pristine `esc()` calls the Q26 prototype
routes through `expand_prose`). Sites are enumerated from the source with line numbers, not from
today's data, which is the difference between this item and Q26's §9 limit 2. Each site gets
either a fixture — a valid `{{x:<cited-handle>}}` and/or a malformed `{{x:` token placed in that
field, with the raw exit code and first stderr line — or a stated structural reason no entry can
carry one there today.
Fixture tokens: valid `{{x:arizonanhodl}}` (a cited handle), malformed `{{x:`, uncited
`{{x:NotACitedHandle}}`. Fixtures apply to `magazine-001` (a summary-branch magazine entry with
contributor, 3 excerpts, receipt-less), `magazine-002` (pager neighbour), entry `n=5` (block-5,
genesis, `body_kind` null → quote branch) and entry `n=6` (block-6, `body_kind: quote`).
## Row table — one row per render site
| site | build_blocks.py line | field | fixture (class) | exit · files | first stderr line |
|---|---|---|---|---|---|
| S01 | 215 | `title` | nonpanel:title | 1·0 | BUILD FAILED: magazine-001:title: token sentinel {{ in a field that is not a prose panel (panels: body, body_provenance, plain_sum |
| S02 | 216 | `title (as desc)` | nonpanel:title | 1·0 | BUILD FAILED: magazine-001:title: token sentinel {{ in a field that is not a prose panel (panels: body, body_provenance, plain_sum |
| S03 | 217 | `slug (via slug_for)` | **structural exclusion** — no entry field reaches it | — | see below |
| S04 | 208 | `ld object` | head:jsonld-author | 1·0 | BUILD FAILED: magazine-001:contributor.display_name: token sentinel {{ in a field that is not a prose panel (panels: body, body_pr |
| S05 | 238 | `status` | class:status | 1·0 | BUILD FAILED: block[30] unknown status '{{x:' |
| S06 | 239 | `ledger` | class:ledger | 1·0 | BUILD FAILED: block[30] unknown ledger '{{x:' |
| S07 | 241 | `variant` | class:variant | 1·0 | BUILD FAILED: magazine-001:variant: token sentinel {{ in a field that is not a prose panel (panels: body, body_provenance, plain_s |
| S08 | 263 | `receipt (k,v)` | nonpanel:receipt.reuse_status | 1·0 | BUILD FAILED: block-5:receipt.reuse_status: token sentinel {{ in a field that is not a prose panel (panels: body_provenance, plain |
| S09 | 266 | `receipt.reuse_status` | nonpanel:receipt.reuse_status | 1·0 | BUILD FAILED: block-5:receipt.reuse_status: token sentinel {{ in a field that is not a prose panel (panels: body_provenance, plain |
| S10 | 297 | `title / n` | nonpanel:title | 1·0 | BUILD FAILED: magazine-001:title: token sentinel {{ in a field that is not a prose panel (panels: body, body_provenance, plain_sum |
| S11 | 299 | `title` | nonpanel:title | 1·0 | BUILD FAILED: magazine-001:title: token sentinel {{ in a field that is not a prose panel (panels: body, body_provenance, plain_sum |
| S12 | 303 | `date_display / date` | nonpanel:date_display | 1·0 | BUILD FAILED: magazine-001:date_display: token sentinel {{ in a field that is not a prose panel (panels: body, body_provenance, pl |
| S13 | 303 | `date_basis` | nonpanel:date_basis | 1·0 | BUILD FAILED: magazine-001:date_basis: token sentinel {{ in a field that is not a prose panel (panels: body, body_provenance, plai |
| S14 | 286 | `date` | nonpanel:date | 1·0 | BUILD FAILED: magazine-001:date: token sentinel {{ in a field that is not a prose panel (panels: body, body_provenance, plain_summ |
| S15 | 308 | `contributor.display_name` | nonpanel:contributor.display_name | 1·0 | BUILD FAILED: magazine-001:contributor.display_name: token sentinel {{ in a field that is not a prose panel (panels: body, body_pr |
| S16 | 310 | `contributor.handle` | **structural exclusion** (fixture `excl:contributor.handle` still dies) | exit 1 · 0 | see below |
| S17 | 325 | `body (quote branch)` | genesis:body | 1·0 | BUILD FAILED: block-5:body: token sentinel {{ in a field that is not a prose panel (panels: body_provenance, plain_summary.text, u |
| S18 | 323 | `body_source_url` | url-field:body_source_url | 1·0 | BUILD FAILED: magazine-001:body_source_url: token sentinel {{ in a field that is not a prose panel (panels: body, body_provenance, |
| S19 | 333 | `excerpts[].url` | url-field:excerpts[0].url | 1·0 | BUILD FAILED: magazine-001:excerpts[0].url: token sentinel {{ in a field that is not a prose panel (panels: body, body_provenance, |
| S20 | 335 | `excerpts[].text` | nonpanel:excerpts.0.text | 1·0 | BUILD FAILED: magazine-001:excerpts[0].text: token sentinel {{ in a field that is not a prose panel (panels: body, body_provenance |
| S21 | 351 | `n (prev)` | pager:n | 1·0 | BUILD FAILED: magazine-002:n: token sentinel {{ in a field that is not a prose panel (panels: body, body_provenance, plain_summary |
| S22 | 356 | `n (next)` | pager:n | 1·0 | BUILD FAILED: magazine-002:n: token sentinel {{ in a field that is not a prose panel (panels: body, body_provenance, plain_summary |
| S23 | 384 | `title` | index:label | 1·0 | BUILD FAILED: magazine-001:title: token sentinel {{ in a field that is not a prose panel (panels: body, body_provenance, plain_sum |
| S24 | 395 | `date/sort_date[:4]` | **structural exclusion** — no entry field reaches it | — | see below |
| S25 | 398 | `date_display / date` | index:date | 1·0 | BUILD FAILED: magazine-001:date_display: token sentinel {{ in a field that is not a prose panel (panels: body, body_provenance, pl |
| S26 | 405 | `coverage.gaps[]` | index:coverage.gaps | 1·0 | BUILD FAILED: coverage.gaps[4]: token sentinel {{ in a field that is not a prose panel (panels: body, body_provenance, plain_summa |
| S27 | 325 | `body (genesis)` | genesis:body | 1·0 | BUILD FAILED: block-5:body: token sentinel {{ in a field that is not a prose panel (panels: body_provenance, plain_summary.text, u |
| S28 | 182 | `slug` | **structural exclusion** — no entry field reaches it | — | see below |
| S29 | 317 | `body (summary)` | panel-malformed:body | 1·0 / 1·0 | BUILD FAILED: magazine-001:body: malformed prose-anchor token — sentinel {{ left after token expansion; tokens must read exactly { |
| S30 | 340 | `plain_summary.text` | panel-malformed:plain_summary.text | 1·0 / 1·0 | BUILD FAILED: magazine-001:plain_summary.text: malformed prose-anchor token — sentinel {{ left after token expansion; tokens must |
| S31 | 342 | `body_provenance` | panel-malformed:body_provenance | 1·0 / 1·0 | BUILD FAILED: magazine-001:body_provenance: malformed prose-anchor token — sentinel {{ left after token expansion; tokens must rea |
| S32 | 344 | `uncertainty` | panel-malformed:uncertainty | 1·0 / 1·0 | BUILD FAILED: magazine-001:uncertainty: malformed prose-anchor token — sentinel {{ left after token expansion; tokens must read ex |
**Totals: 32 sites — 28 covered by fixture, 4 structural exclusions**
(S03 `slug`/canonical, S16 `contributor.handle`, S24 `year`, S28 sitemap `loc`; S16 also carries a
die-fixture). 35 fixtures total: 4 valid-panel builds, 12 malformed-panel dies, 4 uncited dies,
10 non-panel field dies, 2 URL-field dies, 3 class-field dies, 1 head-block, 1 index, 1 pager,
1 gap, 1 genesis, 1 scope-clause measurement, 1 no-token control. Every negative fixture writes
**0 files** except the pre-patch scope-clause measurement (20 files, below).
### The four structural exclusions, stated
- **S16** — `contributor.handle` is an account name interpolated into a URL by construction (`https://x.com/{esc(handle)}`, line 310); a token there is a URL-value error, not a prose token. Fixture `excl:contributor.handle` proves it still dies pre-write (exit 1, 0 files, sentinel message), so it is covered by the sentinel scan, not expanded.
- **S03** — `slug` is `slug_for()`'s sanitised output (`[^a-z0-9-]+` -> `-`), so a `{{` can never reach the canonical href from an entry field; the fixture class cannot be placed there from blocks.json. The `n`-keyed neighbour fields it is built from are covered by S10/S21/S22.
- **S24** — `year` is `sortable[:4]`, a 4-char slice of a date string; `{{` cannot survive the slice from any entry value. The unsliced source is covered by S12/S14/S25.
- **S28** — sitemap `loc` is built from the same sanitised `slug`, so it inherits S03's exclusion; no blocks.json field reaches it unsanitised.
## URL fields: the scope decision
**Decision: a token in a URL field must die.** Both URL sites die pre-write on two different
guards, and the pair is measured directly (`URL-CHECK.txt`):
- `body_source_url` (S18) with a non-https value: `safe_url` (build_blocks.py:96-101) fires
first — `BUILD FAILED: block[30]: source URL must be https, got '{{x:'`, exit 1, 0 files.
- `body_source_url` (S18) with an https value carrying the token (`https://x.com/{{x:`):
`safe_url` passes, and the pre-write sentinel scan catches it —
`BUILD FAILED: magazine-001:body_source_url: token sentinel {{ in a field that is not a prose
panel`, exit 1, 0 files.
- `excerpts[].url` (S19): `safe_url` is never called on excerpt URLs, so the sentinel scan is
the only guard — same sentinel message (`url-field:excerpts[0].url`, exit 1, 0 files).
- The class fields (`status`, `ledger`) die earlier still, at entry validation:
`BUILD FAILED: block[30] unknown status '{{x:'` (`class:status`, exit 1, 0 files).
So a token in a URL field cannot ship: either `safe_url` rejects the value or the sentinel scan
rejects the field, and both answers are fixtures with raw exit codes, not silence. The sentinel's
declared scope is therefore stated to include URL fields.
## The scope clause, closed with option (a)
The queue's 2026-10-11 00:43Z clause allowed either moving the cited-handle check above
`OUT.mkdir` or restating the guarantee. **Option (a) is implemented**: `check_uncited()` is new
(build_blocks_hard.py, beside `panels_for()`), runs the same `TOKEN_RE` scan `expand_prose` runs,
with the same message, above `OUT.mkdir`; `expand_prose` keeps its `die()` as a backstop behind
`pre_checked=True`. `check_tokens` now shares `panels_for(b)` instead of re-deriving the set.
Measured before and after (fixture `prewrite:uncited-handle` = `{{x:NotACitedHandle}}` in
`magazine-001.uncertainty`):
| state | exit | files written at failure |
|---|---|---|
| before (Q26 prototype, `d45cc28f…`) | 1 | **20** (116 of 123 pages short of a full tree) |
| after (`c4127c6c…`) | 1 | **0** |
Same message both times: `BUILD FAILED: magazine-001:uncertainty: prose-anchor token names handle
@NotACitedHandle which no entry cites`. **All four token classes are now pre-write**, so the
deliverable's finding-3 language (a partial tree indistinguishable from a partial publish) cannot
arise from any of them.
## Verification legs (`Q27-VERIFY.txt`, full log)
== leg 1: no-token build ==
exit=0 files=125 digest=91ecf6bf939a2a499983066a2331bb953eab8a4ca85c66a8a19f42cb1b28f7a8
digest matches Q26 baseline 91ecf6bf...: True
== leg 2: uncited pre-write (was 20 files before the patch) ==
exit=1 files=0 BUILD FAILED: magazine-001:uncertainty: prose-anchor token names handle @NotACitedHandle which no entry cites
== leg 3: uncited in quote-branch body (check_uncited fires first) ==
exit=1 files=0 BUILD FAILED: block-6:body: token sentinel {{ in a field that is not a prose panel (panels: body_provenance, plain_summary.text, uncertainty)
== leg 4: cited token in all four panels builds and expands ==
exit=0 files=125 anchors on magazine-001 page: 4
== leg 5: malformed still dies pre-write ==
exit=1 files=0 BUILD FAILED: magazine-001:body: malformed prose-anchor token — sentinel {{ left after token expansion; tokens must read exactly {{x:HANDLE}} with a 1-15 character handle
== leg 6: gap still dies pre-write ==
exit=1 files=0 BUILD FAILED: coverage.gaps[4]: token sentinel {{ in a field that is not a prose panel (panels: body, body_provenance, plain_summary.text, uncertainty)
== leg 7: backstop — expand_prose still dies on uncited when pre_checked=False ==
exit=1 BUILD FAILED: magazine-001:body: prose-anchor token names handle @NotACitedHandle which no entry cites
== leg 8: control build byte-identical to shared site-ledger ==
diff -rq output: '' rc 0
files in shared site-ledger: 125
== leg 9: shared tree untouched ==
97fedb5dd5e85d2b10071e8e4904b3a9b1573072149e722fbece93eaf0774672 /root/stackchain-legends-build/build_blocks.py
63c9cee6103a6824c90c71440fa983d2f5592e380f9c3fb40f7ad366ae41723d /root/stackchain-legends-build/blocks.json
files newer than 01:00Z today: ''
## Raw fixture log (`Q27-RESULTS.json`)
| fixture | entry | exit | files | first stderr line |
|---|---|---|---|---|
| panel-valid:body | magazine-001 | 0 | 125 | (built) |
| panel-malformed:body | magazine-001 | 1 | 0 | BUILD FAILED: magazine-001:body: malformed prose-anchor token — sentinel {{ left after token expansion; tokens must read exactly {{x:HANDLE}} with a 1-15 charac |
| panel-uncited:body | magazine-001 | 1 | 0 | BUILD FAILED: magazine-001:body: prose-anchor token names handle @NotACitedHandle which no entry cites |
| panel-valid:plain_summary.text | magazine-001 | 0 | 125 | (built) |
| panel-malformed:plain_summary.text | magazine-001 | 1 | 0 | BUILD FAILED: magazine-001:plain_summary.text: malformed prose-anchor token — sentinel {{ left after token expansion; tokens must read exactly {{x:HANDLE}} with |
| panel-uncited:plain_summary.text | magazine-001 | 1 | 0 | BUILD FAILED: magazine-001:plain_summary.text: prose-anchor token names handle @NotACitedHandle which no entry cites |
| panel-valid:body_provenance | magazine-001 | 0 | 125 | (built) |
| panel-malformed:body_provenance | magazine-001 | 1 | 0 | BUILD FAILED: magazine-001:body_provenance: malformed prose-anchor token — sentinel {{ left after token expansion; tokens must read exactly {{x:HANDLE}} with a |
| panel-uncited:body_provenance | magazine-001 | 1 | 0 | BUILD FAILED: magazine-001:body_provenance: prose-anchor token names handle @NotACitedHandle which no entry cites |
| panel-valid:uncertainty | magazine-001 | 0 | 125 | (built) |
| panel-malformed:uncertainty | magazine-001 | 1 | 0 | BUILD FAILED: magazine-001:uncertainty: malformed prose-anchor token — sentinel {{ left after token expansion; tokens must read exactly {{x:HANDLE}} with a 1-15 |
| panel-uncited:uncertainty | magazine-001 | 1 | 0 | BUILD FAILED: magazine-001:uncertainty: prose-anchor token names handle @NotACitedHandle which no entry cites |
| nonpanel:title | magazine-001 | 1 | 0 | BUILD FAILED: magazine-001:title: token sentinel {{ in a field that is not a prose panel (panels: body, body_provenance, plain_summary.text, uncertainty) |
| nonpanel:date_display | magazine-001 | 1 | 0 | BUILD FAILED: magazine-001:date_display: token sentinel {{ in a field that is not a prose panel (panels: body, body_provenance, plain_summary.text, uncertainty) |
| nonpanel:date_basis | magazine-001 | 1 | 0 | BUILD FAILED: magazine-001:date_basis: token sentinel {{ in a field that is not a prose panel (panels: body, body_provenance, plain_summary.text, uncertainty) |
| nonpanel:date | magazine-001 | 1 | 0 | BUILD FAILED: magazine-001:date: token sentinel {{ in a field that is not a prose panel (panels: body, body_provenance, plain_summary.text, uncertainty) |
| nonpanel:contributor.display_name | magazine-001 | 1 | 0 | BUILD FAILED: magazine-001:contributor.display_name: token sentinel {{ in a field that is not a prose panel (panels: body, body_provenance, plain_summary.text, |
| nonpanel:excerpts.0.text | magazine-001 | 1 | 0 | BUILD FAILED: magazine-001:excerpts[0].text: token sentinel {{ in a field that is not a prose panel (panels: body, body_provenance, plain_summary.text, uncertai |
| nonpanel:excerpts.0.url | magazine-001 | 1 | 0 | BUILD FAILED: magazine-001:excerpts[0].url: token sentinel {{ in a field that is not a prose panel (panels: body, body_provenance, plain_summary.text, uncertain |
| nonpanel:body_source_url | magazine-001 | 1 | 0 | BUILD FAILED: block[30]: source URL must be https, got '{{x:' |
| nonpanel:receipt.reuse_status | 5 | 1 | 0 | BUILD FAILED: block-5:receipt.reuse_status: token sentinel {{ in a field that is not a prose panel (panels: body_provenance, plain_summary.text, uncertainty) |
| url-field:body_source_url | magazine-001 | 1 | 0 | BUILD FAILED: magazine-001:body_source_url: token sentinel {{ in a field that is not a prose panel (panels: body, body_provenance, plain_summary.text, uncertain |
| url-field:excerpts[0].url | magazine-001 | 1 | 0 | BUILD FAILED: magazine-001:excerpts[0].url: token sentinel {{ in a field that is not a prose panel (panels: body, body_provenance, plain_summary.text, uncertain |
| excl:contributor.handle | magazine-001 | 1 | 0 | BUILD FAILED: magazine-001:contributor.handle: token sentinel {{ in a field that is not a prose panel (panels: body, body_provenance, plain_summary.text, uncert |
| class:status | magazine-001 | 1 | 0 | BUILD FAILED: block[30] unknown status '{{x:' |
| class:ledger | magazine-001 | 1 | 0 | BUILD FAILED: block[30] unknown ledger '{{x:' |
| class:variant | magazine-001 | 1 | 0 | BUILD FAILED: magazine-001:variant: token sentinel {{ in a field that is not a prose panel (panels: body, body_provenance, plain_summary.text, uncertainty) |
| head:jsonld-author | magazine-001 | 1 | 0 | BUILD FAILED: magazine-001:contributor.display_name: token sentinel {{ in a field that is not a prose panel (panels: body, body_provenance, plain_summary.text, |
| index:label | magazine-001 | 1 | 0 | BUILD FAILED: magazine-001:title: token sentinel {{ in a field that is not a prose panel (panels: body, body_provenance, plain_summary.text, uncertainty) |
| index:date | magazine-001 | 1 | 0 | BUILD FAILED: magazine-001:date_display: token sentinel {{ in a field that is not a prose panel (panels: body, body_provenance, plain_summary.text, uncertainty) |
| index:coverage.gaps | coverage.gaps[4] | 1 | 0 | BUILD FAILED: coverage.gaps[4]: token sentinel {{ in a field that is not a prose panel (panels: body, body_provenance, plain_summary.text, uncertainty) |
| pager:n | magazine-002 | 1 | 0 | BUILD FAILED: magazine-002:n: token sentinel {{ in a field that is not a prose panel (panels: body, body_provenance, plain_summary.text, uncertainty) |
| genesis:body | 5 | 1 | 0 | BUILD FAILED: block-5:body: token sentinel {{ in a field that is not a prose panel (panels: body_provenance, plain_summary.text, uncertainty) |
| prewrite:uncited-handle | magazine-001 | 1 | 0 | BUILD FAILED: magazine-001:uncertainty: prose-anchor token names handle @NotACitedHandle which no entry cites |
| control:notoken | - | 0 | 125 | (built) |
## Control: no-token build unchanged
Unmodified `blocks.json` (sha256 `63c9cee6103a6824c90c71440fa983d2f5592e380f9c3fb40f7ad366ae41723d`)
through the patched builder: exit 0, **125 files, tree digest `91ecf6bf939a2a499983066a2331bb953eab8a4ca85c66a8a19f42cb1b28f7a8`** — the Q26 baseline, byte for byte — and
`diff -rq <scratch>/site-ledger /root/stackchain-legends-build/site-ledger` silent. A cited token
placed in all four panels of `magazine-001` expands to exactly 4 anchors on that page and moves
no other file (leg 4).
## Live archive, re-measured (2026-10-11)
- `https://alexanderwhitestone.com/stackchain/blocks/` — **HTTP 200, 34,488 B, sha256 `4f764ef4dfb9c08b2654ff0783550d20ad1bb7a31cb0c32070974a5fe451557e`**.
## Limits
- **Scratch only.** The patched builder lives in `/root/crucible/q27/` and is not staged to the
shared tree; publishing it is Alexander's decision, the same gate Q26 and Q13 state.
- **Fixtures target one entry per shape, not a census.** Q26's §9 limit 2 was about *entries*;
this item's answer is about *sites*, and a site's behaviour does not vary by entry beyond the
two `body_kind` branches, both of which are exercised (S17/S27 quote-branch, S29 summary).
- **Testimony only:** none. Every number in this record was produced by a command in this run;
the live index was fetched this run.