Skip to content

feat(rest): parse crud / metadata / batch / routes at construction and consume the parse (#11984) - #14395

Merged
hotlong merged 8 commits into
mainfrom
claude/issue-11984-normalize-config-parse-siblings
Sep 2, 2026
Merged

feat(rest): parse crud / metadata / batch / routes at construction and consume the parse (#11984)#14395
hotlong merged 8 commits into
mainfrom
claude/issue-11984-normalize-config-parse-siblings

Conversation

@os-trump

@os-trump os-trump commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Fixes #11984

What

RestServer.normalizeConfig now runs the DECLARED contract of its four sibling sub-objects — crud, metadata, batch, routes — at construction, and builds the normalized config from the parsed output. #11637 parsed api alone (one narrowing in front of contract review rather than five); this is the other four, one narrowing later, on the same door.

Consume / validate-only decision per sub-object (key diff)

sub-object keys normalizeConfig reads declared by schema undeclared-but-honoured decision
crud operations.{create,read,update,delete,list}, patterns, dataPrefix, objectParamStyle all none consume
metadata prefix, enableCache, cacheTtl, maskObjectFields, endpoints.{types,items,item,schema} all (maskObjectFields since #11983) none consume
batch maxBatchSize, enableBatchEndpoint, operations.{createMany,updateMany,deleteMany,upsertMany}, defaultAtomic all none consume
routes includeObjects, excludeObjects, nameTransform, overrides all none consume

Newly refused / deliberately kept

Refused (all pinned through the real RestServer construction and, in §B, the real plugin start()): batch.maxBatchSize outside 1..1000 or non-integer; routes.nameTransform and crud.objectParamStyle outside their enums; non-integer metadata.cacheTtl; any declared key with the wrong type; crud.patterns keyed by a non-CRUD operation or carrying a non-HTTP method; a PARTIAL routes.overrides.OBJECT.operations (zod 4 reads that enum-keyed z.record with a required value as exhaustive — the input type already demanded all five).

Kept, because the schema declares no such rule: a negative metadata.cacheTtl (.int() only — the card guessed this one wrong); unknown keys inside a sub-object (non-strict z.object()s strip, as before); the retired openApi31 (ignore posture); api exactly as #11637 / #12450 left it.

Blast radius, measured per sub-object on origin/main @ 08e49496f (re-checked after merging d62f990a98)

  • Construction sites: 140 files carry new RestServer( or createRestApiPlugin( (277 sites). Scanning every crud: / metadata: / batch: / routes: block in those files for the sub-object's own declared keys: 0 sites outside rest-server.ts itself and this PR's pin file pass any of the four sub-objects. (routes: { data: '', ... } fixtures are discovery.routes payloads; the 90 metadata: { blocks are endpoint / plugin metadata.) Positive control: rest-server.ts's own NormalizedRestServerConfig and normalizeConfig blocks hit.
  • Value census of the constrained keys, repo-wide, every file type — positive control packages/spec/src/api/rest-server.test.ts:382/386 (maxBatchSize: 0 / 2000, the schema's own refusal pins) hits:
    • maxBatchSize: 24 lines, 3 out-of-range literals — the two schema-test pins above (never construct a server) and tracing.test.ts:413 (1024, a different schema's key).
    • nameTransform: 6 lines, 0 unknown values. objectParamStyle: 6 lines, 0 unknown values.
    • cacheTtl: 69 lines, 1 non-integer literal (packages/runtime/src/endpoint-policy.test.ts:331, 30.7 — the declarative endpoint policy's cacheTtl, plugin-rest-api.zod.ts / endpoint schemas, not MetadataEndpointsConfig).
  • Empirical: the full @objectstack/rest suite on this change — 168 files / 2828 tests passed at abb61564c6. No fixture changed.

Red-first

New pin file packages/rest/src/rest-sub-config-parse-not-cast.test.ts run against the unmodified tree (08e49496f + test file only): 13 failed | 13 passed (26) — every §A refusal and both §B plugin rejections red, every §C/§D guard green. After the change: 43 passed (43) across this file and #11637's rest-config-parse-not-cast.test.ts.

Changeset

.changeset/rest-sub-configs-parsed-not-cast.md, @objectstack/rest: minor — the level #11637's rest-api-config-parsed-not-cast.md and #12450's rest-project-resolution-parsed.md set for this door (BREAKING accept-set tightening, refused shapes enumerated, adr-0087: not-required (no-migration-prescription)). needs:contract-review per the card's Clause-② ruling.

Also in this diff

  • content/docs/permissions/system-context.mdx — the system-context census page is a GENERATED artifact anchored by rest-server.ts line numbers; inserting the schema table shifted them, check:system-context-census went red, and the page was regenerated (pnpm gen:system-context-census), once before and once after merging origin/main (the os-regen driver deferral discharged in its own commit). The second regeneration (741d9c2d9, after merging origin/main @ 00ff228fe0) had --fix REFUSE ZERO files — the sentence check-system-context-census line 117 asks the repairing PR to state — while rewriting 21 anchors across 16 rows; re-running --fix from the merge commit's own page reproduces the committed page byte for byte (blob 087009f37f), so the commit is a pure re-anchor and not a population change.

Out of scope, filed

Verification

Code head 02fd466d5 — the tree that PASSed contract review (4fe663ec90), plus a merge of origin/main @ 00ff228fe0, the census regeneration (741d9c2d9) and one changeset-prose commit enumerating the three newly-refused shapes the review named. Nothing under packages/ has changed since 4fe663ec90: the three later commits touch only the generated census page and .changeset/rest-sub-configs-parsed-not-cast.md.

  • pnpm exec turbo run build --filter='@objectstack/rest^...' then pnpm --filter @objectstack/rest typecheck (tsc + check:test-typecheck, ledger unchanged, no new debt, no as any in the new file) — exit 0 at e9fddb90bb.
  • Pins + RestApiConfigSchema constrains api.version with a regex the REST server never runs — the seam casts instead of parsing, so api.version: '' is accepted and mounts the whole API at /api// #11637's file + the upstream rest-data-create-address-unknown-key.test.ts3 files / 45 tests passed at e9fddb90bb.
  • Full @objectstack/rest suite — 168/168 files, 2828/2828 tests at abb61564c6 (the merge brought no packages/rest source change; its one new test is in the run above).
  • pnpm lint — exit 0 at abb61564c6; the sweep at head is in the report comment (shared verify lock).
  • Gate union: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands at e9fddb90bb = 61 families; 56 exit 0; 5 NOT MEASURED locally by the gates' own prerequisite exits (check-test-completeness needs a CI test log, check-half-states needs repo-scoped egress, check:skill-examples / check:dual-build-cjs-loads / check:type-check-debt need the whole workspace built); --ran reconciliation: 61 derived, 61 run, 0 unrun.

Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza

Generated by Claude Code

🤖 Generated with Claude Code

https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza


Generated by Claude Code

…ork in progress)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
…ume the parse

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
…origin/main

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016yfqQh2dBgPAymYd7xipza
@github-actions github-actions Bot added the size/l label Sep 2, 2026
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/rest, touching 13 documentable anchor(s).

4 release-owned page(s) name something this change touched. These are read-only:

  • content/docs/releases/implementation-status.mdx (via RestServer (symbol, a top-level class))
  • content/docs/releases/v12.mdx (via RestServer (symbol, a top-level class))
  • content/docs/releases/v16.mdx (via RestServer (symbol, a top-level class))
  • content/docs/releases/v17.mdx (via normalizeConfig (symbol, a method of class RestServer))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 1 anchor(s) matched too much of the corpus to be a work list: snake_case (literal, 45 pages)
  • 5 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 13 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 7da4cc2ba75d8a3a959a83a4a796ac7775bfb50bpackageMentionDocs.

Which tree this was computed on

This run read content/docs from c2f44b5c755a046ab02c5afb75bfd81cf3556a69 — the merge of head 02fd466d5cc024c162723015f73fff695ccfdede into base 7da4cc2ba75d8a3a959a83a4a796ac7775bfb50b, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin c2f44b5c755a046ab02c5afb75bfd81cf3556a69 && git checkout c2f44b5c755a046ab02c5afb75bfd81cf3556a69
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 7da4cc2ba75d8a3a959a83a4a796ac7775bfb50b 02fd466d5cc024c162723015f73fff695ccfdede && git checkout -B drift-repro 7da4cc2ba75d8a3a959a83a4a796ac7775bfb50b && git merge --no-ff 02fd466d5cc024c162723015f73fff695ccfdede

node scripts/docs-audit/affected-docs.mjs --json 7da4cc2ba75d8a3a959a83a4a796ac7775bfb50b

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 7da4cc2ba75d8a3a959a83a4a796ac7775bfb50b → pass the list as
args.docs, on the commit named under Which tree this was computed on.

…n/main

`content/docs/permissions/system-context.mdx` is a generated artifact anchored
by source line numbers. `origin/main` added 24 lines to
`packages/rest/src/rest-server.ts` and moved anchors in `objectql/src/engine.ts`,
`plugin-auth`, `metadata-protocol` and `objectql/src/registry.ts`, so the plain
merge left the page stale: `check:system-context-census` reported 16 problems
over 145 anchors and 109 census sites (8 `site-without-a-row`, 6
`anchor-is-not-a-read-site`, 2 `ledger-row-unused` — all pure line rot).

`pnpm gen:system-context-census` re-anchored 10 citations; the gate now reads
`OK -- 109 elevation read sites in 20 packages across 45 files, all anchored;
145 anchors resolve, 27 declared non-read`. This is the regeneration half of
`scripts/pm/os-regen-merge.sh` (its step 4), discharging the deferral the
pre-commit hook recorded for the merge commit. No source, test or changeset
content is touched.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WXyGTWPbbreqXow7Z2pZCk
…t review named (#11984)

The isolated contract reviewer (comment 5507350669 §4) and the director's
reconciliation adopt as required prose three refusals the changeset left
under its generic "wrong type" bullet: an explicit `null` at a declared key
(the cast-era `??` chain read it as absent and defaulted it; zod's
`.default()` fills `undefined` only), a `crud.patterns` entry missing its
required `path`, and a sub-object that is not an object at all.

Every bullet is a measurement, not a transcription: a throwaway driver
constructed the REAL `RestServer` with each shape and the refusal text quoted
here is what it printed, with an empty config as the discriminating control
(the driver is not committed). The partial-`operations` bullet and the
Migration sentence now cite #14365, so the later `z.partialRecord` widening is
traceable from the refusal it reverses.

Changeset prose only — no source, test or other file changed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WXyGTWPbbreqXow7Z2pZCk
@hotlong
hotlong marked this pull request as ready for review September 2, 2026 11:54

hotlong commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Landing provenance — director seat (summon #9, session session_01WXyGTWPbbreqXow7Z2pZCk): contract review PASS at 4fe663ec90 (#11984 comment 5507238605, director; 5507350669, the cli seat's isolated at-tier reviewer — same head, additive conditions), reconciled in 5508710311. The two later commits are non-executable: a pure machine merge of origin/main plus one regenerated census page (regen round 5507700315; merge tree reproduced byte-for-byte by an independent re-merge), and one changeset-prose commit enumerating the three newly-refused shapes the isolated reviewer named, each driven against the real RestServer before it was written (prose round 5508894942). Final head 02fd466d5: every one of the 37 check runs success or skipped; check-governed-merges --test on the final four-file list → NOT governed; merge-tree against origin/main (53d368921) clean; needs:contract-review absent on both carriers (stripped at clearance, read back). Under the 2026-08-31 ruling (清标即落地): PR flipped ready and auto-merge armed (SQUASH requested) in this stroke; followed to MERGED by this seat, then the card's pm:dispatched is stripped in the same window and the cli seat runs its post-merge close-out as it offered.


Generated by Claude Code

@hotlong
hotlong enabled auto-merge September 2, 2026 11:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

3 participants