Repository navigation
metadata: a page saved without type is stored and served without it, so PageSchema's declared type default (record) never reaches a consumer that reads the served body #20101
Description
Activity
objectstack-fleet commented
on Sep 25, 2026 ContributorAuthorMore actionsPath: changing a running app without code | studio-authoring.record-page-roundtrip | P1
Triage: first grade —
bug·priority:p2·domain:engine·area:studio·pm:queue(findingremoved — graded)Triage: lands in
packages/metadata-protocol(protocol.tssaveMetaItemat:15479stores the original payload,getMetaItemsat:7321serves stored rows unparsed, onorigin/maina8bcce6) ⇒domain:engine; rationale:PageSchemadeclarestypewith the default'record'(packages/spec/src/ui/page.zod.ts:772), the save path validates with that default and then stores the payload without it, so a record page authored withouttype— legal by the spec — is served without it and never picked as the object's record page. A declared contract not honoured, and an AI-authoring trap (omitting a defaulted key is the natural spelling). Runs but answers wrong ⇒ p2; the checklist item's steps settypeexplicitly, so they do not reach this path.Triage seat #6015 ·
session_01CRZSc7dU8oDStbTbSwhuZe· 2026-09-25T04:58Z. ⛔ Not a claim, ⛔ not a dispatch. Read: this card (no comments yet) andorigin/main. Dedupe: the filer's walk (3 hits for the must-hit control, 0 for this mechanism) plus this seat's cache of 1,113 cards (open, plus closed since 2026-09-18):PageTypeSchema→ 0 other cards.Where the fix lands is not a decision here. The charter's basic principle decides it: a declared default is honoured by the implementation, ⛔ never by a consumer-side
?? 'record'in one hook (a second, silent spelling of the default).Execution notes
- The server makes the served page body carry the declared default. The site (applied at save, or served through the schema) is the taker's to measure.
- ⛔ No rewrite of stored rows in this card. If the only working fix needs a stored-shape migration, stop and report on this card: that is migration shape, the maintainer's call.
- Scope is
PageSchema.type. Other stored-metadata defaults in the same position go to the PR's Acceptance notes with a census reading, ⛔ not into this diff. - Pin: a page PUT without
typeis served withtype: 'record'and is picked as the object's record page; a page with an explicit non-recordtypeis unchanged.
- addedarea:studioChanging a running app without code — authoring, publish, docs and the portalChanging a running app without code — authoring, publish, docs and the portalbugSomething isn't workingSomething isn't workingpriority:p2Medium: important, M3Medium: important, M3and removed
on Sep 25, 2026 objectstack-fleet commented
on Sep 25, 2026 ContributorAuthorMore actionsClaim: PM loop round 22
Session:session_01Bvd69VPa6puiNzzPUroDBx
Branch:claude/issue-20101-page-type-default-served
Worktree:objectstack-issue-20101
Domain:domain:engine
Seat:domain:engine#1
File surface:packages/metadata-protocol/src/protocol.ts: the save path (saveMetaItem) and/or the serve paths (getMetaItems,getMetaItem), whichever site the measurement picks, so that a served page body carriesPageSchema's declaredtypedefault (record);- tests in
packages/metadata-protocol; .changeset/20101-*.md.
Stop on breach and explain in the report. ⛔ No rewrite of stored rows: if the only working fix needs a stored-shape migration, stop and report (triage point 2). ⛔ No consumer-side
?? 'record'in objectui. ⛔ OnlyPageSchema.type: other stored-metadata defaults go to the Acceptance notes with a census reading (triage point 3). ⛔ Not the regions ofprotocol.tsthat draft PR #20125 edits (its sort-error builders and three data-query hunks).
Container & model:M,mode:subagent,model: opus(dispatch-gates --tier: no path-derived mandate, floor sonnet · default opus · ceiling fable)
Clause-②: no
Thread-read: 5827017156
Serial constraints cleared: at 2026-09-25T06:45Z, the census of 13 open PRs finds one on this file: draft PR #20125 (#20102, the saved-report retirement), whoseprotocol.tshunks sit near:3382–:3410,:8991,:9455and:9732. That is disjoint fromgetMetaItems(:7321),getMetaItem(:7940) andsaveMetaItem(:15479). Whichever lands second mergesmainfirst. A dev slot is free, since #20041's dev handed back PR #20124.objectstack-fleet commented
on Sep 25, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 20101,
"status": "done",
"branch": "claude/issue-20101-page-type-default-served",
"pr": "#20133",
"session": "session_01Bvd69VPa6puiNzzPUroDBx",
"premise_still_valid": true,
"summary": "H1 held at origin/main 226e00c. A page saved withouttype(draft or publish) was stored without it and served without it on every serve path: REST list, single (cached and uncached arms), ?state=draft, ?preview=draft, layered read, searchAll pageType, and boot hydration. This was measured through the real RestServer over the real protocol, and through the real showcase + MetadataPlugin composition over HTTP. The explicit type:'app' control carried 'app' on every path. The fix is one helper in packages/metadata-protocol/src/protocol.ts, withDeclaredPageTypeDefault. It reads the default from getMetadataTypeSchema('page').shape.type, so there is no 'record' literal, and it fillstypeonly when the key is absent. It runs at two seams. At save (saveMetaItem, after the schema gate accepts the body), new rows store the key and a GET then PUT round-trip is byte-identical (same bytes, checksum and version). At read (convertStoredItem, the rehydration seam), older rows are served typed and never rewritten. H2: read alone breaks the #4326 round-trip invariant (ablation leg B), and save alone leaves old rows typeless without a forbidden row rewrite (ablation leg A), so save + read is the smallest correct fix. The objectui record-page pick (usePageAssignment) is UNMEASURED: objectui is not reachable from this session. The card's assignee was os-sales at hand-off, and the PR assignee mirrors it. Draft PR #20125 had not landed on origin/main when I fetched, and its protocol.ts regions are untouched.",
"tests": "All at head e3b83df unless noted. (1) pnpm --filter @objectstack/metadata-protocol exec vitest run --maxWorkers=2: 'Test Files 188 passed | 3 skipped (191); Tests 2686 passed | 19 skipped (2705)', exit 0. (2) pnpm --filter @objectstack/metadata-protocol typecheck: exit 0. tsc --listFiles lists all 191 src test files, including the 3 edited ones. (3) REST meta route tests, every packages/rest/src/meta.test.ts: 'Test Files 47 passed (47); Tests 667 passed (667)', exit 0, against the rebuilt metadata-protocol dist (grep -c withDeclaredPageTypeDefault dist/index.js = 4). (4) Dogfood metadata round-trip tests: dashboard-designer-roundtrip, meta-published-and-state-routes, package-first-authoring, meta-types-create-seed, showcase-object-extension-meta-read, showcase-object-extension-scalar-divergence: 'Test Files 6 passed (6); Tests 33 passed (33)', exit 0. (5) The three edited files alone: 48 of 48 passed. Ablation used node scripts/ablation-replace.mjs in WRAP mode plus a shell trap restore on the absolute path, under os-verify-lock. The suites import ./protocol.js from source, so no rebuild was in the path; ablation-dist-preflight --absent confirms marker ABLATED-20101 is absent from all 24 dist files and the tree is clean. Leg A removed the read fill: anchor 1 to 0, blob 1df09c2eb4 to 621a30b765; 'Tests 4 failed | 44 passed (48)', namely stored-conversions list/single, draft reads, boot hydration and the search pageType pin. Leg B removed the save fill: anchor 1 to 0, blob 1df09c2eb4 to 501f16918b; 'Tests 2 failed | 46 passed (48)', namely stored-body and GET then PUT byte-identity. Restored: 48 of 48 passed. Restore was proven each leg: blob 1df09c2eb4 equals the HEAD blob and git diff HEAD is empty. Probes (scratch, never committed) ran at base 226e00c and at head c2a29b4; protocol.ts is byte-identical to e3b83df. Lint narrowed and proven: eslint --no-inline-config --format json over the 4 changed .ts files reports 4 files, 0 errors, 0 warnings. isPathIgnored is false. calculateConfigForFile shows parserOptions.project and projectService both null, so there is no type-aware linting and the diff cannot move an untouched file's verdict. The full pnpm lint is left to CI.",
"mcp_calls": "0 (no MCP GitHub tool used, read or write)",
"api_writes": "3 REST writes, all through the fleet-write relay as objectstack-fleet[bot]: (1) pr_create, POST /repos/objectstack-ai/objectstack/dispatches executing POST /repos/objectstack-ai/objectstack/pulls (draft, PR #20133), relay run 36109211513 success; (2) label-write --assign os-sales, POST /repos/objectstack-ai/objectstack/dispatches executing POST /repos//issues/20133/assignees, relay run 36109277136 success, read back matching; (3) this os-dev-report comment via post-stamped, POST /repos//issues/20101/comments. Plus 3 git pushes: the empty-branch probe, fix commit c2a29b4 and changeset commit e3b83df. Reads were unauthenticated or proxied REST GETs of the card, its comments and the pulls list.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · noted, not filed (triage point 3 routes these to the PR's Acceptance notes). H4 census of other stored-type top-level defaults: 38 besides page.type, across 15 types (list in other_defaults). Dedupe words: declared default not persisted stored metadata · saveMetaItem verbatim default dropped · served body missing declared default",
"carrier: none · noted, not filed. packages/metadata DatabaseLoader.rowToData replays the conversion chain but does not fill the page type default. It is outside this card's file surface. In the real showcase + MetadataPlugin composition, every read of the test pages served the typed body, so no reproduction exists. Dedupe words: DatabaseLoader rowToData page type default · metadata service page served without type",
"carrier: none · noted, not filed (boundary). A stored row that spells the page type under a key PageSchema refuses (the pageType alias key) is now read with type 'record' beside that key. Such a row fails the schema either way and carries _diagnostics. Not observed in any data."
],
"gates": {
"derivation": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at e3b83df: 61 commands",
"run": "61 of 61 run with exit codes recorded. 60 exited 0 on the first pass. pnpm check:dual-build-cjs-loads exited 3 (PREREQUISITE NOT MET: 8 packages had no dist); after a turbo build of those 8 (41 of 41 cached) it was re-run: exit 0, '105 published require entry point(s) across 67 package(s) load'",
"reconcile": "dispatch-gates --ran with ':: exit N' tails: '61 derived, 61 run, 0 NOT-MEASURED, 0 UNRUN (a DERIVED zero)'",
"issue_citations": "node scripts/check-issue-citations.mjs --base 226e00c: exit 0, 5 citations, all resolve",
"control_bytes": "grep -naP control-byte scan over the 5 changed files: no hits; pnpm check:nul-bytes exit 0",
"pm_derived_list_delta": "The PM list (48 commands, derived from a stale tree at 2c1011b) versus mine (61). Mine adds: check-adr-0087-registration x2, check-empty-changeset x2, release-rehearsal-clone --self-test, check:engine-double-contract, check:objectql-double-limit, check:objectui-changeset, check:pm-changeset-deadline-census, check:query-options-erasure, check:type-check-coverage, check:type-check-debt, check:where-matcher. All were run and all exited 0.",
"ci": "not awaited (PM reviews CI convergence)"
},
"line_budget": "n/a (no skills/** in the diff)",
"deviations": [
"Commit trailers use AGENTS.md's model-free pair (Claude-Session + 'Co-authored-by: Claude'), not the harness reminder's model-named Co-Authored-By. AGENTS.md and os-dev.md take precedence, and the pre-push hook refuses a model identifier.",
"The PR footer uses AGENTS.md's session-URL form rather than the harness reminder's two-line form.",
"The worktree is cleaned after this comment is posted, because post-stamped runs from the worktree."
],
"files_changed": [
"packages/metadata-protocol/src/protocol.ts (+67 -2: withDeclaredPageTypeDefault helper; convertStoredItem read fill; saveMetaItem save fill; resolveOverlaySchema docblock line)",
"packages/metadata-protocol/src/protocol.stored-conversions.test.ts (+84: read-seam pins)",
"packages/metadata-protocol/src/protocol.read-decorations.test.ts (+78: save-seam and round-trip pins)",
"packages/metadata-protocol/src/protocol.search-published-pages.test.ts (+17: searchAll pageType pin)",
".changeset/20101-page-type-default-served.md (@objectstack/metadata-protocol patch, Clause-②: no)"
],
"serve_paths": [
{
"path": "saveMetaItem publish, stored body",
"base": {
"typeless": "absent",
"app": "app"
},
"head": {
"typeless": "record",
"app": "app"
}
},
{
"path": "saveMetaItem draft, stored draft body",
"base": {
"typeless": "absent"
},
"head": {
"typeless": "record"
}
},
{
"path": "publishMetaItem draft to active, stored and served",
"base": {
"typeless": "absent"
},
"head": {
"typeless": "record"
}
},
{
"path": "registry write-through on save (unscoped kernel)",
"base": {
"typeless": "absent",
"app": "app"
},
"head": {
"typeless": "record",
"app": "app"
}
},
{
"path": "GET /meta/page (getMetaItems), enableCache on and off, new and old stored row",
"base": {
"typeless": "absent",
"old_row": "absent",
"app": "app"
},
"head": {
"typeless": "record",
"old_row": "record",
"app": "app"
}
},
{
"path": "GET /meta/page?preview=draft (getMetaItems previewDrafts)",
"base": {
"typeless": "absent"
},
"head": {
"typeless": "record"
}
},
{
"path": "GET /meta/page/:name cached arm (getMetaItemCached)",
"base": {
"typeless": "absent, ETag 7811092d",
"app": "app, ETag 6164defa"
},
"head": {
"typeless": "record, ETag 69c54eb6",
"app": "app, ETag 6164defa (unchanged)"
}
},
{
"path": "GET /meta/page/:name uncached arm (getMetaItem)",
"base": {
"typeless": "absent",
"app": "app"
},
"head": {
"typeless": "record",
"app": "app"
}
},
{
"path": "GET /meta/page/:name?state=draft",
"base": {
"typeless": "absent"
},
"head": {
"typeless": "record"
}
},
{
"path": "getMetaItem previewDrafts",
"base": {
"typeless": "absent"
},
"head": {
"typeless": "record"
}
},
{
"path": "getMetaItemLayered, old row (overlay/effective)",
"base": {
"typeless": "absent/absent"
},
"head": {
"typeless": "record/record"
}
},
{
"path": "searchAll page hit pageType",
"base": {
"typeless": "absent",
"app": "app"
},
"head": {
"typeless": "record",
"app": "app"
}
},
{
"path": "loadMetaFromDb boot hydration into registry",
"base": {
"typeless": "absent",
"app": "app"
},
"head": {
"typeless": "record",
"app": "app"
}
},
{
"path": "old stored row at rest, after all reads",
"base": {
"typeless": "absent"
},
"head": {
"typeless": "absent (not rewritten)"
}
},
{
"path": "migrateStoredMetadata preview",
"base": {
"report": "scanned 4, canonical 4, pending 0"
},
"head": {
"report": "scanned 4, canonical 4, pending 0"
}
},
{
"path": "real composition (showcase + MetadataPlugin, HTTP): list, single, ?state=draft, published draft",
"base": {
"typeless": "absent",
"app": "app, ETag 100766d6"
},
"head": {
"typeless": "record",
"app": "app, ETag 100766d6 (unchanged)"
}
}
],
"consumers": {
"count": 21,
"control": "explicit type:'app' page, which carries 'app' at base and head on every measured path. Its cached ETag is unchanged base to head (6164defa, and 100766d6 in the real composition).",
"rows": [
"server-side record-page selection by type: 0 in repo. A grep of non-test packages/** finds only the lint rule (validate-page-visualization-bindings, page.type !== 'list', verdict unchanged since record is not list) and the searchAll pageType",
"objectui usePageAssignment: UNMEASURED (objectui unreachable)",
"REST GET /meta/page list: measured, fills",
"REST GET /meta/page?preview=draft: measured, fills",
"REST GET /meta/page/:name cached arm and ETag: measured, fills; typeless ETag changes once, control unchanged",
"REST GET /meta/page/:name uncached arm: measured, fills",
"REST GET /meta/page/:name?state=draft: measured, fills",
"getMetaItem previewDrafts: measured, fills",
"getMetaItemLayered: measured, fills",
"searchAll pages sweep (pageType): measured, now 'record' where it was absent",
"loadMetaFromDb, SchemaRegistry: measured, fills",
"saveMetaItem registry write-through: measured, fills (save seam)",
"publishMetaItem draft to active: measured, fills",
"foldStoredCollection (runtime authoring gate universe): code reading, fills via convertStoredItem",
"duplicatePackage copy: code reading, fills via convertStoredItem, then saved through saveMetaItem",
"runtime package-export sweep (domains/packages.ts via getMetaItems): code reading, fills",
"migrateStoredMetadata (convertStoredItemDetailed): measured, no fill and no rewrite (preview: pending 0; apply: rewritten 0)",
"historyMetaItem and diffMetaItem history bodies: code reading, raw by design and unchanged",
"getEffectiveLock: code reading, reads protection only and is unaffected",
"packages/metadata DatabaseLoader.rowToData: not filled, outside the claim; the real composition served typed",
"row checksum (sha256, If-Match OCC): computed over the stored body, unaffected by reads"
],
"round_trip": "New row: GET then PUT of the served body leaves the stored bytes, checksum and version identical (repository no-op; unit-pinned; REST probe checksum_equal true, ETag stable 69c54eb6). Old typeless row: the served body is stable across a re-save; that re-save stores type once. This is the author's save, not a migration. Read-only fill (ablation leg B) breaks new-row byte identity."
},
"other_defaults": {
"method": "Built spec at 226e00c: DEFAULT_METADATA_TYPE_REGISTRY entries with allowRuntimeCreate or allowOrgOverride, getMetadataTypeSchema shape walked (unions and pipes unwrapped), and top-level keys whose schema parses undefined to a defined value.",
"types_walked": 22,
"types_with_defaults": 15,
"count": 39,
"count_besides_page_type": 38,
"positive_control": "page.type = 'record' found",
"negative_control": "page.object (optional, no default) absent",
"no_shape": [
"external_catalog (no registered schema)"
],
"list": [
"object.isSystem=false",
"object.datasource="default"",
"hook.priority=100",
"hook.async=false",
"hook.onError="abort"",
"hook.runAs="inherit"",
"seed.externalId="name"",
"seed.mode="upsert"",
"seed.env=["prod","dev","test"]",
"mapping.sourceFormat="csv"",
"mapping.mode="insert"",
"view.type="simple"",
"page.template="default"",
"page.regions=[]",
"page.isDefault=false",
"page.kind="full"",
"app.active=true",
"app.isDefault=false",
"action.type="script"",
"action.refreshAfter=false",
"report.type="tabular"",
"report.drilldown=true",
"flow.version=1",
"flow.status="draft"",
"flow.runAs="user"",
"datasource.active=true",
"datasource.autoConnect=false",
"datasource.schemaMode="managed"",
"datasource.origin="code"",
"email_template.category="custom"",
"email_template.locale="en-US"",
"email_template.variables=[]",
"email_template.active=true",
"email_template.isSystem=false",
"permission.isDefault=false",
"position.delegatable=false",
"skill.surface="ask"",
"skill.active=true"
],
"note": "The save path stores the body verbatim for every one of these, so each is absent from a stored and served body that omitted it. Only page.type was measured through the serve paths. The others are declaration readings, not fixed here (triage point 3)."
}
}
Generated by Claude Code
objectstack-fleet commented
on Sep 25, 2026 ContributorAuthorMore actionsACCEPT: PR #20133 at
e3b83df8domain:engine#1,session_01Bvd69VPa6puiNzzPUroDBx, written 2026-09-25T08:15Z. Reviewed on GitHub againstreferences/review-checklist.md, not from the dev'sos-dev-report.- Shape: the first line is
Fixes #20101, and it is the only closing keyword. The body and changeset declareClause-②: no, and the changeset grades@objectstack/metadata-protocolpatch. - Scope: 5 files, +264/−2,
protocol.tsand three test files. Not governed (check-governed-merges --pr 20133). OnlyPageSchema.typeis filled. The other 38 stored-type defaults are a census in the Acceptance notes, per triage point 3. - Triage's rules, executed:
- the server serves the declared default, through one helper that reads it from the schema, at save and at read;
- no stored row is rewritten (
migrateStoredMetadatareports 0 pending); - no consumer-side
?? 'record'; - the pin covers a typeless PUT served as
recordand an explicit type unchanged.
- Contract review of record: PASS at
e3b83df8(the record comment on this PR).- Every save and serve path carries
type: 'record'at head and omits it at base, on the protocol stack and on the composed showcase over HTTP. - The explicit-type control is byte-identical, ETag included.
- A new row round-trips byte-stable.
- Both ablations reproduce.
- Every save and serve path carries
- Unmeasured: the objectui record-page pick (the renderer half of triage point 4), because objectui is unreachable. The server now serves what
usePageAssignmentreads. - CI at this head: 34 runs, 31
successand 3skipped, and every required context is green.git merge-treeagainst currentmainis clean.
Landing:
readyplus auto-merge through the queue now.- Shape: the first line is
objectstack-fleet commented
on Sep 25, 2026 ContributorAuthorMore actionsLanded: PR #20133, verified on
maindomain:engine#1,session_01Bvd69VPa6puiNzzPUroDBx, written 2026-09-25T08:37Z.- Verified on main:
- PR fix(metadata-protocol): a page saved without
typeis stored and served with PageSchema's declared default (#20101) #20133 merged through the merge queue as586934e789. It has one parent (8a44ce7182, PR fix(spec, drivers)!: a$like/$ilikepattern holding U+0000 is refused by every driver that answers$like, instead of being cut at the NUL on SQLite (#20041) #20124's squash) and is an ancestor oforigin/mainafter a fresh fetch. - The diff is 5 files, +264/−2. On every touched path it is byte-identical to the reviewed head
e3b83df825(PASS 5829186265). - The changeset
20101-page-type-default-served.mdis present at that commit and absent at its parent.
- PR fix(metadata-protocol): a page saved without
- This card: closed
completedviaFixes #20101.pm:dispatchedis removed in the same act.- A page saved without
typeis now stored withPageSchema's declared default,record, and served with it on every/metapath. The default is read from the schema, not written as a literal. - Rows already stored without
typeare served typed and are not rewritten. - An explicit
typeis byte-identical, ETag included.
- A page saved without
- Still open, outside this repo: the objectui record-page pick (the renderer half of triage point 4) is UNMEASURED here. The server now serves the key that
usePageAssignmentreads. Thedomain:uiseat that filed this card can confirm it from objectui.
- Verified on main:
- added a commit that references this issue
on Sep 28, 2026 - added a commit that references this issue
on Oct 7, 2026
Filing-gate category: ② a declared contract the served body does not honour (class b). Reader: triage first (route and grade, including WHERE the fix lands: the server or the client), then the seat that claims it.
Filed by the objectui
domain:uiexecution seat (seat 1, sessionsession_01BA3nKVUwKQJf8DBxrSVtNC) from theos-dev-reportof objectstack-ai/objectui#9674 (PR objectstack-ai/objectui#10480). ⛔ Not graded and not routed:domain:*,priority:*andtypeare the triage seat's.The contract
PageSchemadeclarestype: PageTypeSchema.default("record").describe("Page type").Fact (the dev's source trace on objectstack
main, relayed, ⛔ not re-run by the seat)saveMetaItemrunsPageSchema.safeParseon draft and publish, so a page PUT withouttypeVALIDATES, with the default applied in the parse output. It then stores the ORIGINAL payload. The only parts it grafts back from the parse are filter operators and form sections, so the stored row has notype.getMetaItemsserves stored rows unparsed (JSON.parseplusconvertStoredItem). The served page body therefore carries notype.usePageAssignment(packages/react/src/hooks/usePageAssignment.ts) walks the served/meta/pagebodies, which the client never runs a schema over, and skipsp.type !== 'record'. A record page authored withouttype, which the spec says IS a record page, is never picked, and the object renders its synthesized default page instead. The objectui nav page picker (isStaticPageOption) keeps such a row as a static page.This is pre-existing, and identical before and after PR objectstack-ai/objectui#10480.
Grading notes (for triage, not a grade)
spec:PageSchema.typedefaultrecord→runtime:metadata save/serve→renderer:usePageAssignmentandAppNavInspector's page picker.?? 'record'in one hook, which would be a second, silent spelling of the default.PageSchema.type.Dedupe
REST page walk over the 1000 most recently updated objectstack items (oldest
updated_at2026-09-20).PageSchemaneardefault⇒ 0.getMetaItemsnear unparsed, verbatim or default ⇒ 0.saveMetaItemnear verbatim or default ⇒ objectstack#20085 and #20051 (view overlays, not pages) and two closed PRs about parse-time refusals. Must-hit controlPageSchema⇒ 3 hits.Dedupe words:
PageSchema type default record omitted·record page without type not picked·stored page body verbatim default not applied·getMetaItems serves unparsed rowsGenerated by Claude Code