Skip to content

fix(metadata-protocol): a successful revertCommit refreshes the SchemaRegistry, on both limbs (#6621) - #6807

Merged
os-zhuang merged 4 commits into
mainfrom
claude/issue-6621-revert-registry-refresh
Aug 8, 2026
Merged

fix(metadata-protocol): a successful revertCommit refreshes the SchemaRegistry, on both limbs (#6621)#6807
os-zhuang merged 4 commits into
mainfrom
claude/issue-6621-revert-registry-refresh

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #6621

The premise, re-measured on today's base

The issue's measurement was taken on #6563's branch, before PR #6768 and PR #6779 landed. Re-measured on origin/main @ 4bb6f01af with the real SysMetadataRepository over this file's own in-memory double — an object saved twice (v2 adds a due_date field), then reverted:

revertCommit                          ->  { success: true, revertedCount: 1, failed: [] }
stored sys_metadata row fields        ->  ["name","amount"]                 # reverted
SchemaRegistry.getObject(...) fields  ->  [...,"name","amount","due_date"]  # NOT reverted
getObjectOwner(...).packageId         ->  "app.myapp"   (_provenance: "org")

Reproduced verbatim, ownership survival included. Two further measurements the card predicted and this branch confirms:

rollbackMetaItem — the single-item revert over the same repository call — has ended its restore with a registry write-through since #4521, and says why in that file: "a rollback is a live write like any other: the restored body must be the one the runtime dispatches on immediately, not after someone lists the type." One seam over, the rule was simply missing, and rollbackToPackageCommit reverts through the same loop, so a whole-package rollback could report success and change nothing the running process could see.

Restore limb

Mirrors rollbackMetaItem, with the signature PR #6779 shipped this morning:

Soft-remove limb — the decision, and why it is not "obviously yes"

The card flagged this as a decision to make with evidence rather than by omission. Three options were measured, not argued:

Rejected — do nothing. Not a neutral default: it is the measured defect above. A first-build undo reports success, removes the rows, and keeps dispatching the removed bodies until restart.

Rejected — a flat unregister (removeOverlayEntry at the seam). Measured to be actively wrong: with a packaged artifact under the reverted overlay, this deletes a name a code package still ships. The pinned case shows the correct outcome is the artifact coming back (Packaged), not null.

Chosen — reuse deleteMetaItem's heal (restoreArtifactRegistryView, the #6687 three-tier walk). The soft-remove limb performs literally the same repo.delete(ref, …) that deleteMetaItem performs, and deleteMetaItem follows it with this walk. The tiers are the whole point: an overlay shadowing a packaged artifact falls back to the artifact (tier 1, ADR-0005 reset), and only a name no layer serves at all is retired (tier 3, #5079). All three delete/revert callers now run the same walk, exactly as all three already derive the same per-item intent.

No contract change, so no needs_decision. ADR-0067 §5 already defines what a soft-remove leaves behind (metadata row deleted, table kept, recoverable). This makes the in-memory view agree with that state without waiting for a restart; it adds no promise ADR-0067 does not already make.

The org gate on the heal is asymmetric with the write-through's object branch, on purpose. Only an env-wide revert may mutate the registry every org in the process shares. An org-scoped row never entered it (ADR-0005, the rule hydrateOverlayIntoRegistry owns), so healing on its behalf would retire or un-shadow the env-wide row's entry. The object branch of the write-through is deliberately not org-gated, and that carve-out does not transfer: it is argued from assertObjectRegistered failing closed, which licenses registering broadly and never retiring broadly. Register wide, retire narrow — and the reverse verification below shows this gate is load-bearing, not decorative.

Tests

packages/objectql/src/protocol-commit-history.test.ts, extended in place on #6768's #6620 fixtures. Nine new pins: restore limb on object and on an overlay type; ownership survival (getObjectOwner unchanged, no already owned by package clash); the org-scoped restore under #6602; rollbackToPackageCommit inheritance; and the soft-remove decision pinned in the direction decided, including the artifact fall-back and the org gate.

Two pre-existing doubles were corrected rather than worked around: the stub restoreVersion returned {} where SysMetadataRepository.restoreVersion declares PutResult, and the stub engine carried no registry at all. A double that answers less than the contract makes the caller look broken.

The soft-remove pins assert parity with deleteMetaItem rather than a literal registry state. That is deliberate and keeps the pin honest about a gap it does not close (see below): parity stays green when the gap is fixed for both callers.

Reverse verification — direction predicted before each run, four ways

# Change Predicted Measured
1 Delete the restore write-through RED on the object, overlay and rollback-inheritance pins; GREEN on ownership + org 3 failed / 29 passed, exactly those three
2 Keep it, drop only packageId (#4636) RED on ownership and the object body pins 3 failed, carrying Object "myapp_invoice" is already owned by package "app.myapp"
3 Delete the soft-remove heal RED on the three soft-remove pins; GREEN on the org one 3 failed / 29 passed, exactly those three
4 Keep the heal, drop its org gate RED on the org-scoped soft-remove pin only 1 failed: expected null to be 'EnvWide'

Run 4 is the evidence for the asymmetric gate: ungated, org A's undo deleted the env-wide entry every other organization reads.

Two pins are honestly reported as not red-on-removal, rather than dressed up as coverage. Ownership survival stays green in run 1 — with nothing re-registered there is no clash to detect; it is a must-not-regress guard, and run 2 is the direction that actually exercises it. The org-scoped restore was green before the fix too, for the empty reason that nothing was written through at all; it goes red only on passing an org other than the row's own, which is what PR #6779's required parameter exists to prevent. Both notes are written into the test file.

Verification

  • pnpm --filter @objectstack/metadata-protocol --filter @objectstack/objectql test — 62 files / 700 tests and 150 files / 2602 tests, all passing (2593 → 2602 = the 9 new pins).
  • pnpm exec turbo run typecheck --filter='./packages/*' — 70/70 successful.
  • pnpm lint clean, plus all 36 gates enumerated one by one from .github/workflows/lint.yml (including check:engine-double-contract, check:route-envelope, check:error-code-casing, check:nul-bytes) and the new check:adr-links. Re-run in full after merging origin/main.

Scope

packages/metadata-protocol/src/protocol.ts (revertCommit region only), the pin suite, one changeset. origin/main merged before opening; no overlap with #6562's getMetaItem/getMetaItems read exits.

Out of scope, filed separately

Two seams found while measuring, both pre-existing and shared with deleteMetaItem, neither introduced here, and both outside this card's file surface:


Generated by Claude Code

@vercel

vercel Bot commented Aug 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 8, 2026 6:59pm

Request Review

@github-actions github-actions Bot added the size/l label Aug 8, 2026
@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/metadata-protocol.

4 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/concepts/metadata-lifecycle.mdx (via @objectstack/metadata-protocol)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/metadata-protocol)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/metadata-protocol)
  • content/docs/releases/v9.mdx (via @objectstack/metadata-protocol)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Aug 8, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review August 8, 2026 19:08
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 8, 2026
Merged via the queue into main with commit 271cee1 Aug 8, 2026
25 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-6621-revert-registry-refresh branch August 8, 2026 19:19
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/l tests tooling

Projects

None yet

2 participants