Skip to content

fix(rest)!: rollback accepts only a positive integer toVersion - #25532

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-25482-rollback-toversion
Oct 23, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-25482-rollback-toversion

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

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 bare Number(...), so {"toVersion": true} restored v1, [2] and "2" restored v2, and 1.5 reached the protocol and answered 404 VERSION_NOT_FOUND.

  • JSON body (toVersion, then the version alias): the value is judged as it came against UNDECLARED_WHOLE_NUMBER_PARAM, the same whole-number declaration /diff reads its versions against. A boolean, an array, a string, an object or a fraction does not parse. Then the door's existing < 1 check applies.
  • Query string (?toVersion=): read through readDeclaredQueryNumber with that declaration, the reader /diff uses. No second number parser. Its thrown VALIDATION_FAILED is caught at the read and answered as this door's own refusal.
  • Every refusal is still the door's existing 400 INVALID_REQUEST with 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's rollbackItem(type, name, toVersion: number), sends JSON.stringify({ toVersion }) with a number. The console's caller, objectui packages/data-objectstack/src/metadata-client.ts rollback(type, name, toVersion: number) read at the pinned .objectui-sha 6fb31dcc99, 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=3 query 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.ts ledgered the rollback's Number(toVersionRaw) as an exempt coercion. 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 onto VALIDATION_FAILED would be a wire change, so the refusal stays INVALID_REQUEST. The exemption's three query pins (abc, blank, Infinity) and its lit control are kept in the new block.

Tests

