docs(spec): RestServerConfig's crud / metadata / batch keys are embedder-only, and the schema now says so - #16775
Conversation
…e schema and the ledger WIP: docblocks, liveness reachability rows and QA checklist closure. Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x Co-authored-by: Claude <noreply@anthropic.com>
…butor rule off the published page Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): ⛔ 1 release-owned page(s) name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 130 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 ffc743e66e2830253554713cc4672d0c92d52617 && git checkout ffc743e66e2830253554713cc4672d0c92d52617
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin dd2184ae255dde66b66a042da020dcc5e54fa478 fcbbbebfe2a11e23771ee6f9d12625f578fb5b8c && git checkout -B drift-repro dd2184ae255dde66b66a042da020dcc5e54fa478 && git merge --no-ff fcbbbebfe2a11e23771ee6f9d12625f578fb5b8c
node scripts/docs-audit/affected-docs.mjs --json dd2184ae255dde66b66a042da020dcc5e54fa478
|
Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x Co-authored-by: Claude <noreply@anthropic.com>
…achability prose Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x Co-authored-by: Claude <noreply@anthropic.com>
…hecklist prose Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x Co-authored-by: Claude <noreply@anthropic.com>
…st-server-config-embedder-only
ACCEPT — merged head
|
| sibling | check | result |
|---|---|---|
| #16784 (#15080) | packages/spec/liveness/action.json on the branch |
operation → live, patch → live, authorWarn absent on both (control: 40 props total) |
| #16784 | same file, branch vs origin/main |
byte-identical |
| #16792 (#14977) | docs/audits/…strictness-ledger.md, branch vs origin/main |
byte-identical |
Both siblings landed at 06:07Z, both survived. os-regen-merge.sh chose per file and said so — keeping the branch's bytes of rest-server.mdx (branch changed it, main did not) and taking main's side of action.json and the generated state-counts.md. ⛔ Nothing hand-edited to reconcile, which is the rule that makes the result trustworthy.
⭐ The dev was already using the script before the seat's addendum arrived — the addendum named a sequence it had chosen on its own.
The stale-tree deviation is retired by measurement, not by assertion
Last round's declared risk was that the derivation had been taken on a tree 12+ commits behind, and that the merge might add gate families. Answered directly: the derivation on the merged head is 94 families, and diff against the stale-head derivation is EMPTY. The merge added none, and the STALE TREE warning is gone. ⇒ ⛔ That is how a declared deviation should close — with the measurement that resolves it, not with silence.
Also correctly held: no regeneration was owed after the merge, and that is measured rather than assumed — check:generated reports "All 15 generated artifacts are up to date" on the merged tree. state-counts.md did not need recomputing because the three ledger diffs are note / _note prose only; no status moved, so #16784's counts remain correct over the merged set.
Re-run on the merged head, not carried forward: 94 derived / 94 run / 93 green; test → 465 files, 12955 tests (three more than round 1's 12952 — the sibling work the merge brought in); typecheck exit 0; the full ratchet family green. check:dual-build-cjs-loads stays NOT MEASURED with its verbatim PREREQUISITE NOT MET text — ⛔ never a pass.
Deviation accepted
5 commits behind origin/main at report time, not re-merged. Accepted: the three collision-risk files are byte-identical to current main, mergeable_state is clean, the merge queue rebuilds against main at land time, and a second merge would have invalidated the gate run this report cites. ⇒ Declared rather than silently carried, which is the part that matters.
Actions
ACCEPT → ready → enqueue. Not governed (Governed Surface Queue Guard green). Fixes #15543 closes the card on merge; ⛔ this seat does not merge by hand and does not bypass the queue.
The packages/rest finding this PR correctly refused to fix in scope is filed as #16801, with the seat's correction to its routing recorded there: packages/rest is domain:cli; there is no domain:rest lane.
https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x
Generated by Claude Code
Fixes #15543
Executes the director-seat ruling of 2026-09-07 (summon #17, decision batch #2, comment 5572155531, maintainer verbatim 「同意」), option 2: the
crud/metadata/batchkeys ofRestServerConfigstay and keep their runtime reads, their docblocks stop describing a deployment posture nobody can author from the CLI, and the ADR-0049 question "is this key reachable?" gets its written answer.No behaviour changes. No schema shape, default, bound or refusal moves —
Clause-②: no, and the machine-checkable proof is below.What the re-derivation found (the ruling cited two sentences; one does not exist)
The dispatch asked me to re-derive the actual population rather than trust the card's two citations. Measured on
origin/main8ccf7a1df:batch.maxBatchSize's docblock calls the cap "deployment policy"maxBatchSizeoccurs 3 times, so the zero is a real zero and not a broken search. The docblock at its declaration reads onlyMaximum batch size.metadata.maskObjectFields:false"opts this server out":328. Repaired.api.enableSearch:100calls it a "Deployment-wide switch" and thedescribe()at:112a "deployment-wide search opt-out".os servethreads onlyenableProjectScoping/projectResolution, so this key is not reachable from a CLI-started deployment either. Repaired.Real count: 1 inside the ruling's named key set; 2 keys / 3 sentence-carriers across every live key of the file.
Four further hits are measured boundary, not counted and not edited, with the reason stated so the next sweep does not re-litigate them:
api.requireAuth(:132,:142) andbatch.defaultAtomic(:465,:469,:476) areretiredKey()tombstones describing keys that are now refused;requireAuth's text denies a deployment posture rather than asserting one.RouteGenerationConfigSchema(:504) androutes.overrides(:535) callcrud.dataPrefix"deployment-wide" in the scope-of-effect sense (one setting for the whole server, contrasted with the per-object overrides being retired) — that is true, and it is not a reachability claim.:535is additionally author-facingretiredKey()rejection copy, which I do not move.The bounded in-place fix, declared
api.enableSearchis outside the ruling's named key set (crud/metadata/batch), and I fixed it anyway. Declaring it so the reviewing seat can strip it if it disagrees:enableSearchIS deployment-reachable. Fixing it makes the change self-consistent rather than widening it.enableSearchkeeps its type, itsdefault(true)and its runtime read.What changed
packages/spec/src/api/rest-server.zod.tsWHO CAN WRITE THIS CONFIGsection on the module docblock. This is the carrier that matters for an operator:build-docs.tsrenders the module docblock ontocontent/docs/references/api/rest-server.mdx, while the per-schema docblocks are not rendered there at all. It names both programmatic doors, both boot paths and their measured arguments.Reachability: EMBEDDER-ONLYon thecrud,metadataandbatchsub-schema docblocks (the ruling's "three docblocks").crud/metadata/batchmembers ofRestServerConfigSchemasay it in theirdescribe()too, so the fact survives into the generated reference table an operator actually reads, not only into the TS source.metadata.maskObjectFieldsandapi.enableSearchrepaired as above.packages/spec/liveness/{crud,metadata,batch}_endpoints.json— the ADR-0049 answer in writing.reachabilityfield and no convention for one, so per the ledger's own conventions the answer went into prose: aREACHABILITYsentence on each of the 18liverows (6 crud + 7 metadata + 5 batch), and the measurement once in each file's_note. Precedent:batch_endpoints.json's_notealready carries its AUTHOR-WARN CHANNEL paragraph, which is a reachability statement made once per file.statusandverifiedAtare deliberately untouched.liveanswers who READS a key; reachability answers who can SET it. Adding the second re-verified no call graph, and bumpingverifiedAtwould silently reset the liveness staleness clock on a claim nobody re-measured. Each row and each_notesays so.liveness/api.jsonis theApiEndpointmetadata type, notRestApiConfigSchema— theapisub-object ofRestServerConfighas no ledger file, which is why the ruling's three-file key set is exactly the set with live ledger rows.QA checklist closure — so the
knownGapsthis card is cited in do not go stale.docs/qa/platform-checklist/FOLLOW-UPS.md§10b E2's handling column re-pointed at the ruling, including the corrected citation above.api-backend.rest-{crud,batch,metadata}-config-contractitems'fixtures.knownGapseach gain the ruling: the harness scoring they describe is the permanent method, not a workaround pending a boot-path flag, and the reachability no longer has to be re-derived from the boot paths per run.automated.refatrest-config-mount-table.pin.test.ts, [finding] The MOUNT half of everyRestServerConfigswitch is unpinned — the tests assert what a switch normalizes to, never that the route leaves the table #15544). Different sentence, different card, still owed.Verified before editing (the dispatch flagged these as unverified): §10b E2 exists verbatim at
FOLLOW-UPS.md:566/:571, and all three checklist item ids exist with the spellings the ruling names. Nothing drifted.Verification
Gate set derived, never guessed —
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, re-derived after the changeset landed, then reconciled with--ran, which surfaced 24 further families the regenerated.mdxpulled in. All at9ffbde5b1, clean tree.check:dual-build-cjs-loads(exit 3,PREREQUISITE NOT MET). It reads emitted CJS/ESM bytes from all 83 packages'dist/; that is a repo-wide build, which is CI's run, not a local one. My diff adds no export, entry or tsup change, so it cannot move this gate. ⛔ Recorded as not measured, never as a pass.dist(check:api-surface,check:dual-source-exports,check:entry-nameability,check:exported-any,check:browser-reachable-entries,check:doc-formula-expressions,check:skill-examples). Those are NOT MEASURED, not failures; all seven were re-run green after building the packages each named.The tier boundary, mechanically:
pnpm --filter @objectstack/spec check:generatedreports 15 of 15 artifacts current, withcheck:authorable-surfaceandcheck:api-surfaceboth green and requiring no regeneration. Onlycheck:docswent stale, and only from thedescribe()edits. An accept-set or export-surface move could not produce that result — this is the evidence forClause-②: no, not an assertion of it.Tests —
pnpm --filter @objectstack/spec test:465 passed (465)test files,12952 passed (12952)tests, wrapper verdictcommand-exit 0.pnpm --filter @objectstack/spec typecheck:command-exit 0, test layer compiles undertsconfig.test.json. Both throughscripts/pm/os-verify-lock.sh; verdicts read from itsVERDICT command-exitline, never a bare$?.Dependency-closure build (step ①) is an empty run by construction:
packages/spechas no workspace dependencies.Lint — a declared narrowing, with its three pieces of evidence, taken at final
9ffbde5b1. The repo-widepnpm lintscan is CI's.packages/spec/src/api/rest-server.zod.ts. The other seven are reported by eslint itself asFile ignored because no matching configuration was supplied; they are outside the lint population by its reckoning.--format json: 8 entries returned, 1 in population, 0 errors, 0 in-population warnings, exit 0.eslint.config.mjsstates in its own words (line 328) that this repo "never enables type-aware linting (noparserOptions.project, no typed@typescript-eslintrules) for ANY file", measured there with a positive control. So no edit of mine can move an untouched file's verdict.All three hold, so the narrowing is a measurement rather than a skipped run.
Bytes —
pnpm check:nul-bytesgreen, plus an independent scan of every changed file for control characters: zero hits.验收备注
Observations from the sweep. None is filed, and each names who would meet it.
serve.ts:4019reachescreateRestApiPluginthrough anas anycast, so even the two keys it does pass are unchecked at that seam. Named by triage as incidental. Not a defect this PR can reach without movingpackages/cli, and the ruling refuses option 1. Successor: whoever takes option 1, should a request ever arrive.noted, not filed.RestApiConfigSchema's other nine live keys (version,basePath,enableCrud,enableMetadata,enableUi,enableBatch,enableDiscovery,enableOpenApi, plus thedocumentation/responseFormatblocks) are embedder-only on the same measurement, and none carries a false posture sentence — theirdescribe()strings are plain capability statements. The file-header paragraph covers them; I did not append a per-key line to nine honest docblocks. Successor: the same option-1 card.noted, not filed.apihas no liveness ledger file, so its keys carry no reachability row and no liveness verdict at all. Seeding one is a ledger-governance decision (SPEC_ONLY_SCHEMASincheck-liveness.mts), not a rider here. Successor: none identified — recorded so the next reader of these three files does not readapi's absence as a clean bill.noted, not filed.Governance
No governed surface is touched — the diff is
.changeset/,content/docs/references/,docs/qa/,packages/spec/liveness/andpackages/spec/src/;docs/adr/**,.claude/**,skills/**,AGENTS.mdandCLAUDE.mdare all absent. Left as a draft for the seat, per the dispatch.Generated by Claude Code