fix(rest): read IMPORT_JOB_MAX_ROWS from the spec export instead of a mirrored literal (#6535) - #6859
Merged
os-project-manager merged 7 commits intoAug 9, 2026
Conversation
… mirrored literal Refs #6535
…ort-max-rows-single-definition
…ort-max-rows-single-definition
The changeset argued the convergence from the comment alone. Name the mechanism the PR actually measured: api-surface/api.json records the export's NAME, not its VALUE, so moving spec's 50_000 to 20_000 leaves that baseline byte-identical and every gate green while the enforced ceiling never budges. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017uFVNMmTxLpmfQYiuKM1Yx
…ort-max-rows-single-definition
…ort-max-rows-single-definition
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 11 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
os-project-manager
marked this pull request as ready for review
August 9, 2026 01:28
os-project-manager
enabled auto-merge
August 9, 2026 01:28
os-project-manager
deleted the
claude/issue-6535-import-max-rows-single-definition
branch
August 9, 2026 02:05
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #6535
This is observation-class, not a bug fix
No behaviour changes. Both numbers were
50_000before and the enforced ceiling is50_000after. The 413 boundary, the 413 wording and the accepted-payload size are all byte-identical. Nothing a user can reach today behaves differently. Please do not read this as a defect repair — it removes a drift surface, and the honest claim is exactly that and no more.What it removes:
IMPORT_JOB_MAX_ROWShad two definitions.packages/spec/src/api/export.zod.ts:404—export const IMPORT_JOB_MAX_ROWS = 50_000, carrying the TSDoc that is the ceiling's public statement (it feeds the generated reference surface). This is the authority.packages/rest/src/rest-server.ts— a localconst IMPORT_JOB_MAX_ROWS = 50_000whose comment admitted it "mirrors spec IMPORT_JOB_MAX_ROWS".packages/restis the ceiling's only enforcer (themaxRows:argument to the import prep, and the 413 supplementary copysplit the file into batches of ${IMPORT_JOB_MAX_ROWS}), and it read its own literal, not the spec's. The two were joined by a comment and nothing else.The failure direction is "the docs state one number, the system enforces another, and the 413 message lies self-consistently" — because the number interpolated into the 413 is rest's copy, so a diverged system produces an error message that is internally coherent and externally wrong.
Change: delete the local
const;import { IMPORT_JOB_MAX_ROWS } from '@objectstack/spec/api'(subpath style, matching the file's other spec imports —rest-server.tsalready imports from@objectstack/spec/api,/security,/shared,/kernel).packages/specis not modified: the export already exists and is the authority.Both read points confirmed resolving to the spec export, and repo-wide there is now exactly one definition:
grep -rn 'const IMPORT_JOB_MAX_ROWS'overpackages/**(excludingdist/) returns the spec line and nothing else. No other package reads the constant.The card's central claim, measured (not asserted)
The sharp observation in #6535 is that changing spec's constant to
20_000leaves every gate green while the enforced ceiling never moves. I reproduced that on this merge base, rather than restating it:With spec mutated to
20_000, rest at its pre-fix local literal, and the old test in place (hardcoded50_001rows,expect(...).toMatch(/50000/)):pnpm --filter @objectstack/rest test→ 69 files / 1097 tests, all passedcheck:api-surfacePASS ·check:exported-anyPASS ·check:dual-source-exportsPASS ·check:spec-changesPASS ·check:upgrade-guidePASS ·check:variant-docsPASSThe mechanism, located:
packages/spec/api-surface/api.json:472records the export as— the name, not the value. So
50_000 → 20_000leaves that baseline byte-identical, and nothing anywhere compares the declared number to the enforced one. Also checked: no file undercontent/docs/hardcodes the ceiling's digits, so the public statement really is the TSDoc on the exported constant — which is precisely why the spec export must be the thing rest reads.The pin, and how it would have caught the drift
packages/rest/src/import-job-integration.test.ts— the 413 case now derives everything it knows about the ceiling from the spec export: how large a payload must be to breach it (IMPORT_JOB_MAX_ROWS + 1) and the number the 413 copy must name. It does not re-spell50_000; doing so would move the duplicated literal into a third place and re-open the very surface this closes. It asserts the ADR-0112 envelope (status and code) plus the interpolated number, since the 413 wording is contract — it tells the caller what to split the file into.Reverse verification, run as a 2×2 (rest implementation × spec constant), predictions written before measuring:
AssertionError: expected 201 to be 413Cell D is how the pin would have caught it. With rest re-declaring its own literal, moving spec's constant down makes the pin send
spec + 1 = 20_001rows at a route still enforcing the stale50_000.20_001 ≤ 50_000, so the payload is accepted and no 413 is produced at all — the failure lands on the status assertion, not on a message mismatch. Under the imported constant (cell C) the two move together and it stays green. That is the drift made mechanical: the pin is red exactly when declaration and enforcement disagree, and silent otherwise.Honest exclusion: A and B are green in both directions, so the classic "revert the fix, watch the pin go red" is impossible to stage on today's tree — with both numbers at
50_000there is no live defect for any test to catch. The load-bearing comparison is C vs D, where the mutated spec is held fixed and the fix is the only variable. The predicted failure mode was "202 or whatever the accept status is"; measured, it is 201.The third literal — deliberately not converged
The triage round flagged a third
50_000in this file. It is a different ceiling and I did not merge the two concepts:That caps the
limitquery parameter of the streaming export route.IMPORT_JOB_MAX_ROWS's TSDoc is explicit that it is the ceiling on "rows accepted by a single async import job", capping the memory and worker time of a payload the client uploads in one request. Same digits today, different semantics, different failure consequence — a shared symbol would couple two ceilings that have no reason to move together, and the next person to tune one would silently move the other. Recorded here rather than acted on; if the export cap should also become a spec constant, that is its own card.Deployment-configurability — explicitly not decided here
#6535 notes that if the ceiling ought to be deployment-configurable rather than a spec constant, that is a different decision. I did not make it. This PR converges two definitions into the one that already exists; if the ceiling should later become configurable, it now has a single site to change instead of two.
Changeset
patchon@objectstack/rest, argued as an internal-consistency change: no behaviour moves, no acceptance surface changes, no API signature changes — aminorwould overstate it and no bump at all would leave the convergence out of the record. The note says plainly that the number is unchanged.Gates (real output, all re-run on the merged tree)
origin/mainadvanced ~60+ commits while this branch sat, including two siblings touchingrest-server.ts(#6822'sgetApiBasePath()rewiring, #6821'sExportFieldMetaretirement). Merged cleanly, regions verified intact afterwards, and every earlier measurement discarded and re-run on the merged result.pnpm turbo run build→ 71/71 successful (build closure first, per [finding][devx]check:type-check-debt的 ledger 余量会让新写的 pin 变哑:mongodb 曾有 33 条余量吞掉一次真实回退,另有 5 条目前带 4–19 余量 #6376, or the debt ledger refuses to measure)pnpm lint(eslint . --no-inline-config) → exit 0, cleanpnpm turbo run typecheck→ 125/125 successfulpnpm check:type-check-debt→ OK;@objectstack/restis a DEBT-ledger package — TEST_DEBT records 163, measured 153, did not rise. 34 ledger entries re-measured, none above its recorded number.pnpm --filter @objectstack/rest test→ 71 files / 1109 tests, all passedcheck:*battery → 49 of 50 PASSThe one FAIL is
check:objectui-pin-fresh:.objectui-shaonmainis behind objectuimain. Pre-existing and repo-wide — this diff does not touch.objectui-shaorpackages/console— and by that workflow's own design it blocks the Version-Packages/release lane only, never an ordinary code PR ("Between pin bumps an ordinary code PR sits behind objectui almost always — that is the normal state of the repo, not a defect").One measurement I am not claiming: a fully green whole-monorepo
pnpm test. I ran it; it came back with failures in eight unrelated packages, and every single one was a timeout (Test timed out in 5000ms/10000ms/30000ms/120000ms) with zero assertion failures — a sibling agent'sturbo run buildwas saturating the box concurrently. Each implicated package passes standalone (@objectstack/types8 files / 187 tests;@objectstack/rest71 files / 1109 tests). I am reporting the per-package standalone runs as the real signal and flagging the full-suite run as unmeasured under load rather than dressing it up as green. CI is the arbiter.Generated by Claude Code