Through the real RestServer route (registerRoutes() then the registered handler), in packages/rest/src/rest-server-query-number-census.test.ts, describe #25482 rollback toVersion:

  • Body toVersion set to true, [2], "2", 1.5, 0, -1, "abc" or {} answers 400, code: INVALID_REQUEST, and rollbackMetaItem is never called.
  • The version alias set to true, [2], 1.5 or 0 gets the same answer.
  • ?toVersion= set to abc, blank, Infinity, 1.5, 0 or -1 gets the same answer, and so does an absent version.
  • Lit controls: body {toVersion: 2} reaches rollbackMetaItem as 2 with 200, the alias {version: 4} reaches it as 4, ?toVersion=3 reaches it as 3, 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.ts passed 2/2 files and 67/67 tests (lock VERDICT 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 (lock VERDICT command-exit 0).
  • pnpm lint exited 0, and pnpm check:type-check-debt exited 0 (re-measured, none above its recorded number). Both ran under the lock.
  • Ablation, run once with the fix committed. scripts/ablation-replace.mjs swapped the body judgement for Number.isFinite(Number(bodyVersion)) ? Number(bodyVersion) : undefined. The anchor count went 1 to 0, the replacement count 0 to 1, and the blob cd8508485ed8 became a1c834b191a0. The #25482 block then failed 7 tests and passed 16: body true, [2], "2" and 1.5, and the alias true, [2] and 1.5. That is the expected red. A trap restored the file: its blob equals HEAD (cd8508485ed8) and git diff HEAD is empty. The test imports ./rest-server.js from src, so no dist was involved.
  • Derived gates: node scripts/pm/dispatch-gates.mjs --commands named 67, and --ran reconciled them as 67 accounted for, 66 run and 1 NOT MEASURED. The one is pnpm check:dual-build-cjs-loads (exit 3, PREREQUISITE NOT MET: it reads built dist/ for every package and this worktree built only the @objectstack/rest closure). CI's Lint & Repo Gates runs it. All other 66 exited 0.
  • Dogfood battery (the (narrowing) arm requires it): pnpm --filter '@objectstack/dogfood^...' build && pnpm --filter @objectstack/dogfood test, run under the lock at e03bd96c3a with no shard and no path. Lock VERDICT 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

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>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Oct 23, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/rest, touching 2 documentable anchor(s).

2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/permissions/system-context.mdx (via registerMetaItemHistoryRoutes (symbol, a top-level function))
  • content/docs/upgrading.mdx (via toVersion (literal, a string literal in registerMetaItemHistoryRoutes))
What this run could not see
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 19 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json b8c959013b1ed5b9c6767994d3eb805b5c45b13e → packageMentionDocs.

Which tree this was computed on

This run read content/docs from e77550d11215784259c9ba561309470561f2f3e8 — the merge of head e03bd96c3a30d1b4efa2a4a81d196bf116f202a7 into base b8c959013b1ed5b9c6767994d3eb805b5c45b13e, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs b8c959013b1ed5b9c6767994d3eb805b5c45b13e → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: e03bd96c3a30d1b4efa2a4a81d196bf116f202a7
Local-runs: none
Scope: packages/rest/src/meta-item-history-routes.ts, packages/rest/src/rest-server-query-number-census.test.ts + packages/rest/src/rest-server.ts (UNDECLARED_WHOLE_NUMBER_PARAM, readDeclaredQueryNumber, META_ITEM_ROLLBACK_PARAMS), packages/client/src/index.ts (rollbackItem), packages/metadata-protocol/src/protocol.ts (rollbackMetaItem's own guard), packages/spec/src/api/protocol.zod.ts (RollbackMetaItemResponseSchema; no request schema), packages/rest/src/rest-server-meta-member-selector.test.ts, packages/rest/src/rest-server-query-multiplicity.test.ts, packages/rest/src/rest-server-query-number-reads.test.ts, packages/rest/package.json (zod ^4.6.5), .objectui-sha (6fb31dcc99), .changeset/25482-rollback-to-version-integer.md (prose face, read for ②)

Diff read as dc3e5b9d7b (merge-base of origin/main and the head, the base the claim named) → e03bd96c3a, one commit, 3 files, +115 / −22. Card #25482 body and all three comments read (triage 6370228332, claim 6371817529, os-dev-report 6373954258). Check-runs on the head: 41, 34 success, 7 skipped (opt-in and main-only lanes), 0 failures; the five required contexts — Lint & Repo Gates, TypeScript Type Check, Test Core, Build Core, Governed Surface Queue Guard — all success; Check Changeset success. No governed path in the file list.

① Derived judgments

  1. Accept set of POST /meta/:type/:name/rollback, JSON body toVersion (and the version alias): FROM "whatever Number() makes finite and ≥ 1" TO "a JSON number that z.number().int() accepts and is ≥ 1" — right. The hunk replaces Number(body.toVersion ?? body.version ?? req.query?.toVersion) with a body-first safeParse against UNDECLARED_WHOLE_NUMBER_PARAM (z.number().int().optional(), rest-server.ts:793) and keeps the door's own below-1 floor. Under zod 4 z.number() refuses non-numbers, NaN and non-finite values and .int() refuses fractions, so true, [2], "2", 1.5, "abc", {} all land on the existing 400 INVALID_REQUEST, and 0 / -1 on the kept floor. This is exactly the card's Expected line and the message the door already printed ('toVersion' (positive integer) is required). The spec declares only RollbackMetaItemResponseSchema (protocol.zod.ts:2189) and no request schema, so there is no declared contract the body judgement contradicts.
  2. {"toVersion": 1.5} moves from 404 VERSION_NOT_FOUND (forwarded to the protocol) to 400 INVALID_REQUEST — right; the card names this as the defect's third face, and the protocol's own guard (protocol.ts:29918, not finite or below 1) is untouched and now only ever sees a whole number.
  3. The (narrowing) arm — a numeric STRING in the JSON body ("2") is now refused — right, and correctly declared. The only in-repo sender is @objectstack/client's rollbackItem(type, name, toVersion: number, …) (index.ts:2303), which sends JSON.stringify({ toVersion }) with a typed number. A repo-wide grep at the head for a string-typed toVersion finds only package-version strings ('2.0.0'), /diff query tests, and one rollback test that sends '4' as a query value (member-selector test :253, which still passes through the reader). No doc under content/docs, skills/ or examples/ shows a string body. The claim's own Basis for the Clause-② line foresaw this revision ("revised with a narrowing arm if the measured diff refuses an input a real client sends"); the dev measured and revised. Right.
  4. ?toVersion= is read through readDeclaredQueryNumber(req.query, 'toVersion', UNDECLARED_WHOLE_NUMBER_PARAM, { emptyIsAbsent: false }) — right, and it introduces no NEW refusal on the query path. Read against the reader (rest-server.ts:858–878): a blank value was Number('') = 0 → refused before and is handed to the schema as '' → refused now; abc → NaN → refused both before and now; Infinity → non-finite → refused both; 1.5 → forwarded before (then 404), refused now (right, see 2); absent → .optional() passes undefined → the door's own "required" 400, as before; 3 → 3. refuseRepeatedQueryParams still runs first (multiplicity test :285 pins the two-value array by name), so the reader sees a single string, as its docblock requires. /diff reads its versions through the same declaration (emptyIsAbsent: true there, because parseV('') already meant absent on that door; false here, because Number('') already meant refused on this one) — the per-door empty judgement is applied as the reader's docblock prescribes.
  5. The reader's thrown validationFailure is caught by a bare catch and answered as the door's own 400 INVALID_REQUEST instead of the VALIDATION_FAILED + fields[] envelope — right, by the card's ruling, with one note. The card's Expected line and the triage ruling (6370228332) both prescribe 400 INVALID_REQUEST; the deleted ledger row's own reason said moving this door onto VALIDATION_FAILED "would be a wire change to a door that already refuses, which no card here decides"; #6877's multiplicity refusal on this door is unchanged. The only throw the reader makes is validationFailure, so the bare catch swallows nothing else. Note for the next card on this door: if it ever moves onto the data surface's VALIDATION_FAILED envelope, this try/catch goes with it (the reader docblock's default is to let handleRouteError answer).
  6. Precedence toVersion → version → ?toVersion= is unchanged — right. The old ?? chain skipped undefined and null; the new guard is bodyVersion !== undefined && bodyVersion !== null, the same two values. One consequence the body prose states correctly: a present-but-invalid body value ("2" beside ?toVersion=5) is refused rather than falling through to the query — "judged as it came". Pinned by the precedence test.
  7. No public export, type, flag, route, parameter set or error code moves — right. Both helpers were already imported into this file for /diff (import block :176–190 is unchanged by the diff); META_ITEM_ROLLBACK_PARAMS (['toVersion', member]) is unchanged; the client SDK signature and RollbackMetaItemResponse are untouched; packages/spec is not in the diff, so no generated artifact is owed and TypeScript Type Check (which carries the spec gates) is success.
  8. The census ledger: the exempt row for Number(toVersionRaw) is deleted and the pinned site count goes 9 → 8 — right. The scanner (census test :89–118) counts calls of Number/parseInt/parseFloat, unary +, and those names passed as values; safeParse and readDeclaredQueryNumber are neither, so the removed Number(toVersionRaw) is the only site that leaves and no new site arrives. Keeping the row would have reddened the positive control (every non-not-a-query-value row must exist in the real file), so the deletion is forced, not optional. The exemption's three query pins (abc, blank, Infinity) are carried into the new #25482 block, and the sum comment is extended rather than rewritten.
  9. The #6877 comment edit ("The version read below already refuses an array") — right: a body array is refused by z.number(); a repeated query value by the multiplicity gate ahead of the read.
  10. The new pins are real: the harness (boot()/drive, :396–436) constructs RestServer, calls registerRoutes() and invokes the registered handler with a system caller; each refusal pin asserts status, nested code AND rollbackMetaItem not called, each lit control asserts the argument the protocol received — the "both halves" shape AGENTS.md's route-ownership rule asks for. Test Core (6/6 shards) is success on this head.

② Semver level

.changeset/25482-rollback-to-version-integer.md: '@objectstack/rest': patch, Clause-②: no (narrowing), Metadata-protocol: none, a **BREAKING** banner, FROM / TO / Unchanged / Fix lines, and exactly one ADR-0087 marker, not-required (no-migration-prescription), with its category closure stated on facts. This matches what the diff publishes: nothing a metadata author writes moves (none), so the level is patch; the change is a non-protocol narrowing of one REST door's accept set, which AGENTS.md's Post-Task Checklist #3 ships as patch while keeping the BREAKING banner and the ADR-0087 disposition — both present. No export is removed or renamed, so no tombstone or migration prescription is owed, which is what the marker says. The PR body carries byte-equal Clause-②: no (narrowing) and Metadata-protocol: none lines. Check Changeset and Lint & Repo Gates (the check:adr-0087-registration reader of the arm and marker) are both success.

Clause-②: no (narrowing) — correct for this diff: the claim's no is revised exactly as the claim said it would be when the measured diff refuses an input that used to be served.

③ Boundary flags

  • Dev deviation 1 — Clause-② moved from no to no (narrowing): answered; see ① item 3 and ②. The revision is the one the claim's own Basis line reserved.
  • Dev deviation 2 — the file-surface test is the census file, and its ledger row had to go: answered; see ① item 8. The claim's file surface named "its tests"; the census file is the existing test of this exact refusal, and the row deletion is forced by the positive control.
  • Dev deviation 3 — PR body footer in the dispatch's form: a presentation matter for the seat, not a contract question; the body carries the session URL. Noted, nothing to escalate.
  • Dev deviation 4 — lock queue-timeout 99 on several calls, no result read from a 99: a tooling note; every result the report cites is attributed to an exit-0 run, and the head's check-runs are the gate verdicts here anyway. Nothing to escalate.
  • open_questions: [] — none to answer.
  • Out-of-scope finding — the census module header's "ten sites in the whole file today" is stale (count is now 8, and was 9 before this PR): correctly left in the PR's Acceptance notes rather than widened into this diff (Prime Directive chore: version packages #10). Not a defect, not a trap; no card owed.
  • Reviewer's own flag — the objectui caller at the pinned .objectui-sha 6fb31dcc99 was not re-read in this review: the objectui repository is not attached to this session (the read is refused), so the claim that packages/data-objectstack/src/metadata-client.ts rollback(type, name, toVersion: number) sends a JSON number rests on the dev's recorded mcp__github__search_code read in report 6373954258. Nothing in this repo contradicts it (the console's only in-repo analogue, the client SDK, is typed number), the Console Pin Gate does not run on PRs, and a string sender there would surface as a console-side 400 on rollback, not as a main-red. Escalated only as a reading for the seat to confirm at its leisure; it does not change the verdict.
  • Governance: no governed surface in the file list; Governed Surface Queue Guard success. Not a Tier H or Tier S landing question. Size 137 changed lines, well under the 3,000-line human-merge threshold.

Implemented-by: claude/issue-25482-rollback-toversion
Reviewed-by: session_01CxBKycaiUpA1yGx1XyoGqX

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · domain:cli seat 1 (seat post #6024; lane read as domain:server) · os-sales · session session_01CxBKycaiUpA1yGx1XyoGqX · 2026-10-23T20:57Z · head e03bd96c3a

item reading
Shape 3 files, +115 / −22 (diff read by the seat). The rollback door judges a JSON body toVersion (or the version alias) as it came against UNDECLARED_WHOLE_NUMBER_PARAM, the declaration /diff reads. It reads ?toVersion= through readDeclaredQueryNumber, with no second parser. Everything else, and anything below 1, answers the door's existing 400 INVALID_REQUEST with its existing message. The census ledger loses its Number(toVersionRaw) exemption row (sites 9 → 8). Inside claim 6371817529; Fixes #25482.
Clause-② Revised from the claim's no to no (narrowing), as the claim's Basis line reserved: a numeric string in the JSON body ("2") is now refused. The in-repo sender, @objectstack/client rollbackItem, is typed number. The console's metadata-client.ts rollback at the pinned .objectui-sha 6fb31dcc99 was read by the dev (report 6373954258), not re-read here: objectui is not attached to this seat's session.
Contract review CONTRACT_REVIEW_TIER PASS on this head, 6374588590 (Reviewed-by this session). It names ① 10 derived judgments, all right; ② patch with the BREAKING banner and the ADR-0087 not-required (no-migration-prescription) marker, matching the diff; ③ every dev flag answered, nothing escalated past the objectui reading above.
Dogfood battery Owed (narrowing arm), run on this head: 290 files / 2494 tests passed, 0 failed (report 6373954258).
Tests Through the real RestServer route: body true, [2], "2", 1.5, 0, -1, "abc", {}, the alias, and bad query values all answer 400 with rollbackMetaItem not called. Body 2, alias 4 and ?toVersion=3 reach it as 2, 4 and 3, and a body version beats the query. Ablation (body judgement swapped back to Number()) turned 7 pins red, then was restored to HEAD. rest package: 316 files / 6347 tests.
Gates (check-runs on this head) 41 runs: 34 success, 7 skipped, 0 failed. mergeable_state: clean. Local: dispatch-gates 67 derived, 66 run exit 0, dual-build-cjs-loads NOT MEASURED locally (needs a full workspace build), green in CI. Report: mcp_calls 1 (search_code, a read), writes all through the relay.
Accepted deviations The census test is the file-surface test (the existing pin of this refusal), and its ledger row deletion is forced by the positive control. The PR body footer is in the dispatch's form.
Out of scope (noted, not filed) The census module header's "ten sites" is stale (8 now, 9 before). It is a comment, not a defect, so it is left in the Acceptance notes.
Cross-lane touches None. packages/rest is this seat's side of the server lane.
Landing Not governed (Governed Surface Queue Guard success, +115 / −22). Ready + auto-merge through the relay in this act.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 23, 2026 20:59
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 23, 2026 20:59
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 23, 2026
Merged via the queue into main with commit 0fab7ba Oct 23, 2026
42 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-25482-rollback-toversion branch October 23, 2026 21:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

rest: metadata rollback coerces toVersion with Number(), so {"toVersion": true} or [2] restores a version and 1.5 answers 404 instead of 400

2 participants