Uh oh!
There was an error while loading. Please reload this page.
fix(metadata-protocol): route rollbackMetaItem through the canonical type fold, closing a plural-addressable ADR-0010 lock (#8819) - #8870
Conversation
…type fold (#8819) `rollbackMetaItem` was the eighth `/meta` entry point and the last one still deriving its type key from `PLURAL_TO_SINGULAR` instead of `canonicalizeMetaRequestType`. It passed the RAW caller spelling to `assertLockAllowsWrite` while every row operation below it used the folded key, so `getEffectiveLock`'s overlay limb — which queries `sys_metadata` with the raw `type` — looked an ADR-0010 `_lock` up under a type no row carries, returned 'none' (a verdict, not a neutral value: #5706) and let the restore proceed against the folded key. Pinned as group D of protocol-publish-canonical-fold.test.ts, with a positive control so the group cannot pass by being unable to roll back at all. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XeQRiAa7vYRVX5Fog7Zby8
…lback-canonical-fold
… in the pin header Includes the ablate-against-an-immovable-sha warning: origin/main advanced mid-run (shared .git across worktrees) and the first ablation attempt measured a newer protocol.ts against this worktree's older spec. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XeQRiAa7vYRVX5Fog7Zby8
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 3 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also reference the affected code. These are read-only:
|
Uh oh!
There was an error while loading. Please reload this page.
Fixes#8819
rollbackMetaItemis the eighth/metaentry point on thePOST /api/v1/meta/:type/:name/rollbackURL family, and it was the last one still deriving its type key fromPLURAL_TO_SINGULAR— the MANIFEST-COLLECTION map #7894 moved this boundary off — instead ofcanonicalizeMetaRequestType. Seven siblings fold; this one did not.The half that was not fail-closed
assertLockAllowsWritedelegates togetEffectiveLock, whose artifact limb folds and whose overlay limb queriessys_metadatawith the rawtype. The rollback passed the caller's spelling to the gate while every row operation below it used the folded key. So for a manifest-present type, a rollback addressed/meta/views/case_grid/rollbacklooked the ADR-0010_lockup under atypeno row carries, got'none'back — not a neutral value but the verdict "the author declared no protection" (#5706) — and then restored the history body against the folded key, which resolves the protected row perfectly.Severity, at its real width and not rounded up. It needs an environment kernel (
assertLockAllowsWriteopens withif (this.environmentId === undefined) return null) and a lock carried by a stored overlay row rather than a packaged artifact — the artifact limb folds, so an artifact_lockwas already found under either spelling. My measurement agrees with the card's stated window exactly; I did not find it wider or narrower.The fold also reaches three limbs that were incoherent rather than unsafe: the revertability tier took the permissive plugin branch for the four manifest-absent types (
field,seed,external_catalog,translation); and the[not_overridable]refusal, both ADR-0010 audit rows and both receipt sentences reported the caller's spelling for a row written under the canonical one.recordMetadataAuditre-folds internally throughPLURAL_TO_SINGULAR, which covers a manifest-present plural and misses the four manifest-absent ones — so folding at the boundary is what makes the audit trail agree with the write for both classes.Placement
After the existing
toVersionenvelope guard, not at the top of the method. That is the positionsaveMetaItemdocuments for this exact pair, naming this method's opening guard its structural twin: a malformed request envelope is refused before its type key is canonicalised, and both refusals are[invalid_request]/400 either way.Scope — what this deliberately does NOT do
getEffectiveLock's overlay limb still queries the rawtype. Folding it there would close this class at the producer for every present and future caller, and that is the contract-first shape — but it is a shared gate whose blast radius nobody has measured, so per the triage ruling it stays open for its own card with its own measurement. It is not ridden in here and the per-verb fix does not foreclose it; the code comment says so at the site, so a later reader cannot mistake this for the class being closed. The per-verb fix needed no change to the shared gate to be correct, so there was no fork to stop on.Pin —
protocol-publish-canonical-fold.test.tsgroup DThe shape #8769 used for group C, driving the real
ObjectQL/ protocol /SysMetadataRepositoryover an in-memory driver on an environment kernel (an environment-less kernel skips the gate wholesale, and a harness that cannot reach the gate cannot pin it):ITEM_LOCKED/ 403, the ADR-0112 envelope, never "it threw");This verb needed its own group rather than inheriting group C's: a rollback writes a new active row from history instead of promoting a draft, so what a missed lock costs here is the active body silently reverting.
Reverse verification — direction predicted BEFORE running, and one prediction was two-sided
Ablated against the immovable baseline
fd6bdf89fand rebuiltmetadata-protocol, becausepackages/objectqlresolves it throughdist. Mutation proven to reach the artifact by the executable marker this file already uses:grep -o canonicalizeMetaRequestType dist/index.js | wc -lis 9 with the fold (one definition + eight call sites) and 8 without.Predicted 2 red / 8 green; measured 2 red / 8 green — and the part worth recording is that the two reds are red for different reasons, both predicted:
The first is the defect itself: the locked rollback went through. The second is not a broken restore — the restore still succeeded, and only the receipt still named the caller's spelling. A single-reason prediction would have been wrong here even though the count would have matched.
git checkout origin/main -- protocol.ts, andorigin/mainadvanced mid-run (every worktree shares one.git). That pulled aprotocol.tsnewer than this worktree'sspecand died onhas no exported member 'unrecognisedMetaTypeRefusal'— a contaminated measurement that could as easily have produced a plausible wrong number as a loud error. Ablate against an immovable sha. This is recorded in the pin file's header.Verification — all at
c1254a83a, the final commitMerged
origin/mainduring the run (#8770 and #8854 landed while working, as dispatch predicted); the diff vs merge-base is three files and stays inside the declared region.Per-package suites, real numbers:
@objectstack/metadata-protocol@objectstack/objectql@objectstack/rest@objectstack/runtimetypecheckgreen for all four. Gate union, re-derived from the actual changed paths withscripts/pm/dispatch-gates.mjsand run after the final commit:check:cross-package-test-inputs,check:durability-log-level,check:filter-alias-parity,check-cross-package-test-inputs.mjs,check-engine-split-ratio.mjs,check:query-options-erasure,check:type-check-coverage,check:type-check-debt --re-measure— all PASS;check:changeset-gate-self-tests,check:objectui-changeset,check-adr-0087-registration.mjs,check-changeset-no-major.mjs,check-empty-changeset.mjs— all PASS;check:nul-bytes(any edit) — PASS.The TEST_DEBT ratchet was measured on a fully built workspace, not inferred from a green
typecheck:33 ledger entries re-measured in 286.2s, 1926 raw tsc errors total, none above its recorded number. Its reported surplus is in@objectstack/lint, a package this PR never touches, and is already tracked by #6376.Out-of-scope finding
Filed #8868 (unassigned, for triage), not fixed here:
diffMetaItemis a ninth entry point on this URL family with the same unfolded manifest-map lookup, and for the four manifest-absent types it answers a well-formed empty diff instead of the real one. It is read-only — no lock, no write — so none of this card's severity carries over. That issue is not addressed by this PR.Generated by Claude Code