Uh oh!
There was an error while loading. Please reload this page.
fix(metadata-protocol): recovery doors run the ADR-0094 mutation projector - #14982
Conversation
…ector Adds the awaited runMutationProjector call to rollbackMetaItem, both limbs of revertCommit, and deleteMetaItem's legacy raw-engine exit, awaited before the existing fire-and-forget emitMetadataMutation call at each site. Fixes#14415 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
…covery-door-mutation-projector
…d changeset Adds protocol.recovery-doors-mutation-projector.test.ts pinning that rollbackMetaItem, both limbs of revertCommit, and deleteMetaItem's legacy raw-engine exit run the ADR-0094 projector with the correct state/body, before the existing emitMetadataMutation listener call, and adds the patch changeset. Fixes#14415 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
…coverage node scripts/check-engine-double-contract.mjs --write, picking up the delete/findOne/update doubles in protocol.recovery-doors-mutation-projector.test.ts (mirrors the already-pinned stub in protocol.recovery-doors-emit-mutation.test.ts). Fixes#14415 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
📓 Docs Drift CheckThis PR changes 1 package(s): 3 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 — 9 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 cdd5acdcc1e4c37fa4cbbeb9654fce87ece5e9e0 && git checkout cdd5acdcc1e4c37fa4cbbeb9654fce87ece5e9e0
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 5bc2f2727aef5d697530c63d64dd5592df445776 a32f970472b5ea4ed8455c0e50c67e2218773c1d && git checkout -B drift-repro 5bc2f2727aef5d697530c63d64dd5592df445776 && git merge --no-ff a32f970472b5ea4ed8455c0e50c67e2218773c1d
node scripts/docs-audit/affected-docs.mjs --json 5bc2f2727aef5d697530c63d64dd5592df445776
|
os-musk
commented
Sep 3, 2026
Independent review — PASS; marked ready and armed for the merge queueAn independent reviewer (not the author) re-derived this PR from the ruling and the code rather than from the PR body. Verdict PASS, no blocking defects. What was re-proved rather than accepted: Clause-② = Ablation independently reproduced, both directions. Removing Exactly the claimed 2 RED / 8 GREEN. Restored via Symbol fence against #14038 honored.#14038 is still open and scoped to Org scope checked, no mismatch. The legacy branch never resolves an No double-run or unhandled-rejection path. Gates re-derived off the merge base with no path arguments (48 commands): 44 green, 4 legitimate Landing. All 33 check runs on head One note for the ruling record, not a defect in this PRRead with maximal literalness, the ruling's first clause says " Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#14415
Ruling (A)
Director seat, summon #11, decision batch #17, item 2 (verbatim reply: 「同意」). Full ruling text is quoted on the issue thread (comment 5522731140).
What changed
rollbackMetaItem, both limbs ofrevertCommit, anddeleteMetaItem's legacy raw-engine exit restored/removed the metadata row and the in-memory registry, but never called the awaited ADR-0094 mutation projector — so a derived read-model (e.g.permission->sys_permission_set) stayed on the rolled-back-FROM state until an unrelated save/publish/delete on the same name, or boot reconciliation (D3), re-derived it.saveMetaItem,runPublishSideEffects, anddeleteMetaItem's repository branch already ran this hook — this closes the same gap for the projector that a prior card (#14179) closed for the mutation-event choke point, at the same four call sites.All four sites now call
this.runMutationProjector(...), awaited BEFORE the existing fire-and-forgetemitMetadataMutationcall — the ordersaveMetaItem's own comment establishes:rollbackMetaItem(after registry write-through)activerevertCommitrestore limbactiverevertCommitsoft-remove limbdeleteddeleteMetaItem's shape)deleteMetaItemlegacy raw-engine exitdeleteddeleteMetaItem's legacy exit also now populatesprojectionAppliedon its success return — the same optional key its repository-branch sibling has carried since ADR-0094 shipped (no new declared field;rollbackMetaItem/revertCommitcarry none, unchanged).Symbol fence honored: touched only
rollbackMetaItem, both limbs ofrevertCommit, anddeleteMetaItem's legacy raw-engine exit inpackages/metadata-protocol/src/protocol.ts. Did not touchlistCommitsor its emitters (fenced to #14038).Zone 2 measurement, quoted with file:line (HEAD
a32f97047)origin/main@d5cbb44f3(the commit the issue cites):runMutationProjectorhad exactly 3 call sites (:15438saveMetaItem,:16671runPublishSideEffects,:20455deleteMetaItem repo branch);emitMetadataMutationhad 7 (including the four target sites at:19435,:19534,:19915,:20625— none preceded by a projector call). All four target sites existed and none already projected.runMutationProjectorisprivate async(protocol.ts, declared ~L4932 pre-diff), best-effort (catches, logs viaconsole.warn, never throws), andMetadataMutationProjector/registerMutationProjectorare a server-side extension seam — not part ofObjectStackProtocol's wire contract (same status asonMetadataMutation). Confirms Clause-②: no.deleteMetaItem's repository branch already makes:this.runMutationProjector({ type: singularTypeForRepo, name: request.name, state: 'deleted', organizationId: orgId })(pre-diff:20455-20460). The legacy exit was missing exactly that call (same shape, same state, org scope fromrequest.organizationId ?? nullinstead of the resolvedorgId— the legacy path's own scoping variable, unchanged from what its existingemitMetadataMutationcall already used).packages/plugins/plugin-security/src/permission-set-projection.test.ts(the one projector actually registered today, forpermission) has zero tests exercising rollback/revert against the projector — this gap is exactly what the issue reports, so there's nothing today relying on the pre-fix silence. Ran the full objectql consumer suite that drives these three doors directly (protocol-commit-history,protocol-delete-object-registry-heal,protocol-lock-enforcement,protocol-org-overlay-registry-gate,protocol-publish-canonical-fold,protocol-publish-rollback,protocol-revert-org-scope,protocol-writepath-object-ownership— 113 tests) plus the sibling emit-mutation, mutation-listener and cluster-fanout suites (53 tests): all green.Tests
New file:
packages/metadata-protocol/src/protocol.recovery-doors-mutation-projector.test.ts— one pin per door (registers a fake projector, asserts it receives the correctstate/bodyand runs before the mutation listener), a positive control (saveMetaItem), negative controls (unregistered type, throwing projector, already-absent row), a wire-shape check (deleteMetaItemlegacy exit'sprojectionApplied), and a structural guard (exactly 7runMutationProjectorcall sites).Ablation (declared and run before landing): removing the
rollbackMetaItemprojector call —git checkout HEAD -- <path>restore, confirmed clean viagit diff HEADempty after — turned exactly 2 of the 10 new tests red (the rollback pin's body assertion, and the 7-call-site structural count dropping to 6), the other 8 green. Matches the file's own docblock claim.Union re-run at HEAD
a32f97047(metadata-protocol + objectql):Gates run (dispatch-derived,
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands, 48 families): all green except two genuinePREREQUISITE NOT MET(exit 3, not a finding) that need a full-treepnpm buildthis local run did not do —check:dual-build-cjs-loadsandcheck:type-check-debt's--re-measure; CI'slint.ymlbuilds the full workspace closure before both.check:engine-double-contractinitially flagged the new test file's engine double as unrecorded coverage — fixed vianode scripts/check-engine-double-contract.mjs --write(3 rows added, 0 lost), re-run green.Companion ADR PR
Per the ruling, the code PR must not land without the ADR-0094 D2 amendment in flight: #14980 (draft,
docs/adr/**governed surface, review requested fromos-zhuangandhotlong, human merge only).Changeset
patch—.changeset/recovery-door-mutation-projector.md(internal projection ordering; no published schema or wire shape moves).🤖 Generated with Claude Code
https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
Generated by Claude Code