Repository navigation
feat(spec)!: a page gains an optional print declaration and a linted printable block subset; the zero-reader document schemas retire whole (#22158) - #22193
Conversation
…rintable block subset The print page, card 1 of the B-prime ruling on #8346: PagePrintSchema on PageSchema.print (paper size, orientation, margins in millimetres, the running header and footer as the page's own header/footer regions, page numbers, page-break hints), the parse-time composition check checkPagePrintComposition, the printable block subset and its refusal reasons in the spec, and validatePrintPageBlocks in @objectstack/lint on all three commands and the page save door. Claude-Session: https://claude.ai/code/session_01RPo7FUd6bSnAfkWMAKi848 Co-authored-by: Claude <noreply@anthropic.com>
…; print rows in the liveness ledger The 17.0 retirement of 'pdf' stands; the sentence 'PDF export itself was declined as NOT PLANNED' stopped being true when PDF documents were re-planned for v18, so the prescription now names the view's allowPrinting for printing a list and a page that declares print for a document. The print declaration's eleven rows are planned with authorWarn on the container, naming card 2 of the #8346 ruling as the carrier. Claude-Session: https://claude.ai/code/session_01RPo7FUd6bSnAfkWMAKi848 Co-authored-by: Claude <noreply@anthropic.com>
…mplate, Document, ESignatureConfig, DocumentVersion) ADR-0049 enforce-or-remove, by the B-prime ruling on #8346: a printable document is a page that declares print, so 'template' means one thing. Four defs leave @objectstack/spec/data via RETIRED_DEFS_BY_MAJOR[18] with the D3 semantic entry document-schemas-retired and a step-18 rationale fragment; the ESignatureConfig deadline-key entries stay as history. Generated artifacts follow in the next commit. Claude-Session: https://claude.ai/code/session_01RPo7FUd6bSnAfkWMAKi848 Co-authored-by: Claude <noreply@anthropic.com>
…e print page and the document retirement json-schema.manifest/data.json -4 defs, authorable-surface/data.json -30 rows and authorable-defaults/data.json -2 (the deliberate hand-deletions gate (a) asks for on a whole-def retirement); ui.json +1 def and +9 rows for PagePrint and Page:print; api-surface, declaration-map and export-origins follow; the data/document reference page removed and the page reference regenerated; the strictness ledger drops its two document.zod.ts rows. Claude-Session: https://claude.ai/code/session_01RPo7FUd6bSnAfkWMAKi848 Co-authored-by: Claude <noreply@anthropic.com>
…document family retirement Clause-② yes (narrowing): the print declaration widens PageSchema and the document family's exports leave @objectstack/spec/data. Claude-Session: https://claude.ai/code/session_01RPo7FUd6bSnAfkWMAKi848 Co-authored-by: Claude <noreply@anthropic.com>
The merge script's designed collection point: content/docs/references/index.mdx regenerated from the merged sources, so main's API row (+1 schema) and this branch's data and ui rows compose. Claude-Session: https://claude.ai/code/session_01RPo7FUd6bSnAfkWMAKi848 Co-authored-by: Claude <noreply@anthropic.com>
…ement records check:objectui-pin-citations finds a pin citation by its backticks; the retired-defs entries, the retirement pin and the print liveness rows now spell every cited sha that way. Claude-Session: https://claude.ai/code/session_01RPo7FUd6bSnAfkWMAKi848 Co-authored-by: Claude <noreply@anthropic.com>
…e prose count The page form does not offer print until the console's print rendering lands (the ruling puts its authoring UI in the page designer with it), so the reconciliation ledger records the omission. The list-page refusal names the page types in quoted form so the app-root prose pin keeps reading the page-type sentences it counts. Claude-Session: https://claude.ai/code/session_01RPo7FUd6bSnAfkWMAKi848 Co-authored-by: Claude <noreply@anthropic.com>
…30 -> 15 of 29) Claude-Session: https://claude.ai/code/session_01RPo7FUd6bSnAfkWMAKi848 Co-authored-by: Claude <noreply@anthropic.com>
…geBlocks joins the registry) Claude-Session: https://claude.ai/code/session_01RPo7FUd6bSnAfkWMAKi848 Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 package(s): 14 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 5 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 139 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 2af178c9fe9e8ddfb4a6ebf5d568b25b2d2ed626 && git checkout 2af178c9fe9e8ddfb4a6ebf5d568b25b2d2ed626
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 73a0a6bf1db3216a0c82bee1503011040c6a1394 9f9fa3dba1c6046ce9dffc8f385c73aaae367c5f && git checkout -B drift-repro 73a0a6bf1db3216a0c82bee1503011040c6a1394 && git merge --no-ff 9f9fa3dba1c6046ce9dffc8f385c73aaae367c5f
node scripts/docs-audit/affected-docs.mjs --json 73a0a6bf1db3216a0c82bee1503011040c6a1394
|
Contract reviewServed-tier: ① Derived judgmentsRead, and nothing else: card #22158 (body and its three comments: claim Every accept-set and public-surface change the diff implies, each named right or wrong against the ruling's words. Widenings (additive):
Narrowings (each a refusal that did not exist before this head):
The Not-taken list, checked against the diff: no ② Semver level
③ Boundary flagsEvery dev flag and both Open question 2 — the running header and footer as the page's own Open question 1 — the 13-type allow list against a narrower list. WITHIN THE RULING. The ruling says "A printable block subset is defined and linted: a block that virtualises rows or lays out responsively is refused inside a print page, with pins" — it prescribes what MUST be refused and leaves the subset to be defined. Every block its two criteria reach is refused (①.5); every body-vocabulary kind it names is in; the additions ( Deviation 2 — Deviation 4 — the subset rule on the page save door from birth ( Deviation 1 (H1 falsified) and deviation 3 (structural refusals in the schema, the block walk in lint): mechanism choices the ruling does not constrain. The parse-time refusals sit beside the two existing Deviation 5 — about 15 files outside the claim's declared surface (pins, transcript counts, ledger rows, Out-of-scope findings — carriers judged:
Nits for the seat (none blocks the verdict):
Check-runs on the head Implemented-by: VERDICT: PASS Rendered 2026-10-08T05:54Z; an isolated contract-review subagent of the session named above, read-only. |
CI red on
|
…/meta/types page control counts print Patch round 1 on the print page. The /meta/types control pinned page at 24 top-level properties; print is the 25th, a declared key, not a derivation change (the same note field and object carry). A utility page is a floating panel, not a document, so the parse refuses print there beside list, and every refusal names the admitted types (record, home, app) from one phrase. The type-alias pin's PageTypeSchema line gets its space back, and views.mdx stops saying PDF export was declined: pdf stays out of exportOptions, a list prints through allowPrinting, a document is a page that declares print. Claude-Session: https://claude.ai/code/session_01RPo7FUd6bSnAfkWMAKi848 Co-authored-by: Claude <noreply@anthropic.com>
…ing its admitted page types Claude-Session: https://claude.ai/code/session_01RPo7FUd6bSnAfkWMAKi848 Co-authored-by: Claude <noreply@anthropic.com>
…counted once its neighbour's space came back The PagePrintSchema pin added one isomorphic pin; the same edit dropped the space in the untouched PageTypeSchema line, whose '= Assert<' the count's pattern then missed, so the count read 772 and stayed green by accident. Restoring the space made it 773; the literal, the case title and the header's prose follow, with the arrow entry naming why. Claude-Session: https://claude.ai/code/session_01RPo7FUd6bSnAfkWMAKi848 Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: ① Derived judgmentsRead, and nothing else: card #22158 (body and all four comments: claim Widenings (additive against
Narrowings (each a refusal that did not exist before this head):
The two moved pins — each still proves what its title says:
The Not-taken list, checked against the diff: no ② Semver level
③ Boundary flagsEvery flag of both rounds and both first-round Open question 2 — the running header and footer as the page's own Open question 1 — the 13-type allow list against a narrower list. WITHIN THE RULING. The ruling says "A printable block subset is defined and linted: a block that virtualises rows or lays out responsively is refused inside a print page, with pins" — it prescribes what MUST be refused and leaves the subset to be defined. Every block its two criteria reach is refused (①.5); every body-vocabulary kind it names is in; the additions ( Deviation 2 — Deviation 4 — the subset rule on the page save door from birth ( Deviations 1 and 3 (regions instead of H1's block arrays; the structural refusals in the schema beside the two existing Deviation 5 — files outside the claim's declared surface (pins, transcript counts, ledger rows, Patch-round flags (os-dev-report
Out-of-scope findings — carriers judged:
Nits for the seat (none blocks the verdict):
Check-runs on the head Implemented-by: VERDICT: PASS Rendered 2026-10-08T07:42Z; an isolated contract-review subagent of the session named above, read-only. The record |
Removed from the merge queue on a conflict. Back to draft,
|
…int-page-spec # Conflicts: # packages/spec/src/ui/list-view-export-options.ts
The merge script's collection point: content/docs/references/ui/page.mdx regenerated from the merged sources, so main's --write sentence and this branch's print describe compose. Claude-Session: https://claude.ai/code/session_01RPo7FUd6bSnAfkWMAKi848 Co-authored-by: Claude <noreply@anthropic.com>
…angesets pre mode main entered pre mode (tag next) with the v18 opening marker; the no-major guard stands aside in pre mode, so the export removal is graded what it is. @objectstack/lint stays minor. Clause-② yes (narrowing) unchanged: the print key and the new exports widen, the retirements narrow. Claude-Session: https://claude.ai/code/session_01RPo7FUd6bSnAfkWMAKi848 Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: ① Derived judgmentsRead, and nothing else: card #22158 (body and all five comments: claim Where this head differs from Widenings (additive against
Narrowings (each a refusal that did not exist before this head):
The two moved pins, re-read on this head: The Not-taken list, checked against the diff: no ② Semver level
The release state at merged The ③ Boundary flagsEvery flag of all three rounds and both first-round The merge-queue removal ( Merge-round deviation 1 — the Merge-round deviation 2 — Merge-round deviation 3 — Merge-round deviation 4 (worktree lifecycle) and the patch-round process notes (PR-body PATCH, lock-queue timeouts re-queued): process notes this record does not rely on; the head's check-runs are the gates. Open question 2 — the running header and footer as the page's own Open question 1 — the 13-type allow list against a narrower list. WITHIN THE RULING. The ruling prescribes what MUST be refused ("a block that virtualises rows or lays out responsively") and leaves the subset to be defined. Every block its two criteria reach is refused (①.5); every body-vocabulary kind it names is in; the additions each draw all they declare at any width, by their declarations at the head. Refusing controls, inputs, shell chrome, folding containers and per-viewer banners goes beyond the two reasons but contradicts nothing in the ruling, and the allow list is the loud form: widening is additive on a named pull, narrowing later would be breaking. Option B would refuse Deviation 2 — Deviation 4 — the subset rule on the page save door from birth ( Deviations 1 and 3 (regions instead of H1's block arrays; the structural refusals in the schema beside the two existing Deviation 5 — files outside the claim's declared surface (pins, transcript counts, ledger rows, Patch-round flags ( Out-of-scope findings — carriers judged, unchanged: Nits for the seat (none blocks the verdict):
Check-runs on the head Implemented-by: VERDICT: PASS Rendered 2026-10-08T10:20Z; an isolated contract-review subagent of the session named above, read-only. The record |
Fixes #22158
Clause-②: yes (narrowing)
Card ① (spec only) of the ruling of record on #8346. #8346 remains open for cards ② (objectui: the print CSS mapping, the printable blocks, the record page's print action), ③ (the render service, the Chromium driver, the archive) and ④ (the record-page "generate PDF" action); none of them is in this PR.
The ruling this executes (verbatim, comment
6051470224on #8346, maintainer 「8346 B′」, 2026-10-08T03:18Z)What changes
1.
PageSchema.print— the print declaration (additive,PagePrintSchema, closed)paperSize'A4' | 'A5' | 'Letter' | 'Legal'@page { size }size keywordorientation'portrait' | 'landscape'@page { size }orientation keywordmargins{ top, right, bottom, left }, numbers ≥ 0, millimetres@page { margin }repeatHeaderheaderregion repeated on every sheetrepeatFooterfooterregion repeated on every sheetpageNumbers@pagemargin box withcounter(page)/counter(pages)repeatTableHeadersthead { display: table-header-group }avoidBreakInsidebreak-inside: avoidon every blockNo
.default()anywhere, so nothing materializes into parsed pages (PagePrintis one type, pinned intype-alias-convention.pin.test.ts). Every.describe()names its consumer: the console's browser print rendering (card ②) and later the server-side renderer (card ③). Declared-equals-enforced: nothing reads these keys until ② lands, soliveness/page.jsoncarriesprintand its 10 nested keys asplanned(11 rows; the container carriesauthorWarn+authorHint), naming ② as the carrier; the page form records the omission inmetadata-form-zod-reconciliation.test.tsfor the same reason.Refused at the parse —
checkPagePrintComposition, attached by identifier and exported for.shapemirrors (pinned inobject-refinement-check-exports.test.ts):printon aslottedpage (unwritten slots draw the synthesized default layout), on anhtml/jsx/reactpage (blocks come fromsource), on atype: 'list'page (the list's print control isinterfaceConfig.allowPrinting), on atype: 'utility'page (a floating panel, not a document — added in patch round 1), on afullpage with noregions(synthesized default layout), andrepeatHeader/repeatFooterwith no region of that name. A print page is therefore akind: 'full'page oftype: 'record','home'or'app'with its blocks inregions, and every refusal names those three types from one phrase.2. The printable block subset and its lint
PRINTABLE_PAGE_COMPONENT_TYPES(13 types, an allow list) andPRINT_REFUSED_PAGE_COMPONENT_TYPES(39 types, each with its reason) live inpage.zod.ts, so card ② reads the same set the lint enforces.page-print.test.tspins that the two classify every live vocabulary type exactly once (56 known = 13 printable + 39 refused + 4 retired-by-name).@objectstack/lintgains the gating ruleprint-page-block-unprintable(validatePrintPageBlocks,packages/lint/src/validate-print-page-blocks.ts) in the existing page-block family (it walks with the sharedwalkPageComponents, ascomponent-type-unknowndoes). Registered inauthoring-rules.tson all three commands (os validate/os build/os lint) and the page save door (surfaces: CLI_AND_RUNTIME,runtimeTypes: ['page']): unlike its sibling, which is held off the runtime door pending a false-refusal budget over stored tenant rows, this rule can only speak about a page carryingprint, a key no stored row or config file could carry before this PR (the shape is closed) — the budget is zero by construction, and the judgment is page-local, so apagewrite's one-page snapshot is its whole input.Block-type classification (measured by declaration, renderer spot-checked at the
.objectui-shapina58626c88)Printable = draws everything it declares, in full, laid out the same at any width. Refused = it virtualises / windows its rows, or lays itself out to the screen (the ruling's two reasons), or has no printable content of its own (other).
action:buttonaction:groupaction:iconaction:menuai:chat_windowai:suggestionapp:launchercloud-connection:panelelement:buttonelement:definition-listelement:dividerelement:filterelement:formelement:imageelement:metadata_viewerelement:numberelement:record_pickerelement:repeaterelement:textelement:text_inputglobal:notificationsglobal:searchmarketplace:installed-listmcp:connect-agentnav:breadcrumbnav:menuobject-calendarobject-formmobile); print a record's values withrecord:detailsobject-ganttobject-gridpagination/pageSize), so a printed copy carries only the page on screenobject-kanbanobject-mapobject-master-detail-formrecord:detailsand its lines withrecord:line_itemsobject-metricobject-timelinelimit) along a time axis scrolled to the visible rangeobject-treepage:accordionpage:cardpage:footerpage:headermaxVisible/mobileMaxVisible) and carries the record chrome (star, copy id)page:sectionpage:sidebarpage:tabsrecord:activitylimit) with comment and reaction controls, so a printed copy carries only part of itrecord:alertrecord:chatterposition,width,collapsible) around a live feedrecord:detailsrecord:discussionposition,width,collapsible) around a live feedrecord:highlightsrecord:historylimit, 50 unless set), so a printed copy carries only part of itrecord:line_itemslimitcap, 500 unless set)record:pathrecord:detailsprintsrecord:quick_actionsrecord:reference_railrecord:related_listlimitrelated records (5 unless set) behind a "View all" link, so a printed copy carries a window of the rows, never all of themuser:profileflex,object-chart, a plugin widget)Evidence behind the windowing verdicts, by declaration:
record:related_list.limitdefaults to 5 behindshowViewAll;object-griddeclarespagination/pageSize/showPagination;record:history.limitrenders 50 by default;record:line_itemsdeclares "The grid has no pagination" (rendererLineItemsPanel.tsxat the pin carries no pagination control), so it is the printable all-rows table.page:headerdeclaresmaxVisible/mobileMaxVisible(a viewport-switched action bar);object-formdeclaresmobile.3. The list-export retirement sentence
LIST_VIEW_EXPORT_PDF_RETIREDno longer says "(PDF export itself was declined as NOT PLANNED)".'pdf'stays refused; the prescription now points at the view'sallowPrintingfor printing a list as shown and at "a page that declaresprint" for a document. PR #22142 landed first (de70e97a2, the house sentence names--write); the merge round stacks both intents in this one string: the sentence ends "--writeapplies the ones it can prove, and you apply the rest by hand." and still points atallowPrintingand the print page. Pins:view.test.ts(the twoNOT PLANNEDassertions replaced by the pointer assertions plus anot.toMatch(/NOT PLANNED|declined/)).4. The retirements (ADR-0049, ADR-0087 D3)
data/document.zod.tsis deleted:DocumentTemplateSchema,DocumentSchema,ESignatureConfigSchema— the ruling's three — plusDocumentVersionSchema, whose only carrier wasDocumentSchema.versioning.versions(the retirement playbook's orphan-value-schema rule; see Deviations). Route: whole-def removal, no tombstone and no D2 conversion (none of them is a stack collection member or a metadata type — no seam a conversion could run on), the change-management-family precedent (#15513):RETIRED_DEFS_BY_MAJOR[18]:data/DocumentTemplate,data/Document,data/ESignatureConfig,data/DocumentVersion(four entry files undermigrations/entries/retired-defs/, registry regenerated);document-schemas-retired+ aSTEP18_RATIONALEfragment (order 88);ESignatureConfigdeadline-key entries inRETIRED_KEYS_BY_MAJOR[18]stay as history (gate (b2) accepts an entry naming a key the build no longer emits);json-schema.manifest/data.json−4 defs,authorable-surface/data.json−30 rows,authorable-defaults/data.json−2; generated: api-surface / declaration-map / export-origins shards,content/docs/references/data/document.mdxremoved, the data nav and index regenerated;llms.txt203 → 202 / data 31 → 30;PROTOCOL_MAP.mdrow dropped; the strictness ledger's twodocument.zod.tsrows dropped; the quick reference's Document row dropped (16 of 30 → 15 of 29);src/data/document-schemas-retirement.test.ts(registry rows, the exports gone from@objectstack/spec/dataand the root entry withDocumentSchemaValidationSchemaas the lit survivor, and a tree-scoped absence walk over the radius@objectstack/spec#testalready declares, registered invitest.repo-tests.json);document.test.tsandesignature-deadline-keys-retirement.test.tsleave with the module.Zero-reader census, each against a lit control:
fec87e7e0(all packages, apps, examples, docs, skills)CHANGELOG.mdand one audits ledger rowpackages/specnameFieldSchema.objectui-shapina58626c88and maincef0eeeData.Document…/ spec-import ofDocument)PageSchema1e88edc(public, read-only clone)defineStack/ObjectSchemacloud: NOT MEASURED (no checkout in this session).
Release state (H5, re-read in the merge round):
.changeset/pre.jsonis now PRESENT onorigin/main({"mode": "pre", "tag": "next"}, entered bya87d8be29, with the v18 openingmajormarker22080-v18-line-opens.md). In pre modecheck-changeset-no-majorstands aside (its RC exemption: amajoronly ever produces anX.0.0-next.Nprerelease), so the launch-window "breaking ships asminor" convention no longer forces the level: the export removal is gradedmajoron@objectstack/spec, and@objectstack/lintstaysminor(a new refusal over a population that could not exist before, plus new exports). The fixed group is already at 18 through the opening marker, so this grade moves no version by itself. Disposition marker unchanged:registered document-schemas-retired. Changeset:.changeset/22158-print-page-spec.md(@objectstack/specmajor,@objectstack/lintminor).Deviations from the dispatch's mechanism hypotheses (each measured, stated here for the contract review)
header/footerregions, not page-block arrays underprint. H1 proposedheader/footeras block arrays. A second component tree would be a new composition root that every page walker must learn in lockstep — lint'swalkPageComponents, the ADR-0087 conversion walker and the exportedwalkAddressedPageComponentsbehindtranslatePage(held equal bypage-walk-conversion-parity.test.ts), plus objectui's validator and Studio's designer. The regions are already walked, translated, designed and placed at the top and bottom of the page on screen, so the printed sheet and the on-screen preview agree. The declaration carriesrepeatHeader/repeatFooter, andprint.header/print.footerare answered by aguidanceprescription naming the region.DocumentVersionSchemaretires with the three named schemas. The ruling names three; the fourth export of the module had one carrier,DocumentSchema.versioning.versions, and the retirement playbook's orphan-value-schema rule takes it with its carrier (an exported schema with no consumer reads as a capability). If the review wants it kept, it is one entry file and one export to restore.kind/type/regions/ region-name checks are page-local field checks and sit beside the two existingPageSchemarefinements (every door, the save door included); the subset needs the nested component walk, which lives in the lint page family as the claim says.validateComponentTypesis CLI-only pending a stored-row false-refusal budget. The claim said "wired to the authoring doors that family already reaches"; the card and the ruling ask for the save door. The budget argument is in the rule's header (zero population by construction).type-alias-convention.pin.test.ts,object-refinement-check-exports.test.ts,metadata-form-zod-reconciliation.test.ts(the form-omission ledger row forprint),view.test.ts,scripts/file-description.test.ts(a fixture named the deleted schema),vitest.repo-tests.json,llms.txt,PROTOCOL_MAP.md,liveness/README.md+state-counts/page.md,docs/audits/2026-07-unknown-key-strictness-ledger.md(+ generated counts),content/docs/getting-started/quick-reference.mdx, and four CLI transcript pages whose printed author-time rule count moves 49 → 50 (check:docs-transcript-drift).Acceptance notes (not filed; carrier named)
— rewritten in patch round 1:content/docs/ui/views.mdx:112still says "PDF export was declined"'pdf'is not an export format; a list prints as shown through the view'sallowPrinting; a printable document is a page that declaresprint.types/src/objectql.ts,ListView.tsx,ReportViewer.tsx, app-shellstyles.css). Carrier: card ②.skills/**is a governed surface and not in this card;skills/objectstack-ui/rules/actions.mdalso still teaches a per-project PDF endpoint. Carrier: the seat, as its own governed PR.template(multi-column templates) is not judged by the subset; whether a print page constrains it is card ②'s call.object-chartis outside the component vocabulary (no props row), so a chart is refused in a print page today; a monthly report with charts is a named pull to widen the subset additively.docs/NEXT_STEP.md:55still plans aui/document-template.zod.ts. Carrier: whoever next edits that roadmap file.Patch round 1 (head
0b96e61e1)d4b428426, this PR's:@objectstack/metadata-protocolprotocol.meta-types-degenerate-derivation.test.ts— "control:pagestill serves 24 top-level properties",expected 25 to be 24. Reproduced locally atd4b428426(exit 1).printis the 25th top-level key of the servedpageschema, a declared key and not a derivation change, so the control moves 24 → 25 with that reason besidefieldandobject's own moves; it still pins every other type's count.9077995ae, this PR's:@objectstack/spectype-alias-convention.pin.test.ts— "still declares all 772 isomorphic pins",expected [ …(773) ] to have a length of 772 but got 773. Not a pin main brought in (main959c209d5carries 772export type Iso_…lines and the literal 772): thePagePrintSchemapin added one, and the same first edit dropped the space in the untouchedIso_ui_page__PageTypeSchema = Assertline (it read=Assert), which the count's pattern —^export type Iso\w+ = Assertfollowed by a less-than sign — then missed — sod4b428426read 772 and stayed green by accident. Restoring the space (the review's nit) made the count honest: literal, case title and header prose now say 773, with an arrow entry. Red → green reproduced locally: the file as of9077995ae→ exit 1 with CI's exact message; HEAD → exit 0 (restore bygit checkout HEAD --, proven by blob hash and an emptygit diff HEAD).6053373285):printis refused at parse on atype: 'utility'page, in thelistrefusal's voice, pinned beside it; the admitted types (record,home,app) are named from one phrase in every refusal and in theprintdescribe;content/docs/ui/views.mdxno longer says PDF export was declined.Merge round (head
9f9fa3dba)The merge queue removed this PR on
MERGE_CONFLICTafter the contract review's PASS on0b96e61e1.origin/main31cd2104dmerged in throughbash scripts/pm/os-regen-merge.sh(merge commitbf3359dff; no rebase, no force-push;gen:schemaran only after the merge commit existed).packages/spec/src/ui/list-view-export-options.ts,LIST_VIEW_EXPORT_PDF_RETIRED, both intents stacked. Main (de70e97a2, fix(spec): the retirement sentence names--write: it applies the edits it can prove, you apply the rest #22142) kept the old body ("PDF export itself was declined as NOT PLANNED") and changed the closing house sentence to name--write; this branch rewrote the body to point atallowPrintingand the print page and kept the old closing sentence. Resolution: this branch's body + main's closing sentence — "… a printable document (an invoice, a delivery order, a letter) is a page that declaresprint— its paper, margins and running header and footer — with its blocks inregions. Runos migrate meta --from 16to list the mechanical edits for existing sources;--writeapplies the ones it can prove, and you apply the rest by hand."view.test.tsauto-merged with both sides' pins (the pointer pins and the--writesentence pin), and both pass.page.zod.ts,migrations/registry.ts,authoring-rules.ts,view.test.tsandbuild-with-claude-code.mdxauto-merged;content/docs/references/ui/page.mdxwas regenerated on the merged tree as its own commit (5f68ebe48)..changeset/pre.jsonis present on main (pre mode, tagnext,a87d8be29).scripts/check-changeset-no-major.mjs(its RC exemption: "Accumulating the next major's breaking changes is precisely what an RC window is FOR, so this guard stands aside") and the AGENTS.md checklist (yestakes at leastminor,(narrowing)is BREAKING) leave the level to the change:@objectstack/specis gradedmajor(an export removal),@objectstack/lintminor. Thea87d8be29edit tocheck-adr-0087-registration.mjsis one constant naming the consumed-prerelease directory; the disposition rule is unchanged.Clause-②: yes (narrowing). The criterion (references/execution-duties.md, 「本卡放宽接受集或扩大公开面吗」) answers yes —PageSchemaaccepts a new key and the spec and lint entries gain exports. The new rule (5d5a88eff, 「收窄已发布接受集的卡是Clause-②: no」) covers a card that only narrows;scripts/pm/clause2-line.mjsnames this card's case exactly: "yes (narrowing)— a diff that widens one surface and narrows another. Both facts are true and both are read." Same line in the changeset.Tests and gates (all at
9f9fa3dba, the final commit; the shared box, commands underscripts/pm/os-verify-lock.sh)pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2(both projects): 678 files passed, 19583 tests passed, 1 todo, exit 0.pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2: 126 files, 5778 tests passed, exit 0.page-print.test.ts,view.test.ts(this branch's pointer pins + fix(spec): the retirement sentence names--write: it applies the edits it can prove, you apply the rest #22142's--writepins) andshared/retired-key-migrate-sentence.test.ts(the class-wide sentence pin): 3 files, 501 tests passed, exit 0.node scripts/check-changeset-no-major.mjs --base origin/main --event EVENT.json(apull_requestpayload carrying this body): exit 0 — "Changesets is in pre-release mode (tag: next) —majorbumps are the expected product of an RC window", and "LEVEL AXIS: this PR declares clause-②yes (narrowing), and no package whosepackages/**/src/**it moves is gradedpatch".check-adr-0087-registration.mjs --base origin/mainwith the same event: exit 0 — ".changeset/22158-print-page-spec.md[major+BREAKING+bang+clause-②-narrowing] registered document-schemas-retired (new here: document-schemas-retired)". Both--self-tests: exit 0.pnpm --filter @objectstack/spec check:generated: all 15 artifacts current on the merged tree.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 117 families at9f9fa3dba; every one ran, plus the 44 artifact-roster commands it prints apart (check:docs-image-tagjoined the roster with main's changes): 161 commands, every exit code 0.--ran: "117 derived famil(ies) accounted for — 117 run, 0 NOT-MEASURED (a DERIVED zero — all 117 recorded an exit code and none of them is 3)".@objectstack/metadata-protocolsuite at0b96e61e1(221 files, 28253 tests passed), thePageSchema-shape sweep across 13 packages (all exit 0), and the save-door probe throughrunRuntimeAuthoringRules({ type: 'page' })(a print page holdingobject-gridrefused withprint-page-block-unprintable; a printable-only page passes).Authored by the
domain:specseat 3 dispatch, sessionsession_01RPo7FUd6bSnAfkWMAKi848.