Repository navigation
fix(rest)!: rollback accepts only a positive integer toVersion - #25532
Conversation
POST /meta/:type/:name/rollback coerced toVersion with a bare Number(), so true, [2] and "2" in a JSON body restored a version and 1.5 answered 404 VERSION_NOT_FOUND. The body value is now judged as it came against the whole-number declaration /diff reads, the query string goes through readDeclaredQueryNumber, and everything else answers the door's existing 400 INVALID_REQUEST. The census ledger loses the rollback exemption row. Claude-Session: https://claude.ai/code/session_01CxBKycaiUpA1yGx1XyoGqX Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 19 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 e77550d11215784259c9ba561309470561f2f3e8 && git checkout e77550d11215784259c9ba561309470561f2f3e8
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin b8c959013b1ed5b9c6767994d3eb805b5c45b13e e03bd96c3a30d1b4efa2a4a81d196bf116f202a7 && git checkout -B drift-repro b8c959013b1ed5b9c6767994d3eb805b5c45b13e && git merge --no-ff e03bd96c3a30d1b4efa2a4a81d196bf116f202a7
node scripts/docs-audit/affected-docs.mjs --json b8c959013b1ed5b9c6767994d3eb805b5c45b13e
|
Contract reviewServed-tier: Diff read as ① Derived judgments
② Semver level
Clause-②: no (narrowing) — correct for this diff: the claim's ③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
席位核对ACCEPT ·
Generated by Claude Code |
Requested by Sales · project thread
Fixes #25482
Clause-②: no (narrowing)
Metadata-protocol: none
What changed
POST /api/v1/meta/:type/:name/rollback(packages/rest/src/meta-item-history-routes.ts) read its version with a bareNumber(...), so{"toVersion": true}restored v1,[2]and"2"restored v2, and1.5reached the protocol and answered404 VERSION_NOT_FOUND.toVersion, then theversionalias): the value is judged as it came againstUNDECLARED_WHOLE_NUMBER_PARAM, the same whole-number declaration/diffreads its versions against. A boolean, an array, a string, an object or a fraction does not parse. Then the door's existing< 1check applies.?toVersion=): read throughreadDeclaredQueryNumberwith that declaration, the reader/diffuses. No second number parser. Its thrownVALIDATION_FAILEDis caught at the read and answered as this door's own refusal.400 INVALID_REQUESTwith the message'toVersion' (positive integer) is required. The precedence (toVersion,version,?toVersion=) and the gates in front of the read are unchanged.Why a numeric string in the body is now refused (the narrowing arm)
The door declares no request schema. The only in-repo client,
@objectstack/client'srollbackItem(type, name, toVersion: number), sendsJSON.stringify({ toVersion })with a number. The console's caller, objectuipackages/data-objectstack/src/metadata-client.tsrollback(type, name, toVersion: number)read at the pinned.objectui-sha6fb31dcc99, sends a JSON number too. No real client sends a string, so"2"in a JSON body is refused like the other non-numbers. A?toVersion=3query string is still read. That refusal of an input that used to be accepted is the(narrowing)arm. The changeset carries the BREAKING banner, the FROM/TO and the ADR-0087 disposition (not-required (no-migration-prescription)).The census ledger
rest-server-query-number-census.test.tsledgered the rollback'sNumber(toVersionRaw)as anexemptcoercion. That site is gone, so its row is deleted and the pinned site count goes from 9 to 8. The row's own reason said that moving the door ontoVALIDATION_FAILEDwould be a wire change, so the refusal staysINVALID_REQUEST. The exemption's three query pins (abc, blank,Infinity) and its lit control are kept in the new block.Tests
Through the real
RestServerroute (registerRoutes()then the registered handler), inpackages/rest/src/rest-server-query-number-census.test.ts, describe#25482 rollback toVersion:toVersionset totrue,[2],"2",1.5,0,-1,"abc"or{}answers400,code: INVALID_REQUEST, androllbackMetaItemis never called.versionalias set totrue,[2],1.5or0gets the same answer.?toVersion=set toabc, blank,Infinity,1.5,0or-1gets the same answer, and so does an absent version.{toVersion: 2}reachesrollbackMetaItemas2with200, the alias{version: 4}reaches it as4,?toVersion=3reaches it as3, and a body version wins over?toVersion=.All run at head
e03bd96c3a:pnpm --filter @objectstack/rest exec vitest run --maxWorkers=2 src/rest-server-query-number-census.test.ts src/rest-server-query-multiplicity.test.tspassed 2/2 files and 67/67 tests (lockVERDICT command-exit 0).pnpm --filter @objectstack/rest typecheck && pnpm --filter @objectstack/rest test: typecheck clean, then 316 files passed, 6347 tests passed and 326 skipped (lockVERDICT command-exit 0).pnpm lintexited 0, andpnpm check:type-check-debtexited 0 (re-measured, none above its recorded number). Both ran under the lock.scripts/ablation-replace.mjsswapped the body judgement forNumber.isFinite(Number(bodyVersion)) ? Number(bodyVersion) : undefined. The anchor count went 1 to 0, the replacement count 0 to 1, and the blobcd8508485ed8becamea1c834b191a0. The#25482block then failed 7 tests and passed 16: bodytrue,[2],"2"and1.5, and the aliastrue,[2]and1.5. That is the expected red. A trap restored the file: its blob equals HEAD (cd8508485ed8) andgit diff HEADis empty. The test imports./rest-server.jsfromsrc, so nodistwas involved.node scripts/pm/dispatch-gates.mjs --commandsnamed 67, and--ranreconciled them as 67 accounted for, 66 run and 1 NOT MEASURED. The one ispnpm check:dual-build-cjs-loads(exit 3, PREREQUISITE NOT MET: it reads builtdist/for every package and this worktree built only the@objectstack/restclosure). CI'sLint & Repo Gatesruns it. All other 66 exited 0.(narrowing)arm requires it):pnpm --filter '@objectstack/dogfood^...' build && pnpm --filter @objectstack/dogfood test, run under the lock ate03bd96c3awith no shard and no path. LockVERDICT command-exit 0. 290 test files passed and 3 skipped (293). 2494 tests passed and 18 skipped (2512). No failures.Acceptance notes
rest-server-query-number-census.test.ts's module header still says "ten sites in the whole file today", which was already stale before this change. Not touched here.🤖 Generated with Claude Code
https://claude.ai/code/session_01CxBKycaiUpA1yGx1XyoGqX