Skip to content

rollback-verb-skips-the-canonical-fold: rollbackMetaItem addresses around the ADR-0010 overlay _lock when spelled with a plural #8819

Description

@hotlong

Measured while implementing #8769 (not fixed there — out of that card's declared region, which is publishMetaItem).

What was measured

#8769 routed publishMetaItem through canonicalizeMetaRequestType, making it the seventh /meta entry point to funnel through the boundary fold. rollbackMetaItem is an eighth entry point on the same URL family, and it does not funnel through it either.

Measured on origin/main @ 60ade586e:

That asymmetry is the same one #8769 pinned one verb over. assertLockAllowsWrite delegates to getEffectiveLock, whose artifact limb folds and whose overlay limb does not — it queries sys_metadata with the raw type (:10310). So for a type present in the manifest map, a rollback addressed with the plural spelling looks the lock up under a type no row carries, gets 'none' back — which is not a neutral value, it is the verdict "the author declared no protection" (#5706) — and then performs the write against the folded key, which resolves the protected row perfectly.

Reachable from the wire: POST /api/v1/meta/:type/:name/rollback is a registered route with a caller-supplied :type (packages/rest/src/rest-route-ledger.ts:183, handler at packages/rest/src/rest-server.ts:6510).

Why this is filed rather than fixed in #8769

#8769's dispatch declared its file surface as publishMetaItem and its region; this is a different method. It is also not merely the same fix pasted: rollbackMetaItem writes a new active row from history rather than promoting a draft, so the fold's blast radius there (the not_revertable refusal sentence at :15323, the receipt at :15380, the conflict message at :15385) wants its own measurement and its own pin.

Severity, stated at its real width

Same preconditions #8769 measured for the publish verb, so the window is narrow and should not be rounded up: it needs an environment kernel (lockWriteRefusal and assertLockAllowsWrite both open with if (this.environmentId === undefined) return null, skipping the gate wholesale otherwise), and a lock carried by a stored overlay row rather than a packaged artifact (the artifact limb folds, so an artifact _lock is found under either spelling).

Within that window it is a lock gate that can be addressed around from the wire, and "a lock gate must not fail open" is the rule getEffectiveLock already carries in its own header.

Suggested disposition

Route rollbackMetaItem through canonicalizeMetaRequestType like the seven siblings, with a pin in the shape #8769 used (packages/objectql/src/protocol-publish-canonical-fold.test.ts group C — canonical spelling refused by the lock, plural spelling refused by the SAME lock, and the protected body unchanged).

Worth deciding at the same time whether the durable fix is at getEffectiveLock instead: folding its overlay limb would close this class at the producer for every present and future caller, rather than once per entry point. That is the contract-first shape, but it is a change to a shared gate and wants a maintainer's call rather than a dev's.

Backlink: #8769 · the boundary fold's rationale is #7894 · the fail-open-is-not-neutral rule is #5706.

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions