Repository navigation
Demo book layer: a private entity served as gists, built keyless-testably - #33
Conversation
…ably - demo/build-lib.ts: the demo build on the schema-4 pipeline as pure functions (adapters, collectEntities, gist drafting into demo/corpus/projections.json, policy resolution, vectors reused by content hash, gold-query vectors reused by id); demo/build.ts supplies the real client. demo/build.test.ts runs the same path with a fake embedder and a scripted drafter: three layers, the book drafted and resolved, the served view stripped, the sweep clean, a no-op rebuild embedding nothing, an edit re-embedding one fragment, a quoting draft failing the lint and resolving to locator. - demo/run.ts: --natural+book unions demo/corpus/index.book.json and demo/gold.book.yaml after checking the layer's served view and sweeping every released gist against the gold's canaries; the headline is untouched (7/7, rho 1.0000; the spire 9/9; int4 rejected). - demo/artifacts.test.ts reads schema 4 (entity allowlist, hashes through embedStringFor); demo/book.test.ts holds the committed layer to one fragment per chapter, every chapter served as a gist, and a clean sweep, skipped until the keyed build has run. - scripts/prepare-gutenberg-book.mjs turns the Gutenberg text into the book file without rewriting a word (both heading layouts, header, preface, illustrations, and license dropped, --expect, --check-canaries), with a spawn test. The text itself is the keyed build's input and is not in this commit. - Docs: demo/README.md, demo/corpus/README.md (provenance row, choice 8), build-handoff §6, CONTRACT §12, STANDARDS, CHANGELOG. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01F2ti456guy1bXVmjA3fBTV
Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01F2ti456guy1bXVmjA3fBTV
The route still resolves to the chapter and the answer is the fixed template with the released description, because the wire type has no field the text could arrive in; the teaching corpus's q12 and q13 are the twins. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01F2ti456guy1bXVmjA3fBTV
Automated Checks (advisory, non-blocking)✅ All checks passed. Standards ComplianceI don’t see a boundary-integrity regression in the served path here. The new book-layer preflight in const served = validateServedIndex(toServedIndex(bookFile), `${BOOK_INDEX} (served view)`);
const sweep = sweepCanaries(served, canaries);is well aligned with the repo standards around validating the stripped served view before use and sweeping every released gist keylessly. Likewise, One standards-adjacent gap is still open in const missing = canaries.map((c, i) => (new RegExp(c, 'i').test(chapterText) ? null : i)).filter((i) => i !== null);The surrounding comments/docs describe canaries as literal wording from the novel. Treating them as regexes means metacharacters change the meaning of the check, and malformed regex syntax will throw instead of reporting missing canaries by index. That weakens the handoff guard that is supposed to verify the canaries actually exist in the prepared text. SummaryThis PR extracts the keyed demo build into What to pay attention to
Things I noticed
Good patterns
Suggested improvements
Questions for the author
Surmado Code Review (v1.5-mt, review #5 on Want to change your STANDARDS.md or YML? Edit it directly, or tune it with our AI agent Scout. Comment |
A committed query vector was reused by id alone, so a gold query edited under the same id would have been judged against a stale embedding. query-vectors.json is schema 2: each entry carries queryContentHash of the text it embeds; demo:build reuses a vector only when the hash matches and re-embeds the rest; demo:run refuses a missing or stale vector by id; demo/artifacts.test.ts holds every committed vector to the gold text in tree. The committed file was stamped keylessly by scripts/stamp-query-vectors.ts (no query text had changed since the vectors were written: 166ad22 onward changed notes and labels only), and no vector changed. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01F2ti456guy1bXVmjA3fBTV
|
Both points, in order. Stale query vectors: real, fixed in 3338a28. Reuse by id alone would have let an edited gold query be judged against the embedding of its old text. First build from empty: already handled. Generated by Claude Code |
… one duplicate-id guard
scripts/stamp-query-vectors.ts checked existence by catching loadGold,
which hid a malformed gold file behind "absent"; it now checks existsSync
and lets a bad file throw its own message. It also stamped the text
substituted with an empty author rather than the configured one the build
embeds; the demo queries carry no {{author}}, so the committed hashes are
unchanged, and the script now substitutes config.authorName as the build
does. assertUniqueGoldIds (demo/build-lib.ts) is shared by the build, the
runner, and the script, so none can judge two cases under one id.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01F2ti456guy1bXVmjA3fBTV
|
All three in 4563015.
Generated by Claude Code |
A checkout without demo/corpus/private-books/ but with the book's artifacts would have cleared the author-facing projections while the book index stayed stale. assertLayerNotOrphaned refuses a layer whose index exists but whose sources are gone, and assertNoOrphanedProjections refuses a stored gist no source produces, both before anything is written and both naming the remedy; the test proves nothing is written. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01F2ti456guy1bXVmjA3fBTV
Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01F2ti456guy1bXVmjA3fBTV
|
Refused, not cleared. The split you describe was real: an absent So the intended model is the second one you named: once book artifacts exist, a build without the book is an error, not a state the build quietly reconciles. Generated by Claude Code |
|
/rerun-review |
What & why
Refs #24, #28 (the keyless half; #28 closes when the keyed build lands on this branch). The demo gains the contract's other exposure: The Wonderful Wizard of Oz (Project Gutenberg #55) as one private entity, one fragment per chapter, served as gists (
exposure: semantic). Private is the same layer assignment the sermons carry; what the layer shows is that a chapter reaches a consumer as provenance plus a released description and never as text.demo/build-lib.ts(new): the demo build on the schema-4 pipeline as pure functions (adapters →collectEntities→ gist drafting intodemo/corpus/projections.json→ policy resolution → embed →writeIndex). Vectors are reused by content hash and gold-query vectors by id, so adding the book never re-embeds a Smith and the headline cannot move.demo/build.tsonly supplies the real client and drafter.demo/build.test.tsruns the same functions keyless with a fake embedder and a scripted drafter: three layers from markdown, the book drafted and resolved, the served view stripped and verified, the sweep clean, a no-op rebuild embedding nothing, an edit re-embedding one fragment, a quoting draft failing the lint twice and resolving tolocator, a canary in a released gist named by index.demo/run.ts:--natural+book(and--natural+synthetic+book) unionsdemo/corpus/index.book.jsonand loadsdemo/gold.book.yaml, after a keyless check of the layer's served view (validateServedIndex(toServedIndex(...))) and a canary sweep over every gist it would release; a hit fails the run. The headline is untouched:--natural7/7 at rho 1.0000, the spire 9/9, int4 still rejected 7/9.demo/artifacts.test.tsreads schema 4 (entity allowlist; hashes throughembedStringFor, which is the same invariant stated in v4 terms).demo/book.test.tsholds the committed layer to one fragment per chapter, every chapter served as a gist, no chapter text in the served view, and a clean sweep; it skips until the keyed build has run.demo/gold.book.yaml: two route cases pinned to the related-material template (#ch8,#ch1), a verbatim demand (oz-verbatim, the twin ofeval/gold.yamlq12/q13), and five canaries from the novel for the sweep.scripts/prepare-gutenberg-book.mjs: turns the Gutenberg text into the book file without rewriting a word (both heading layouts; header, preface, illustrations, license dropped;--expect 24;--check-canariesnames a canary that does not occur, by index), with a spawn test intest/prepare-gutenberg-book.test.ts.demo/README.md(new section),demo/corpus/README.md(provenance row, authored choice 8),build-handoff.md§6 (the owner's steps),docs/CONTRACT.md§12,.github/STANDARDS.md(the committed-artifacts exception now names the layer),CHANGELOG.md.What is not in this PR, on purpose: the novel's text, its gists, and its vectors. Gutenberg and its mirrors are unreachable from the sandbox. The steps are build-handoff §6: run the converter on
55-0.txt,npm run demo:build(keyed; drafts 24 gists, embeds 24 chapters and 3 queries, touches nothing else),npm run demo:run -- --natural+book, commitdemo/corpus/private-books/…,index.book.json,projections.json,query-vectors.jsonon this branch. Two things to read from that run: theunservablelines (a chapter whose draft failed the lint twice is served as a locator until its draft is edited) and the--check-canariesreport (a canary the text does not contain guards nothing).Checklist
npm testpasses without an API key (the offline CI gate) — 116 pass, 2 skipped by design until the layer is built, no live callsnpm run typecheckpassestoServedIndex/project()the Smiths use, and the test asserts no chapter phrase survives the stripnpm run test:distpasses;demo:runholds all three headlines.github/STANDARDS.md— the converter and the runner name pieces and indexes, never a canary or a gist🤖 Generated with Claude Code
https://claude.ai/code/session_01F2ti456guy1bXVmjA3fBTV
Generated by Claude Code