From c7836c7c2001a09aa6b8518dbe4775a324f076a0 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 8 Sep 2026 16:51:53 +0000 Subject: [PATCH 1/2] docs(rest): the batch cap is embedder policy, not deployment policy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `enforceBatchSize`'s docblock called `RestServerConfig.batch.maxBatchSize` "deployment policy". That is false of every shipped boot path: a `RestServerConfig` is the argument a host passes at construction, there is exactly one door (`createRestApiPlugin({ api })`, whose `start()` is the only non-test site reaching `new RestServer(...)`), and neither shipped boot path opens it with a `batch` config — `os serve` forwards two `api.*` keys and the dev plugin passes none. A CLI-started deployment always gets the 200 default and cannot move it. Narrow the prose onto that fact using the vocabulary already landed on the spec side: `Reachability: EMBEDDER-ONLY`, matching the `BatchEndpointsConfigSchema` docblock, the WHO CAN WRITE THIS CONFIG header in `packages/spec/src/api/rest-server.zod.ts` and the per-key REACHABILITY row in `packages/spec/liveness/batch_endpoints.json`. Two wordings for one fact in two packages is how this defect survived the spec-side fix. No behaviour change: the 1..1000 range and the 200 default are correct and still enforced. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_015QE8qk46e5CHJxyQEUjbf8 --- packages/rest/src/rest-server.ts | 34 +++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/packages/rest/src/rest-server.ts b/packages/rest/src/rest-server.ts index c1eb345534..e1658a0e24 100644 --- a/packages/rest/src/rest-server.ts +++ b/packages/rest/src/rest-server.ts @@ -2061,7 +2061,7 @@ export class RestServer { } /** - * [#3939] Enforce the deployment's batch-size cap on a bulk write route. + * [#3939] Enforce the configured batch-size cap on a bulk write route. * Returns `true` when a response was sent (the caller must return). * * The cap was declared in three places in `batch.zod.ts` (`.max(200)` on @@ -2076,10 +2076,34 @@ export class RestServer { * own result), which turns a 10k-id body into 10k sequential engine * round-trips inside one request instead of one statement. * - * The cap is deployment policy — `RestServerConfig.batch.maxBatchSize` - * (1..1000, default 200) — so it lives here and the schemas carry shape - * only. One place decides it, and it is the place that knows the - * deployment's configured value. + * The cap is `RestServerConfig.batch.maxBatchSize` (1..1000, default 200), + * so it lives here and the schemas carry shape only: one place decides it, + * and it is the place that holds the constructed config. + * + * Reachability: EMBEDDER-ONLY (#15543, #16801). ⛔ It is NOT deployment + * policy — this docblock said exactly that until #16801, and no shipped + * boot path makes it true. A `RestServerConfig` is the ARGUMENT a host + * passes when it constructs the server, and there is exactly ONE door: + * `createRestApiPlugin({ api })` (`packages/rest/src/rest-api-plugin.ts`), + * whose `start()` is the only non-test site that reaches + * `new RestServer(...)`. Neither shipped boot path opens it with a `batch` + * config — `os serve` (`packages/cli/src/commands/serve.ts`) forwards + * exactly two keys out of the stack config's `api:` block + * (`api.enableProjectScoping`, `api.projectResolution`), and the dev plugin + * (`packages/plugins/plugin-dev/src/dev-plugin.ts`) calls + * `createRestApiPlugin()` with no config at all. ⇒ A CLI-started + * deployment always gets the schema default of 200, and no flag, config + * file or CLI option moves it. + * + * This is the recorded posture, not a gap awaiting a fix, and it is written + * the same way on the spec side — the `BatchEndpointsConfigSchema` docblock + * and the WHO CAN WRITE THIS CONFIG header in + * `packages/spec/src/api/rest-server.zod.ts`, plus the per-key REACHABILITY + * row in `packages/spec/liveness/batch_endpoints.json`. Keep the two + * wordings together: threading a `batch` config through a boot path would + * be a NEW authorable key, which the spec-side siblings were denied for + * want of measured demand, so reversing that is its own decision and + * ⛔ not a docblock's to take. */ private enforceBatchSize(res: any, count: number, max: number, object?: string): boolean { if (count <= max) return false; From 35c8e983dce5da3149d888a9975e7fe18977a382 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 8 Sep 2026 17:19:24 +0000 Subject: [PATCH 2/2] chore(changeset): patch @objectstack/rest for the batch-cap prose correction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Measured, not assumed: the docblock reaches published output. Built before and after the fix and read `packages/rest`'s own `files[]` (dist, README.md, CHANGELOG.md) — `deployment policy` went 1 -> 0 and `EMBEDDER-ONLY` 0 -> 1 in each of dist/index.d.ts, index.d.cts, index.js and index.cjs, and dist/index.d.ts moved 129.91 KB -> 131.47 KB. Positive control on the same paths: `enforceBatchSize` 14 hits, `BATCH_TOO_LARGE` 2. Published bytes move, so `skip-changeset` is not available. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_015QE8qk46e5CHJxyQEUjbf8 --- .changeset/rest-batch-cap-embedder-only-prose.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .changeset/rest-batch-cap-embedder-only-prose.md diff --git a/.changeset/rest-batch-cap-embedder-only-prose.md b/.changeset/rest-batch-cap-embedder-only-prose.md new file mode 100644 index 0000000000..0c342d7843 --- /dev/null +++ b/.changeset/rest-batch-cap-embedder-only-prose.md @@ -0,0 +1,11 @@ +--- +"@objectstack/rest": patch +--- + +`enforceBatchSize`'s docblock no longer calls the batch cap "deployment policy". It is embedder policy, and this correction narrows the claim onto what is actually reachable. + +`RestServerConfig.batch.maxBatchSize` (1..1000, default 200) is the argument a host passes when it constructs the server. There is exactly one door — `createRestApiPlugin({ api })`, whose `start()` is the only non-test site that reaches `new RestServer(...)` — and neither shipped boot path opens it with a `batch` config: `os serve` forwards exactly two keys out of the stack config's `api:` block (`api.enableProjectScoping`, `api.projectResolution`), and the dev plugin calls `createRestApiPlugin()` with no config at all. So a CLI-started deployment always gets the 200 default, and no flag, config file or CLI option moves it. An operator reading the old sentence would have gone looking for a knob that is not there. + +The wording now matches what `@objectstack/spec` 17 already says about the same key — `Reachability: EMBEDDER-ONLY` in the `BatchEndpointsConfigSchema` docblock and the WHO CAN WRITE THIS CONFIG header of `rest-server.zod.ts`, plus the per-key REACHABILITY row in the liveness ledger. Two wordings for one fact in two packages is how the claim survived the spec-side correction. + +No behaviour change: the 1..1000 range and the 200 default are unchanged and still enforced on all five bulk routes. Hosts that construct their own `RestServerConfig` keep setting the cap exactly as before.