Commit 89c6ec5
docs(spec): the batch cap config is embedder-only, not a deployment's — the sixth carrier (#18951)
Fixes #18763
Clause-②: no
One comment changes. `packages/spec/src/api/batch.test.ts` told a reader
that the one route that did cap a batch "read the deployment's
configured `batch.maxBatchSize`". The claim that a *deployment* can
configure that key was ruled false on 2026-09-07 (director seat, summon
#17, decision batch #2, maintainer verbatim 「同意」): the key is
**embedder-only**. Five carriers have been corrected under that ruling
(#15543 · #16801 · #16940 · #17183 · #18739 / PR #18757); this is the
sixth.
No test body, no assertion, no schema key, no export moves. The diff is
comment text above one `it(...)`, plus a merge of `main`.
## Why five sweeps read zero on this file
The sentence **wraps across two comment lines** — `the deployment's`
ends `:110`, `configured` begins `:111` — and every prior probe matched
line by line. Measured here on the base commit `02bdeaaf23`, same tree,
same phrase:
| probe | hits |
|---|---|
| `git grep -c "the deployment's configured"`, whole tree | 3 files —
and `batch.test.ts` is **not** one of them |
| same phrase, fold-proof (strip comment leaders, collapse all
whitespace incl. newlines to one space) | **1** —
`packages/spec/src/api/batch.test.ts` |
The wrap is the entire difference between the two readings. That is this
card's durable half: **a line-scoped probe is blind to a wrapped claim
exactly as a directory-scoped probe is blind to what sits outside the
directory**, and it reports the shortfall as a clean zero.
## The two sentences, side by side
Before (`:108-112`):
> `[#3939]` The count bounds moved OUT of the schema. They were a second
source of truth that never matched reality: the schema said 1..200 while
the routes enforced nothing, and the one route that did cap read **the
deployment's configured** `batch.maxBatchSize` (1..1000) instead. The
cap is now enforced at the route from that config, and the schema
carries shape only.
After:
> `[#3939]` The count bounds moved OUT of the schema. They were a second
source of truth that never matched reality: the schema said 1..200 while
the routes enforced nothing, and the one route that did cap read **the
constructed** `RestServerConfig.batch.maxBatchSize` **(1..1000, default
200)** instead. The cap is now enforced at the route from that config,
and the schema carries shape only.
>
> **Reachability: EMBEDDER-ONLY (#15543, #16801).** That config is NOT
deployment policy: it is written only by a host that constructs the
`RestServerConfig` itself, never by `os serve` or the dev plugin, so a
CLI-started deployment always gets the default of 200 and no flag,
config file or CLI option moves it. Same posture as the schema this file
pins (`BatchUpdateRequestSchema` in `batch.zod.ts`) and
`RestServer#enforceBatchSize`; the per-key record is the REACHABILITY
row in `packages/spec/liveness/batch_endpoints.json`.
Every fact the old paragraph carried survives: the bounds left the
schema, why they left (a second source of truth that never matched),
where today's cap comes from, and that the schema carries shape only.
What changed is the attribution of the config — plus one paragraph
stating the reachability outright, so the next reader does not have to
re-derive it. **The wording is copied, not invented**: it follows
`RestServer#enforceBatchSize` and the `BatchUpdateRequestSchema`
paragraph sentence for sentence, which is what #18739 asked of whoever
took the fifth carrier.
Verified against `origin/main` rather than taken from the card: the
route reads `const maxBatch = batch.maxBatchSize ?? 200`
(`packages/rest/src/rest-server.ts:13358`) out of the constructed
config; the schema is `z.number().int().min(1).max(1000).default(200)`
(`rest-server.zod.ts:516`); and `BatchUpdateRequestSchema.records`
carries no `.max()`.
## `batch.zod.ts` is NOT a seventh carrier — the question the dispatch
asked, answered
The dispatch flagged that the positive control's hit,
`packages/spec/src/api/batch.zod.ts:128-133`, says the cap *is*
`RestServerConfig.batch.maxBatchSize` — and that file **ships**
(`src/**/*.zod.ts` is in `packages/spec`'s `files[]`, `.test.ts` is
not). It asked for (a) a seventh, published carrier, or (b) still true
today.
**(b), and it is not close.** Those six lines are the **first half of
the paragraph PR #18757 landed to correct the fifth carrier**. The
paragraph runs `:128-144`; reading it whole is what settles it:
```
// [#3939] No `.max()` here: the batch-size cap is
// `RestServerConfig.batch.maxBatchSize` (1..1000, default 200), enforced at
// the route so one place decides it. …
//
// Reachability: EMBEDDER-ONLY (#15543, #16801). ⛔ The cap is NOT DEPLOYMENT
// policy — this comment said exactly that until now, and no shipped boot path
// makes it true. It is written only by a host that constructs the
// `RestServerConfig` itself, never by `os serve` or the dev plugin, …
```
Two reasons, either sufficient:
1. **It is not the ruled-false claim.** The ruling falsified *who may
write the key* — an operator deploying the platform — never *which key
the route reads*. "The cap is `RestServerConfig.batch.maxBatchSize`,
enforced at the route" is a true statement about the runtime, and it is
the same statement the four landed carriers make.
2. **The very next sentence states the reachability**, in the ruling's
own vocabulary, and names the two records that hold it. A reader cannot
leave that comment believing a deployment can move the cap.
The six-line window reads ambiguous only because it stops one sentence
early. ⛔ The file is untouched by this PR, and it needs no card.
## Acceptance
All readings re-taken on this branch, ⛔ none inherited. Population and
legs at head `45c81f390b`, over `git ls-files` tracked text files
excluding `CHANGELOG.md`, with comment leaders stripped and all
whitespace (newlines included) collapsed to one space:
```
scanned 8808 tracked text file(s) [7 binary/undecodable, 80 CHANGELOG.md skipped]
0 SUBJECT "the deployment's configured `batch.maxBatchSize`"
1 LIT "the batch-size cap is" :: packages/spec/src/api/batch.zod.ts
0 DARK "the batch-size cap is DEPLOYMENT policy"
```
⭐ SUBJECT and LIT are from **one run**: the 0 is a reading, not a dumb
instrument. The same three legs read `1 / 1 / 0` on the base commit
`02bdeaaf23` before the edit — so the SUBJECT leg moved, and the other
two did not. The 8808 was re-measured here (the card says 8757: the tree
grew, not a conflict).
| check | result |
|---|---|
| `pnpm --filter @objectstack/spec exec vitest run
src/api/batch.test.ts` | 29/29 pass |
| `pnpm --filter @objectstack/spec test` | **488 files / 14182 tests
pass**, exit 0 |
| `pnpm --filter @objectstack/spec typecheck` | exit 0 — the test layer
compiles under `tsconfig.test.json` |
| `pnpm exec eslint . --no-inline-config --format json` | **6855 files**
(eslint's own selection), 0 errors, 0 warnings, exit 0 — the whole repo
union, not a narrowing |
| `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` | 76 families derived, all 76 run, exit
codes landed to disk first and reconciled with `--ran` |
Of those 76: **73 exit 0**. The other three exit **3**, the code these
gates use for *PREREQUISITE NOT MET — nothing was measured*:
`check:dual-build-cjs-loads`, `check:lean-entry-closure` and
`check:type-check-debt` each need the **whole monorepo built** on disk
first — 87 unbuilt packages named by the first of them, 30 by the third
— which CI does before the step and which this shared container
OOM-killed once already (exit 137, kernel OOM on the spec DTS pass). ⛔
Not reds, and not passes — NOT MEASURED, and CI builds the closure
before each of them.
`skip-changeset`: nothing published moves. `packages/spec`'s `files[]`
is `dist`, `json-schema`, `liveness`, `prompts`, `llms.txt`,
`README.md`, `src/**/*.zod.ts`, `CHANGELOG.md`, `api-surface`,
`spec-changes.json` — no test glob, so `src/api/batch.test.ts` is not in
the tarball. `check:published-files` and `check:api-surface` both ran
green.
## Acceptance notes
- **#18740** (open, `domain:devx`) carries the same falsified claim in
the **17.0.0 CHANGELOG entries** of `packages/rest` / `packages/spec`,
in its strongest form. Untouched here: CHANGELOG is release-owned and is
never amended from a code PR. Its card says the two probably want the
same taker; ⛔ merging the cards is the maintainer's call, not this PR's.
- Nothing else in `batch.test.ts` carries the claim. The two sibling
titles — `does not cap the record count — that is the route's job
(batch.maxBatchSize)` (`:122`, `:356`) — state where the cap lives and
assert no reachability, so they are correct as they stand and are
untouched.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3)_
Co-authored-by: Claude <noreply@anthropic.com>1 parent e19ae67 commit 89c6ec5
1 file changed
Lines changed: 13 additions & 3 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
107 | 107 | | |
108 | 108 | | |
109 | 109 | | |
110 | | - | |
111 | | - | |
112 | | - | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
113 | 123 | | |
114 | 124 | | |
115 | 125 | | |
| |||
0 commit comments