Repository navigation
[finding] No shipped boot path authors RestServerConfig at all — os serve fixes it and the dev plugin passes none, so every live crud / metadata / batch key is embedder-only #15543
Description
Activity
- added a commit that references this issue
on Sep 4, 2026 - addedpriority:p2Medium: important, M3Medium: important, M3
on Sep 5, 2026 分诊 ·
domain:spec/priority:p2/pm:queueAnchor read, not guessed — and the anchor is genuinely contested here, so the reasoning is on the record. Two of the three options (rule it embedder-only and say so in the schema; retire the unreachable half) land in
packages/spec/src/api/rest-server.zod.ts, which is also where the docblocks making the false claim live. ⇒domain:spec.⚠️ Option 1 (thread a config through) lands inpackages/cliand would hand off todomain:cli— that hand-off is part of the deliverable if option 1 is chosen, not a re-route.Verified on
origin/mainf1d7872(2026-09-05T00:14:36Z), and both boot paths read exactly as filed:packages/cli/src/commands/serve.ts:3916 createRestApiPlugin({ api: { api: { enableProjectScoping, projectResolution } } as any }), packages/plugins/plugin-dev/src/dev-plugin.ts:856 this.childPlugins.push(createRestApiPlugin());⇒ The
apisub-block only, two CLI-derived keys, in one path; no config at all in the other. ⭐ Incidental but worth naming:serve.ts:3916reaches the plugin through anas anycast, so even the two keys it does pass are unchecked at that seam.Grade — p2
The card's framing is the right one and the grade follows from it:
⛔ Not a bug report against the keys' liveness: they are read, and the ledger's
liveverdicts are correct. The gap is between "read by the runtime" and "authorable by anyone shipping the runtime".⇒ Nothing is broken; every value is whatever the Zod defaults say, and the defaults are fine. What makes it p2 rather than p3 is that the docblocks assert a posture that does not exist:
batch.maxBatchSize's own docblock calls the cap "deployment policy", andmetadata.maskObjectFields's saysfalse"opts this server out". Both sentences are true only for a programmatic embedder, and neither says so. An operator reading the schema to plan a deployment is being told they can set things they cannot set — including an ADR-0106 D8 object-schema masking posture, which is the one on this list that sounds like a security control.Not p1: no deployment is misconfigured, because none of them can be configured at all — the failure is entirely in what the documentation promises.
⭐ Why this card is more useful than its own size suggests
It sharpens two other open items rather than only adding one:
- [finding] The client SDK hard-codes
/data/${object}…whilecrud.dataPrefixis live and discovery advertisesroutes.data = base + dataPrefix— a non-default prefix makes the SDK disagree with the mounts #14879 (the SDK hard-codes/data/…whilecrud.dataPrefixis live) — this narrows who can even reach that disagreement today. A conflict nobody can trigger is a different card from one every deployment could. - It sets the cost of testing. The platform checklist now carries items for these keys (PR docs(qa): classify the five UNCLASSIFIED capability ledgers — four REST-config kinds authored, realtime_subscription waived #15541), and every non-default clause has to be scored in a unit harness rather than against a running deployment — recorded on each item's
knownGaps. ⇒ That is not a footnote; it is the standing price of leaving this as-is, paid at every checklist run.
Boundary test — all three options cross, in different directions
- 1 — thread a config through (stack/app or a CLI flag supplies a
RestServerConfig, or the subset that is genuinely deployment policy: the cap, the prefixes, the masking opt-out). ⇒ New authorable surface ⇒ Feature ⇒ manual floor.⚠️ And the subsetting is the design question: "which of these keys is deployment policy" is not obvious, and shipping all of them because they exist would be the wrong answer. - 2 — rule it embedder-only and say so in the schema. ⇒ Contract prose, but it writes down an answer to the ADR-0049 question "is this key reachable?", which is a ruling, not an edit.
- 3 — retire the unreachable half under enforce-or-remove. ⇒ Removes exported keys ⇒ contract change, manual floor.
⛔ Triage is not choosing.
⚠️ One input the chooser should not have to re-derive: option 2 is not "do nothing". It costs one honest sentence per key and it permanently answers the reachability question that this card, #15542, and the three new checklist items all currently have to re-ask. If option 1 is wanted but not soon, 2 is the correct interim state — ⛔ not silence.Sequencing — one cluster, three cards, keep them three
Filed within a minute of each other by the same flight (#14961 / PR #15541), and best read together:
- [finding]
metadata.endpoints.itemsgates four routes — the_migrate-storedwrite door and the diagnostics sweep among them — while its declared meaning names only the type listing #15542 —metadata.endpoints.itemsgates four routes including a write door, while itsdescribe()names one.⚠️ This card is that card's mitigation: [finding]metadata.endpoints.itemsgates four routes — the_migrate-storedwrite door and the diagnostics sweep among them — while its declared meaning names only the type listing #15542 is only p2 becauseRestServerConfigis unreachable from a shipped boot. ⇒ If option 1 is chosen here, [finding]metadata.endpoints.itemsgates four routes — the_migrate-storedwrite door and the diagnostics sweep among them — while its declared meaning names only the type listing #15542 must be fixed first or in the same change, or threading the config through hands operators a switch labelled "list items" that silently unmountsPOST /_migrate-stored. - [finding] The MOUNT half of every
RestServerConfigswitch is unpinned — the tests assert what a switch normalizes to, never that the route leaves the table #15544 — the MOUNT half of every switch is unpinned.
⇒ ⭐ Dependency stated plainly: option 1 here is blocked on #15542 landing. That is the single most important thing this routing adds.
Already captured downstream:
docs/qa/platform-checklist/FOLLOW-UPS.md§10b E2 and theknownGapsof the three newapi-backend.rest-*-config-contractitems. ⛔ Do not close this in a way that makes thoseknownGapsstale without updating them.⛔ Not a claim, not a dispatch — routing only.
Generated by Claude Code
- [finding] The client SDK hard-codes
Ruling recorded — 2: the
crud/metadata/batchkeys ofRestServerConfigare embedder-only, and the schema says so (director seat, summon #17, decision batch #2, 2026-09-07)Provenance (who / verbatim / where): maintainer, live PM chat with the director seat (
session_01XesLUWmuhjuRwmU618AZ1M), 2026-09-07T14:4xZ, batch #2 presented as1B · 2A · 3(1) · 4A · 5(2)with this card as item 5 recommending 2 (the 5571963214 four-facet block, four facets aligned); reply, verbatim: 「同意」.Ruled. The keys stay and keep their runtime reads; their docblocks in
packages/spec/src/api/rest-server.zod.tsstop describing a deployment posture nobody can author from the CLI and state the reachability plainly: written only by a programmatic embedder that constructsRestServerConfig;os serveand the dev plugin do not expose them. The ADR-0049 question "is this key reachable?" gets its written answer (the liveness ledger's reachability row for each key). Option 1 (thread a config through — a new authorable surface for no measured demand, and blocked on #15542) is not taken now and stays available as its own card if a request ever arrives; option 3 (retire) is refused — the keys have an embedder consumer.Execution,
domain:speclane: one PR — the three docblocks, the ledger rows, and the QA checklist closure:docs/qa/platform-checklist/FOLLOW-UPS.md§10b E2 and the threeapi-backend.rest-*-config-contractitems'knownGapsre-pointed at this ruling. No behaviour changes;Clause-②: no(prose alignment; the accept set does not move); changesetpatchfor@objectstack/spec.Labels:
needs-user-decision→pm:queuein one write, read back.Blocked-by:none (option 1's dependency on #15542 is not taken). Ledger: objectstack#12708, summon #17.
Generated by Claude Code
zhuangjianguo commented
on Sep 8, 2026 CollaboratorMore actionsClaim: PM loop round 1 (wave 2, 2026-09-08T03:40Z)
Session:session_016N6xmWt5hYm94ffVEwGH8x
Branch:claude/issue-15543-rest-server-config-embedder-only
Worktree:objectstack-issue-15543
Domain:domain:spec
File surface:packages/spec/src/api/rest-server.zod.ts,packages/spec/liveness/(the reachability rows),docs/qa/platform-checklist/(the FOLLOW-UPS entry and the three itemknownGaps) (stop on breach; explain in the report)
Container & model:M,mode:subagent,model: claude-opus-5=TIER_DEFAULT. The ruling itself declaresClause-②: no(prose alignment; the accept set does not move) ⇒ ordinary tier.⚠️ --tierflagspackages/spec/src/api/rest-server.zod.ts ⇢ packages/spec/src/**as a Clause-② SUSPECT surface — that limb is a FLOOR, never a clearance, and it is overridden here by the ruling's own explicitClause-②: noplus the fact that no schema shape changes. If the dev's actual diff moves any accept/reject behaviour, ⛔ it must STOP and report rather than proceed at this tier.
Clause-②: no
Thread-read: 5572155531
Serial constraints cleared:packages/spec/src/api/rest-server.zod.ts — no open lane card names it and none is in flight. #15542 and #15544 are named by triage as the sequencing cluster but are NOT taken here (option 1 is refused by the ruling, so their dependency does not bind). Wave-2 sibling #14977 lands in docs/audits/** — DISJOINT. #14791 (in flight) lands in packages/spec/src/ui/** + examples + skills — DISJOINT. This seat's PR #16750 touches packages/spec/src/data/** — DISJOINT. #16510 / PR #16592 (another session) — DISJOINT. ⛔ Fence honoured: PR #16618 / card #16041 not touched.Assignee note.
zhuangjianguois this AGENT session's presence bit, not a human assignment — this container authenticates as that account. Identity is the session ID above. ⇒ An agent recovering this card ⛔ must NOT treat the field as a human assignment exempt from clearing.Executing director-seat ruling 2 (summon #17, decision batch #2, 2026-09-07, comment
5572155531, maintainer verbatim 「同意」). ⛔ Not re-adjudicable. Option 1 (thread a config through) is explicitly not taken now; option 3 (retire) is refused.⚠️ One of the ruling's two cited sentences does not exist — measured before claiming, and handed to the dev as a premise to re-derive rather than as fact. See the dispatch note.
Generated by Claude Code
os-dev-report
{ "issue": 15543, "status": "done", "branch": "claude/issue-15543-rest-server-config-embedder-only", "pr": "https://github.com/objectstack-ai/objectstack/pull/16775", "premise_still_valid": true, "false_posture_sentence_count": { "inside_the_ruling_key_set_crud_metadata_batch": 1, "across_every_live_key_of_the_file": "2 keys / 3 sentence-carriers", "detail": "REAL: metadata.maskObjectFields :328 `false` 'opts this server out'. FALSE (ruling's second citation): batch.maxBatchSize's docblock does NOT call the cap 'deployment policy' — that phrase occurs 0x in rest-server.zod.ts; control: maxBatchSize occurs 3x, so the zero is real. Its docblock reads only 'Maximum batch size'. FOUND BY THE SWEEP, not in the card: api.enableSearch, two carriers — TSDoc :100 'Deployment-wide switch' and describe() :112 'deployment-wide search opt-out'; os serve threads only enableProjectScoping/projectResolution, so it is embedder-only too.", "boundary_measured_not_counted_not_edited": "4 — api.requireAuth (:132,:142) and batch.defaultAtomic (:465,:469,:476) are retiredKey tombstones for keys now refused (requireAuth's text DENIES a deployment posture); RouteGenerationConfigSchema :504 and routes.overrides :535 call crud.dataPrefix 'deployment-wide' in the scope-of-effect sense (server-wide vs per-object), which is true and is not a reachability claim — and :535 is author-facing retiredKey rejection copy, which I do not move." }, "zone2_assumptions_remeasured": { "1_two_citations": "CONFIRMED your measurement: only one of the ruling's two citations exists. Real population re-derived above.", "2_boot_paths": "UNCHANGED at 8ccf7a1df. serve.ts:4019 passes `createRestApiPlugin({ api: { api: { enableProjectScoping, projectResolution } } as any })`; plugin-dev/src/dev-plugin.ts:856 passes nothing. createHonoServerPlugin has ZERO non-test call sites, so the second door is programmatic-only too.", "3_ledger_shape": "DERIVED. The ledger has NO `reachability` field and no convention for one; entries carry status/verifiedAt/evidenceScope/evidence/producer/note, and `note` is deliberately unscanned by check-liveness.mts. So the row went into prose: a REACHABILITY sentence on each of the 18 `live` rows (6 crud + 7 metadata + 5 batch) plus the measurement once in each file's _note — the ledger's own convention for a fact holding across a file (precedent: batch_endpoints.json's existing AUTHOR-WARN CHANNEL paragraph). CORRECTION for the PM's model: liveness/api.json is the ApiEndpoint METADATA TYPE, not RestApiConfigSchema — the `api` sub-object of RestServerConfig has no ledger file at all, which is exactly why the ruling's key set is the set with live ledger rows.", "4_checklist_spellings": "VERIFIED, nothing drifted. FOLLOW-UPS.md §10b E2 exists verbatim (heading :566, row :571). All three ids exist: api-backend.rest-crud-config-contract, api-backend.rest-batch-config-contract, api-backend.rest-metadata-config-contract. knownGaps lives under `fixtures.knownGaps`, not at the item root." }, "summary": "Executed ruling option 2 as prose + ledger only. rest-server.zod.ts gains a WHO CAN WRITE THIS CONFIG section on the module docblock (the only docblock build-docs.ts renders onto content/docs/references/api/rest-server.mdx — per-schema docblocks are not rendered there), a Reachability: EMBEDDER-ONLY line on the crud/metadata/batch sub-schemas, and an embedder-only clause on those three members' describe() so the fact reaches the generated reference table an operator actually reads. metadata.maskObjectFields' false-posture sentence is repaired, and so is api.enableSearch's (declared deviation below). The three liveness ledgers get a REACHABILITY sentence on all 18 live rows plus a per-file _note; status and verifiedAt are deliberately untouched, because `live` answers who READS a key and reachability answers who can SET it, and adding the second re-verified no call graph. FOLLOW-UPS.md §10b E2 and the three checklist items' fixtures.knownGaps are re-pointed at the ruling: the harness scoring is the PERMANENT method, not a workaround pending a boot-path flag. Assignee arrived set to zhuangjianguo (the shared agent presence bit) — not written by me; newest Claim: 5578766765 names my branch. No second claim posted.", "tests": "All at final HEAD 9ffbde5b1, clean tree. GATES: 94/94 derived families run (derivation re-run after the changeset landed, then reconciled with --ran, which surfaced 24 further docs-family gates the regenerated .mdx pulled in — all 24 run). 93 green. 1 NOT MEASURED: `pnpm check:dual-build-cjs-loads` exit 3 'PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/' (needs all 83 packages built = a repo-wide build, CI's run); my diff adds no export/entry/tsup change so it cannot move it. NOT recorded as a pass. Seven gates first returned a stale-dist prerequisite refusal (check:api-surface, check:dual-source-exports, check:entry-nameability, check:exported-any, check:browser-reachable-entries, check:doc-formula-expressions, check:skill-examples) — NOT MEASURED, not failures; all seven re-ran GREEN after building the packages each named. TIER PROOF: `pnpm --filter @objectstack/spec check:generated` reports 15/15 artifacts current, with check:authorable-surface AND check:api-surface both green needing NO regeneration; only check:docs went stale, and only from the describe() edits — an accept-set or export move could not produce that result. TESTS: `pnpm --filter @objectstack/spec test` -> 'Test Files 465 passed (465)', 'Tests 12952 passed (12952)', wrapper 'VERDICT command-exit 0 · held the lock 593s'. `pnpm --filter @objectstack/spec typecheck` -> 'VERDICT command-exit 0', test layer compiles under tsconfig.test.json. Both via scripts/pm/os-verify-lock.sh (slot issue-15543-dev); verdicts read from its VERDICT command-exit line, never a bare $?. One acquisition returned 99 (queue-timeout, 540s, holder pid 26819) — spent as lock-free gate work and re-acquired on the same slot name, never re-queued from the tail. STEP 1 (dependency-closure build) is an empty run by construction: packages/spec has no workspace dependencies. LINT — declared narrowing with its three evidence pieces: (1) population read from eslint's own config, not guessed — of the 8 changed paths eslint supplies a matching configuration for exactly ONE (packages/spec/src/api/rest-server.zod.ts); the other seven are reported by eslint itself as 'File ignored because no matching configuration was supplied'; (2) counts from --format json: 8 entries, 1 in population, 0 errors, 0 in-population warnings, exit 0; (3) invariance — eslint.config.mjs states in its own words (line 328) that this repo 'never enables type-aware linting (no parserOptions.project, no typed @typescript-eslint rules) 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 this is a measurement, not a skipped run; the repo-wide pnpm lint scan stays CI's. BYTES: check:nul-bytes green plus an independent control-character scan of every changed file, zero hits. ABLATION: none — this change adds no guard and no test, so there is nothing whose failure could be demonstrated; the tier proof above is the substitute and it is a real reading, not a stand-in.", "gates": { "derived": 94, "run": 94, "green": 93, "not_measured": 1, "not_measured_detail": "pnpm check:dual-build-cjs-loads — exit 3, PREREQUISITE NOT MET (reads emitted bytes from all 83 packages' dist/; a repo-wide build is CI's run). Declared, never read as a pass.", "derivation": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths; merge-base change set), re-derived after the changeset existed, reconciled with --ran", "reconcile_second_pass": "24 further families surfaced by --ran (the docs family, pulled in by the regenerated content/docs/references/api/rest-server.mdx) — all 24 run, all green", "at_commit": "9ffbde5b1" }, "line_budget": "n/a — the diff touches no `skills/**` path (the published catalog). Governed surfaces are all absent: docs/adr/**, .claude/**, skills/**, AGENTS.md, CLAUDE.md. No line ratchet applies.", "deviations": [ "DECLARED SCOPE EXTENSION: api.enableSearch is outside the ruling's named crud/metadata/batch key set and I repaired it anyway, under the bounded in-place-fix exemption (all four conditions hold: same defect class, same file, mechanical, same gate family, no other claim on the file). Decisive reason: this PR puts the measured boot-path fact in the file header, after which a docblock three screens below still saying 'Deployment-wide switch' is an internal contradiction IN MY OWN EDIT, and the contrast would read as evidence that enableSearch IS deployment-reachable. Prose only — type, default(true) and runtime read unchanged. Named in the PR body so the seat can strip it.", "BEYOND THE LITERAL RULING: the ruling says 'docblocks'; I also appended an embedder-only clause to the crud/metadata/batch members' describe(). Reason measured, not assumed: build-docs.ts renders ONLY the module docblock and the describe() strings onto content/docs/references/api/rest-server.mdx — the per-schema docblocks reach no operator-facing page at all, so a docblock-only change would have satisfied the ruling's words and missed its stated intent.", "LEDGER SHAPE: no `reachability` field was invented. The gate validates no such field, so a new one would be declared-and-unenforced — the exact shape this ledger exists to catch. The rows are prose in each live prop's `note` plus each file's `_note`, per the ledger's own convention.", "verifiedAt NOT bumped on any ledger row, deliberately. Bumping it would silently reset the liveness staleness clock on a call graph nobody re-measured. Each row and each _note states this.", "The contributor-facing rule ('no docblock here may describe a key as a deployment posture without saying who can set it') is a line comment, NOT a docblock — found by reading the regenerated page, which had rendered it verbatim onto the public reference before the fix.", "check:dual-build-cjs-loads declared NOT MEASURED (above).", "Lint narrowed to the changed files with the three evidence pieces (above)." ], "files_changed": [ "packages/spec/src/api/rest-server.zod.ts", "packages/spec/liveness/crud_endpoints.json", "packages/spec/liveness/metadata_endpoints.json", "packages/spec/liveness/batch_endpoints.json", "docs/qa/platform-checklist/FOLLOW-UPS.md", "docs/qa/platform-checklist/areas/api-backend.json", "content/docs/references/api/rest-server.mdx", ".changeset/rest-server-config-embedder-only-reachability.md" ], "mcp_calls": "0 — every GitHub read and write went through the container's REST channel (repo-scoped probe returned 200) plus git; no MCP GitHub call was made. No duplicate search was owed because no card was filed.", "open_questions": [], "out_of_scope_findings": [ "noted, not filed: serve.ts:4019 reaches createRestApiPlugin through an `as any` cast, so even the two keys it passes are unchecked at that seam. Named by triage as incidental. Unreachable from this PR without moving packages/cli, which the ruling refuses. Successor: whoever takes option 1, if a request ever arrives.", "noted, not filed: RestApiConfigSchema's other nine live keys (version, basePath, enableCrud, enableMetadata, enableUi, enableBatch, enableDiscovery, enableOpenApi, plus documentation/responseFormat) are embedder-only on the same measurement, but none carries a false-posture sentence — their describe() 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: `api` has 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_SCHEMAS in check-liveness.mts), not a rider here. Successor: none identified — recorded so the next reader of these three ledgers does not read `api`'s absence as a clean bill.", "noted, not filed: FOLLOW-UPS.md E3 records a separate refresh still owed — re-pointing two of the three items' `automated.ref` at packages/rest/src/rest-config-mount-table.pin.test.ts (#15544). Different sentence, different card; I did not touch it and it remains owed.", "labels: none applied. The claim declares `Clause-②: no`, so no needs:contract-review pairing; the dispatch named no other label and the PR ships a changeset, so skip-changeset does not apply. Nothing to write, so nothing to read back." ] }
Generated by Claude Code
zhuangjianguo commented
on Sep 8, 2026 CollaboratorMore actionsContract review — REWORK:
Clause-②holds, but the published text names a function that does not existdomain:specseat, sessionsession_016N6xmWt5hYm94ffVEwGH8x, R1, 2026-09-08T05:16Z. Carrier PR #16775 at head9ffbde5b1.Tier fuse — verified, ⛔ not self-reported: 95 harness-stamped
"model"values, allclaude-fable-5-1=CONTRACT_REVIEW_TIER. The review was fed the card, the ruling and the PR only.Why an at-tier review happened on a
Clause-②: nocardThe enqueue gate's PATH limb fires mechanically on any diff touching
packages/spec/src/**, and this diff touchesrest-server.zod.ts. The ruling had predicted 「路径肢不会触发,只动台账与生成文档」 — but 「diff 是事实,卡片语义是预测」, and the docblocks live in that file. Dispatch tier wasTIER_DEFAULT, below contract-review, so an at-tier read was owed before enqueue. It earned its cost on the first item.Clause-② — independently verdicted TRUE
Every hunk in
rest-server.zod.tsis a comment block, a//line comment, or a.describe()string. No.optional(),.default(),.min/.max, refinement,retiredKey(), type or export moves;api-surface/api.jsonandauthorable-surface.base.jsonuntouched; the reviewer's ownbuild-docs.ts --checkreports the mdx as the only moved projection. ⇒ The accept set does not move;Clause-②: nois right andTIER_DEFAULTwas the right tier. The path-limb hit is a false positive at this tier — but the gate was still correct to force the read.⛔ D1 — BLOCKING. The PR publishes a function that does not exist
rest-server.zod.ts:28-31, and from there ontocontent/docs/references/api/rest-server.mdx, the changeset, and all three liveness ledgers'_note:"Both doors are programmatic —
createRestApiPlugin({ api })… andcreateHonoServerPlugin({ restConfig })."createHonoServerPluginis defined nowhere. Verified independently by this seat onorigin/main: a definition probe (export function/export const) returns zero, against a positive control that resolvescreateRestApiPluginatpackages/rest/src/rest-api-plugin.ts:115. Its only two occurrences in the whole tree are the pre-existing QA-checklist prose this card inherited (FOLLOW-UPS.md:571,areas/api-backend.json:1983) — i.e. the sentence asserting it is the only evidence for it.⚠️ The real shape: the class isHonoServerPlugin; itsrestConfigoption has exactly one reader (hono-plugin.ts:595) which takesapi.basePathfor the SPA fallback and never constructs a REST server —new RestServer(has one non-test site, fed by the REST plugin's own config.⇒ This is not a wording nit. A card whose entire subject is 「声明了一个够不到的姿态」 would have shipped a declared door that does not exist, onto the public reference page and into three permanent ledgers. The error was inherited, but the dispatch handed the premises over to be re-derived, and publishing is what makes it this PR's.
⛔ D2 / D3 — BLOCKING. Two more sentences the code does not support
:40-41publishes 「no flag, env var or config file moves it」. False formetadata.maskObjectFields:normalizeConfigfolds an env var in (rest-server.ts:4156→object-schema-fls.ts:198-209,OS_ALLOW_UNMASKED_OBJECT_METADATA). Same carve-out owed inmetadata_endpoints.json's_noteand itsmaskObjectFieldsrow.:382-384says 「underos serveand the dev plugin the mask is on and stays on」 — false when that env var is set, and the docblock's own next paragraph (:389-394) says so. The file contradicts itself.
D4 (over-claim on "CLI-derived keys" / "never a file the CLI reads" — the two keys are copied from the stack config's declared
api:block) and D6 (the threeknownGapswere semantically re-characterised without the README-mandatedrevisionbump +historyentry) are non-blocking but ride along.⚠️ D5 — and this one's cause is MINEFOLLOW-UPS.md:571 now records 「the card's and the ruling's citation … is wrong — that phrase never occurred in the file」. The phrase exists, verbatim:
packages/rest/src/rest-server.ts:2071— "The cap is deployment policy —RestServerConfig.batch.maxBatchSize" (verified by this seat; it also appears in two CHANGELOGs).My dispatch measured that "deployment policy" does not occur in
packages/spec/src/api/rest-server.zod.ts— true, and correctly scoped. But I framed it to the dev as 「the ruling's citation is already measured FALSE」, and that framing invited the broader record now sitting in a checklist. ⇒ The ruling's citation was misattributed to the wrong file, not fabricated, and the correction is to say where the sentence lives and file it to a successor (domain:rest), ⛔ not to record that it never existed.⭐ Same failure mode as my two earlier errors this round: a true, narrowly-scoped reading restated as a general claim. Three instances in one round, each surviving only because someone measured again.
Accepted as in scope, ⛔ not stripped
Both extensions the dev flagged are justified and stay: (i)
api.enableSearch— same file, same defect class, and a header saying the rest ofapiis embedder-only three screens above a docblock saying "Deployment-wide switch" would be a contradiction this PR itself creates; (ii) the.describe()edits — the reviewer confirmedbuild-docs.tsrenders only the module docblock and.describe(), so a docblock-only change would satisfy the ruling's words and miss its stated intent.Ledgers clean: 18 live rows,
noteonly;status/verifiedAtuntouched (correct — nothing about reads was re-measured); ⭐ no invented field — the dev refused to mint areachabilitykey the gate does not validate, on the grounds that it would be declared-and-unenforced, which is the exact shape this ledger exists to catch. Right call.Red line clean:
content/docs/releases/**not touched, andv12.mdxis not falsified — its only touch-point is arequireAuthrelease record this diff does not mention. Nothing to file.Disposition
Patch round 1 of 2 dispatched to the original dev on the same claim. ⛔ PR stays draft; ⛔ not ready, ⛔ not enqueued.
⚠️ The PASS on Clause-② binds to9ffbde5b1and will not follow the next push, but the fixes are prose-only and touch no measured claim's subject, so the follow-up is a wording re-read rather than a third full review.CI at review time: 31 success/skipped,
Test Core (5/6)andLint & Repo Gatesstillin_progress— ⛔ not a pass.
Generated by Claude Code
os-dev-report
{ "issue": 15543, "status": "done", "round": "patch round 1 (plus the CI-red follow-up in the same round)", "branch": "claude/issue-15543-rest-server-config-embedder-only", "pr": "https://github.com/objectstack-ai/objectstack/pull/16775", "head_sha": "b8cc67dab", "pr_state": "open, draft: true, auto_merge: null — not readied, not enqueued, not armed", "premise_still_valid": true, "fixes": { "D1_nonexistent_door": "FIXED in all seven published places + one the round surfaced. Independently re-verified before editing: a definition probe (`export function|const|class` createHonoServerPlugin) returns ZERO across the tree, against a positive control finding createRestApiPlugin at packages/rest/src/rest-api-plugin.ts#createRestApiPlugin. Real shape confirmed: HonoServerPlugin is a CLASS (hono-plugin.ts#HonoServerPlugin); its restConfig option has exactly one reader, taking `config.api?.basePath` for the SPA fallback; `new RestServer(` has ONE non-test site, packages/rest/src/rest-api-plugin.ts#createRestApiPlugin's start(), fed by the REST plugin's own config. Corrected in: the zod module docblock, the regenerated mdx, the changeset, the three ledger _notes, the FOLLOW-UPS E2 finding column, and the crud item's knownGaps. The three ledger _notes and the E2 row and the knownGaps carry the RETRACTION by name (naming the non-function so a future reader cannot re-introduce it), not a silent deletion.", "D2_env_var_carveout": "FIXED. Re-verified the mechanism myself: rest-server.ts#normalizeConfig writes `maskObjectFields: isObjectSchemaMaskingEnabled(metadata.maskObjectFields)`, and packages/metadata-core/src/object-schema-fls.ts#isObjectSchemaMaskingEnabled returns false when OS_ALLOW_UNMASKED_OBJECT_METADATA is any non-empty value other than `0`/`false`. 'no flag, env var or config file moves it' is now 'no flag, config file or CLI option moves it' plus an explicit ONE CARVE-OUT paragraph naming the env var as the only thing outside an embedder's argument that changes any value in the file. Carried into metadata_endpoints.json's _note and its maskObjectFields row, which no longer says 'always gets the schema default'.", "D3_self_contradiction": "FIXED. The docblock now separates the two facts it was conflating: the KEY is always true under os serve and the dev plugin (it is unreachable), and the MASK is on UNLESS OS_ALLOW_UNMASKED_OBJECT_METADATA is set. The contradiction with its own next paragraph is gone.", "D4_over_claim": "FIXED. Verified at packages/cli/src/commands/serve.ts#apiConfig: the two keys are read from `(config as any).api ?? {}` — the stack config's own top-level `api:` block. 'never a file the CLI reads' and 'two CLI-derived keys' are replaced by: os serve reads the stack config's top-level `api:` block and forwards exactly two keys out of it, through an `as any` cast, so the CLI DOES read a config file and just forwards those two keys.", "D5_record_correction": "FIXED, and the record now says WHERE rather than whether. Verified myself: 'The cap is deployment policy — `RestServerConfig.batch.maxBatchSize` (1..1000, default 200)' exists verbatim at packages/rest/src/rest-server.ts#enforceBatchSize. FOLLOW-UPS E2 no longer says the phrase 'never occurred in the file'; it now records that the phrase does not occur in packages/spec/src/api/rest-server.zod.ts (control: maxBatchSize occurs there 3x) but DOES exist in the REST server, that the ruling MISATTRIBUTED it to the wrong file rather than inventing it, and that the carrier is real, same defect class, and OWED TO A SUCCESSOR CARD in domain:rest (this PR does not touch packages/rest).", "D6_revision_history": "FIXED. api-backend.rest-crud-config-contract 1 -> 2, api-backend.rest-batch-config-contract 1 -> 2, api-backend.rest-metadata-config-contract 2 -> 3, each with a history entry dated 2026-09-08, ref #15543, stating the semantic change (workaround -> permanent method, so a runner is not waiting for a boot-path flag that is not coming) and both factual corrections. Acceptance clauses, steps, variants and oracles untouched.", "CI_red_followup": "FIXED. check:docs went red at 167ef867 because the D1-D3 edits changed the module docblock and describe() strings — the exact two inputs build-docs.ts renders — and I did not regenerate in the same push. Regenerated with gen:schema && gen:docs (⛔ never hand-edited), committed at 506e5c864. Verified the corrections reached the RENDERED page, not just the source: `createHonoServerPlugin` count on the mdx is 0, and the three stale claims ('no flag, env var or config file moves it', 'never a file the CLI reads', 'two CLI-derived keys') return zero hits there." }, "new_finding_this_round": "check:platform-checklist went RED on my own patch prose — 6 x SOURCE LINE CITATION. The checklist tree forbids `file.ts:NNN` pins (README: 'Every call cites framework source as `file` plus the symbol it lands in'); my D1/D4/D5 corrections had cited rest-api-plugin.ts:115, serve.ts:3966-3968 and rest-server.ts:2071. All six converted to the `path#Symbol` form (#createRestApiPlugin, #apiConfig, #enforceBatchSize) and the gate now reports 'symbol anchors: 630/630 resolved against 307 cited sources'. Note the scope: this convention governs docs/qa/platform-checklist/** only, so the line citations in the liveness ledgers and the zod docblock are a different tree's convention and were left alone.", "summary": "Patch round 1 applied in full: D1 (a published door that does not exist), D2 and D3 (the env-var carve-out and the docblock contradicting itself), D4 (the CLI-reads-no-file over-claim), D5 (the record correction — the ruling misattributed the 'deployment policy' sentence to the wrong file, it did not invent it) and D6 (revision/history bumps). Every claim was re-measured before editing rather than carried over, which is the lesson the round named and which is also what caught the seventh problem: my own corrections tripped the checklist tree's source-line-citation ban, now fixed. The regenerated reference page is part of the fix, not a follow-up — that was the CI-red cause and it is closed with check:docs green on the final head. Diff unchanged in shape: still prose, ledger rows and one generated artifact; no schema shape, default, bound or refusal moved.", "tests": "All at final head b8cc67dab, clean tree. GATES: re-derived on the NEW head (94 families — the 24 doc-family gates now fall out of the derivation directly, since the mdx change is in the committed diff from the start; that set equals my round-1 union of 70 + 24). Reconciled: 'Run reconciliation — 94 derived, 94 run, 0 NOT-MEASURED, 0 UNRUN' and '94 derived famil(ies) accounted for'. 93 green. 1 declared NOT MEASURED: pnpm check:dual-build-cjs-loads exit 3, 'PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/' (needs all 83 packages built; a repo-wide build is CI's run). Five further gates first returned a prerequisite refusal in the recreated worktree (check:doc-formula-expressions, check:doc-security-posture, check:skill-examples, check:docs-transcript-drift, and check:platform-checklist's real finding above) — the four prerequisite ones re-ran GREEN after building formula/lint/client-react/client. THE PM'S SPECIFIC ASK: check:docs exit 0, and check:generated reports 'All 15 generated artifacts are up to date'. RATCHET FAMILY RE-RUN ON THE FINAL HEAD after the last commit moved it: check:generated, check:docs, check:liveness, check:api-surface, check:authorable-surface, check:nul-bytes, check:platform-checklist — all exit 0. TESTS re-run on the final head, not carried over from round 1: pnpm --filter @objectstack/spec test -> 'Test Files 465 passed (465)', 'Tests 12952 passed (12952)', VERDICT command-exit 0; pnpm --filter @objectstack/spec typecheck -> VERDICT command-exit 0. Both via scripts/pm/os-verify-lock.sh, slot issue-15543-dev; verdicts read from the VERDICT command-exit line, never a bare $?. TIER unchanged and re-proved on this head: check:api-surface and check:authorable-surface both green with no regeneration; only check:docs ever went stale, and only from the describe()/docblock edits.", "gates": { "derived": 94, "run": 94, "green": 93, "not_measured": 1, "not_measured_detail": "pnpm check:dual-build-cjs-loads — exit 3, PREREQUISITE NOT MET (repo-wide build; CI's run). Declared, never read as a pass.", "reconcile": "94 derived, 94 run, 0 NOT-MEASURED, 0 UNRUN", "at_commit": "b8cc67dab" }, "line_budget": "n/a — no `skills/**` path touched; no governed surface touched (docs/adr/**, .claude/**, skills/**, AGENTS.md, CLAUDE.md all absent from the diff).", "deviations": [ "⚠️ dispatch-gates printed a STALE TREE warning on this head: the branch is at least 10 commits behind origin/main and 6 files the derivation reads changed across that range. I did NOT merge main — the round said do not widen the diff beyond the corrections and the regeneration. So the 94 is this tree's derivation; CI on the merge ref is the authoritative set, and a family added on main since the branch point would not appear above. Declared rather than silently assumed away.", "check:dual-build-cjs-loads declared NOT MEASURED (repo-wide build).", "Source-line citations were converted to file#symbol ONLY inside docs/qa/platform-checklist/**, which is the tree whose README carries that ban and whose gate enforces it. The liveness ledgers' and the zod docblock's line references were left as they are — a different tree with a different convention, and changing them would widen the diff." ], "files_changed": [ "packages/spec/src/api/rest-server.zod.ts", "packages/spec/liveness/crud_endpoints.json", "packages/spec/liveness/metadata_endpoints.json", "packages/spec/liveness/batch_endpoints.json", "docs/qa/platform-checklist/FOLLOW-UPS.md", "docs/qa/platform-checklist/areas/api-backend.json", "content/docs/references/api/rest-server.mdx", ".changeset/rest-server-config-embedder-only-reachability.md" ], "commits_this_round": [ "167ef867b — D1-D6 corrections in source, ledgers, checklist and changeset", "506e5c864 — regenerate content/docs/references/api/rest-server.mdx (the CI-red fix)", "b8cc67dab — cite file plus symbol, never a source line number, in the checklist prose" ], "mcp_calls": "0 for this round as well — REST plus git throughout; cumulative 0.", "open_questions": [], "out_of_scope_findings": [ "FOR PM / A SUCCESSOR CARD (domain:rest): packages/rest/src/rest-server.ts#enforceBatchSize's docblock says 'The cap is deployment policy — RestServerConfig.batch.maxBatchSize (1..1000, default 200)'. That is the ruling's second citation, real but in the wrong package, and it is the same defect class this card closes: a cap described as deployment policy that no CLI-started deployment can set. Not filed by me — this PR does not touch packages/rest and the ruling's scope is packages/spec — and recorded in FOLLOW-UPS E2 as owed. ⇒ Recommend PM file it, or hand it to the next domain:rest dispatch.", "noted, not filed: serve.ts reaches createRestApiPlugin through an `as any` cast, so even the two forwarded keys are unchecked at that seam. Successor: whoever takes option 1.", "noted, not filed: RestApiConfigSchema's other nine live keys are embedder-only on the same measurement but carry no false-posture sentence; the file-header paragraph covers them. Successor: the same option-1 card.", "noted, not filed: `api` has no liveness ledger file, so its keys carry no reachability row and no liveness verdict; seeding one is a ledger-governance decision (SPEC_ONLY_SCHEMAS in check-liveness.mts). Successor: none identified.", "noted, not filed: FOLLOW-UPS E3's separate owed refresh (two items' automated.ref -> rest-config-mount-table.pin.test.ts, #15544) is untouched and still owed.", "labels: none applied — Clause-②: no, no label named by the dispatch, and the PR ships a changeset." ] }
Generated by Claude Code
os-dev-report
{ "issue": 15543, "status": "done", "round": "merge round (patch round 2) — sync with origin/main, everything re-measured on the merged head", "branch": "claude/issue-15543-rest-server-config-embedder-only", "pr": "https://github.com/objectstack-ai/objectstack/pull/16775", "head_sha": "fcbbbebfe", "pr_state": "open, draft: true, auto_merge: null, mergeable_state: clean — not readied, not enqueued, not armed", "premise_still_valid": true, "merge": { "tool": "bash scripts/pm/os-regen-merge.sh — ALREADY the path this merge took, before the addendum arrived. Its log shows step 1 (git merge origin/main), step 2 (per-file side selection on the os-regen paths) and step 3 (commit the merge BEFORE any regeneration); step 4 was then executed by hand as the script intends, since the regen chain varies by surface.", "commit": "fcbbbebfe, a real merge commit with two parents (b8cc67dab + a749dcc4b). ⛔ No rebase, no amend, no force-push.", "distance_closed": "15 commits behind at the start of this round (the PM measured 12, then 20 — main moves continuously).", "generated_file_collision_the_script_exists_for": "Handled exactly as predicted, and the script chose per file: it printed 'KEEPING the branch's bytes of content/docs/references/api/rest-server.mdx — the branch changed it and main did not', and took main's side of packages/spec/liveness/action.json and the generated state-counts.md (#16784). ⛔ Nothing was hand-edited to reconcile.", "regeneration_needed_after_the_merge": "NONE, and this is a measurement, not an assumption: after building the spec on the merged tree, `check:generated` reports 'All 15 generated artifacts are up to date'. state-counts.md did not need regenerating because my three ledgers' diffs are note/_note prose only — no `status` moved — so the counts #16784 recomputed are still correct over the merged set.", "swallow_check_step_4_3": "Asserted by quoted-exact-name comparison against origin/main, not by reading the merge's exit code. Sibling #16784: action.json `operation` and `patch` are `live` on the merged tree and on origin/main alike. Sibling #16792: docs/audits/2026-07-unknown-key-strictness-ledger.md is byte-identical to origin/main (empty diff). My own implementation body survived: 'WHO CAN WRITE THIS CONFIG' 2x in the zod + 1x in the mdx, 'Reachability: EMBEDDER-ONLY' 3x, 'ONE CARVE-OUT' 1x + 1x, the embedder-only describe() clause 3x + 3x, and 21 REACHABILITY hits across the three ledgers (18 live rows + 3 _notes).", "drift_since_the_merge": "5 commits behind origin/main as this report is written — main moved during the build and gate run. NOT chased: the three files the PM named as the collision risk (action.json, state-counts.md, the strictness-ledger audit) are byte-identical to current origin/main, so no new collision exists, and re-merging would invalidate the gate run this report cites. mergeable_state reads `clean`, and the merge queue rebuilds on the current main at land time." }, "summary": "Merged origin/main into the branch with scripts/pm/os-regen-merge.sh — which is what this merge already used — and re-derived and re-ran everything on the merged head rather than carrying round-1 results forward. The stale-tree deviation I declared last round is now closed with a measurement rather than an assumption: the derivation on the merged head is 94 families, byte-identical to the derivation on the stale head, so the merge added no families. No regeneration was owed after the merge, and no sibling entry was swallowed. The PR's own diff is unchanged in shape — still 8 files, +216/-51, zero governed-surface and zero content/docs/releases/ paths.", "tests": "All at merged head fcbbbebfe, clean tree. DERIVATION: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on the merged head — 94 families, and `diff` against the stale-head derivation is EMPTY, which is the direct answer to 'the merge may add families': it did not. The STALE TREE warning that accompanied the previous derivation is gone. GATES: 94 run, 93 green. Reconciled: 'Run reconciliation — 94 derived, 94 run, 0 NOT-MEASURED, 0 UNRUN' and '94 derived famil(ies) accounted for'. NOT MEASURED, unchanged and NOT a pass: pnpm check:dual-build-cjs-loads, exit 3, verbatim 'PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/' listing @objectstack/account, setup, studio, cloud-connection, connector-mcp, connector-openapi and more — it needs all 83 packages built, which is a repo-wide build and CI's run; my diff adds no export, entry or tsup change so it cannot move it. RATCHET FAMILY, all re-run on the merged head after the last commit: check:generated (exit 0, 'All 15 generated artifacts are up to date'), check:docs 0, check:liveness 0, check:api-surface 0, check:authorable-surface 0, check:nul-bytes 0, check:platform-checklist 0. TESTS re-run on the merged head: pnpm --filter @objectstack/spec test -> 'Test Files 465 passed (465)', 'Tests 12955 passed (12955)' — three more than round 1's 12952, which is the sibling work the merge brought in — VERDICT command-exit 0. pnpm --filter @objectstack/spec typecheck -> VERDICT command-exit 0. Both via scripts/pm/os-verify-lock.sh, slot issue-15543-dev; verdicts read from the VERDICT command-exit line, never a bare $?. TIER re-proved on the merged head: check:api-surface and check:authorable-surface green with no regeneration required.", "gates": { "derived": 94, "run": 94, "green": 93, "not_measured": 1, "not_measured_detail": "pnpm check:dual-build-cjs-loads — exit 3, 'PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/'. Repo-wide build; CI's run. ⛔ Never a pass.", "reconcile": "94 derived, 94 run, 0 NOT-MEASURED, 0 UNRUN", "derivation_delta_vs_stale_head": "empty — the merge added no families", "at_commit": "fcbbbebfe" }, "line_budget": "n/a — no `skills/**` path; no governed surface (docs/adr/**, .claude/**, skills/**, AGENTS.md, CLAUDE.md) and no content/docs/releases/** path in the diff: verified as 0 files by path filter on the diff against the current merge-base.", "deviations": [ "5 commits behind origin/main at report time (main moved during this round's build and gate run). Not re-merged: the three files named as the collision risk are byte-identical to current origin/main, mergeable_state is clean, and a second merge would invalidate the run this report cites. Declared rather than silently carried.", "check:dual-build-cjs-loads remains NOT MEASURED with its exit-3 prerequisite text, as instructed.", "The previous round's stale-tree deviation is now RETIRED, by measurement: the merged-head derivation is identical to the stale-head one." ], "files_changed": [ "packages/spec/src/api/rest-server.zod.ts", "packages/spec/liveness/crud_endpoints.json", "packages/spec/liveness/metadata_endpoints.json", "packages/spec/liveness/batch_endpoints.json", "docs/qa/platform-checklist/FOLLOW-UPS.md", "docs/qa/platform-checklist/areas/api-backend.json", "content/docs/references/api/rest-server.mdx", ".changeset/rest-server-config-embedder-only-reachability.md" ], "commits_this_round": [ "fcbbbebfe — Merge remote-tracking branch 'origin/main' (via scripts/pm/os-regen-merge.sh; no regeneration commit was owed, check:generated proves it)" ], "mcp_calls": "0 this round; cumulative 0 — REST plus git throughout.", "open_questions": [], "out_of_scope_findings": [ "ACKNOWLEDGED, and it is yours: the packages/rest finding (rest-server.ts#enforceBatchSize's 'deployment policy' docblock) is filed by you, not me, and I have touched no packages/rest file in this PR. Correction accepted on my framing: I wrote `domain:rest`, and this repo's table has no such lane — packages/rest belongs to `domain:cli`. FOLLOW-UPS E2's prose records the measurement and that it is owed to a successor, and it does NOT name a domain label, so nothing there needs changing for this correction.", "noted, not filed: serve.ts reaches createRestApiPlugin through an `as any` cast, so even the two forwarded keys are unchecked at that seam. Successor: whoever takes option 1.", "noted, not filed: RestApiConfigSchema's other nine live keys are embedder-only on the same measurement but carry no false-posture sentence; the file-header paragraph covers them. Successor: the same option-1 card.", "noted, not filed: `api` has no liveness ledger file, so its keys carry no reachability row and no liveness verdict; seeding one is a ledger-governance decision (SPEC_ONLY_SCHEMAS in check-liveness.mts). Successor: none identified.", "noted, not filed: FOLLOW-UPS E3's separate owed refresh (two items' automated.ref -> rest-config-mount-table.pin.test.ts, #15544) is untouched and still owed.", "labels: none applied — Clause-②: no, no label named by the dispatch, and the PR ships a changeset." ] }
Generated by Claude Code
zhuangjianguo commented
on Sep 8, 2026 CollaboratorMore actionsClosed by PR #16775 —
pm:dispatchedand the assignee released in one writedomain:specexecution seat, sessionsession_016N6xmWt5hYm94ffVEwGH8x, 2026-09-08T07:24Z (clock read).PR #16775 merged at 07:19:54Z and closed this card via
Fixes(state_reason: completed).One label write, read back: removed
pm:dispatched, cleared the assignee; keptpriority:p2,domain:spec,finding.Option 2 executed (the 2026-09-07 ruling, director seat summon #17, batch #2, maintainer verbatim 「同意」): the
crud/metadata/batchkeys stay and keep their runtime reads, and the schema now says plainly who can write them —Reachability: EMBEDDER-ONLYin the docblocks plus a per-keyREACHABILITYrow inpackages/spec/liveness/{crud,metadata,batch}_endpoints.json. A runner no longer re-derives it from the boot paths every run.Corrections that rode along, each measured rather than inherited:
- ⛔ A door that does not exist. Seven published places named
createHonoServerPlugin({ restConfig }). A definition probe returns zero across the tree, against a positive control findingcreateRestApiPluginatpackages/rest/src/rest-api-plugin.ts#createRestApiPlugin.HonoServerPluginis a class whoserestConfigoption has one reader takingapi.basePathfor the SPA fallback; it never constructs a REST server. ⭐ Every site carries the retraction by name rather than a silent deletion — a deletion leaves nothing to stop the next reader reintroducing it. - The env-var carve-out:
OS_ALLOW_UNMASKED_OBJECT_METADATAis the one thing outside an embedder's argument that moves a value in this file, and the docblock now says so instead of claiming nothing does. - An over-claim withdrawn:
os servedoes read a config file — the stack config's own top-levelapi:block — and forwards exactly two keys out of it through anas anycast. ⚠️ A correction to the record itself: the card and its ruling citebatch.maxBatchSize's docblock calling the cap "deployment policy" and attribute it topackages/spec/src/api/rest-server.zod.ts. The phrase does not occur in that file (control:maxBatchSizeoccurs there 3×, so the zero is real). It exists verbatim inpackages/rest/src/rest-server.ts#enforceBatchSize. ⇒ The ruling misattributed the sentence; it did not invent it. That carrier is real, is the same defect class, and is filed as [finding]rest-server.ts#enforceBatchSizecalls the batch cap "deployment policy", but no shipped boot path can set it — the same defect #15543 just closed inpackages/spec, one package over #16801 — ⛔ correctly refused in-scope here, since this PR touches nopackages/restfile. Routing corrected there too:packages/restisdomain:cli; there is nodomain:restlane.
The merge that landed it went through
scripts/pm/os-regen-merge.sh(a real merge commit, two parents, ⛔ no rebase or force-push), and the swallow check was made againstorigin/mainrather than an exit code: #16784'saction.json(operation/patchbothlive) and #16792's strictness ledger are both byte-identical to main. Nothing was dropped. The previous round's stale-tree deviation closed by measurement — the merged-head derivation is 94 families and diffs empty against the stale-head one.https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x
Generated by Claude Code
- ⛔ A door that does not exist. Seven published places named
Record correction, closing half — the misattributed carrier is now fixed
domain:cliexecution seat, out of successor card #16801. ⛔ Not a re-post: comment 5581004629 already corrected the record on 2026-09-08T07:24Z, before this dispatch existed. This adds only the half that comment could not carry — the landing — plus an independent re-take of its measurement, because a citation nobody re-measured is how the original misattribution travelled.The measurement, re-taken on this seat's own tree (base
70f7d6d735)Not relayed from #16801 or from comment 5581004629 — grepped here:
probe tree reading deployment policypackages/spec/2 hits, both in CHANGELOG.mddeployment policypackages/spec/src/0 deployment policypackages/spec/src/api/rest-server.zod.ts(the file this card and its ruling named)0 maxBatchSizepackages/spec/src/hits in 11 files — api/batch.zod.ts6,api/rest-server.zod.ts4,api/contract.zod.ts1, …maxBatchSizepackages/spec/src/api/rest-server.zod.ts4 The control fires in the same tree and in the same file, so each zero is a reading rather than a probe that never reached. ⇒ The ruling misattributed the sentence to
packages/spec/src/api/rest-server.zod.ts; it did not invent it. Confirmed, independently.Where the sentence actually lived, and where it went
Verbatim carrier, located by symbol (its line number has moved three times across the readings on this card and #16801, so a line citation is not usable):
packages/rest/src/rest-server.ts#enforceBatchSize— "The cap is deployment policy —RestServerConfig.batch.maxBatchSize(1..1000, default 200)"That carrier is fixed on branch
claude/issue-16801-batch-cap-embedder-only-prose, opening as a draft PR againstmainnow. Route 1 only — the prose is narrowed onto the fact this card already landed, using this card's own vocabulary rather than a second phrasing:Reachability: EMBEDDER-ONLY, matching theBatchEndpointsConfigSchemadocblock and the WHO CAN WRITE THIS CONFIG header inpackages/spec/src/api/rest-server.zod.ts, plus the per-keyREACHABILITYrow inpackages/spec/liveness/batch_endpoints.json. ⛔ Nobatchconfig is threaded through any boot path: that would be a new authorable key, which the 2026-09-07 ruling declined for the spec-side siblings, and reversing it is its own decision.⇒ Anyone arriving at this card looking for the "deployment policy" sentence should read
packages/rest/src/rest-server.ts#enforceBatchSize, notrest-server.zod.ts, and will now find it already corrected there.One more carrier, still open
The same claim has a third carrier that neither this card nor #16801 names:
content/docs/api/data-api.mdx(hand-written) tells an operator the cap is "the deployment'sbatch.maxBatchSize(default 200, configurable 1–1000)". Filed separately rather than folded in — it is outside #16801's fenced file surface, and it is invisible to the docs-drift tool, which anchors only on the symbolRestServerand never on this claim's inputs.
Generated by Claude Code
- added a commit that references this issue
on Sep 9, 2026 - added a commit that references this issue
on Sep 17, 2026 - added a commit that references this issue
on Sep 17, 2026 - added 4 commits that reference this issue
on Sep 28, 2026
Filed unassigned and unlabelled for triage, from the capability-coverage work on #14961 (PR #15541). Not that PR's change and not addressed there.
Measured on
origin/main6f944589RestServerConfigdeclares four sub-objects the REST server parses and consumes at construction —crud,metadata,batch,routes— with live keys that decide real behaviour: which CRUD and bulk routes are mounted, the data and metadata path prefixes, the batch size cap, and the ADR-0106 D8 object-schema masking posture.No shipped boot path authors any of them.
packages/cli/src/commands/serve.tsconstructs the plugin ascreateRestApiPlugin({ api: { api: { enableProjectScoping, projectResolution } } })— theapisub-block only, and only those two keys, both derived from CLI flags.packages/plugins/plugin-dev/src/dev-plugin.tscallscreateRestApiPlugin()with no config at all.RestServerConfigare programmatic:createRestApiPlugin({ api })(packages/rest/src/rest-api-plugin.ts) andcreateHonoServerPlugin({ restConfig })(packages/plugins/plugin-hono-server/src/hono-plugin.ts).So a deployment driven by the CLI cannot set
batch.maxBatchSize, movecrud.dataPrefix, disable a CRUD operation, or opt out of object-schema masking, however the schema documents those keys. Every value is whatever the Zod defaults say.Why it is worth a decision
batch.maxBatchSize's own docblock calls the cap "deployment policy";metadata.maskObjectFields's saysfalse"opts this server out". Both are true only for an embedder./data/${object}…whilecrud.dataPrefixis live and discovery advertisesroutes.data = base + dataPrefix— a non-default prefix makes the SDK disagree with the mounts #14879 — the SDK hard-codes/data/...whilecrud.dataPrefixis live — by narrowing who can even reach the disagreement today.knownGaps.Options (not decided here)
RestServerConfig(or the subset that is genuinely deployment policy: the cap, the prefixes, the masking opt-out).⛔ Not a bug report against the keys' liveness: they are read, and the ledger's
liveverdicts are correct. The gap is between "read by the runtime" and "authorable by anyone shipping the runtime".Where it is already captured
docs/qa/platform-checklist/FOLLOW-UPS.md§10b E2, and in theknownGapsof the three newapi-backend.rest-*-config-contractitems.Generated by Claude Code