fix(metadata-protocol): a successful revertCommit refreshes the SchemaRegistry, on both limbs (#6621) - #6807
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 4 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
This was referenced Aug 8, 2026
os-zhuang
marked this pull request as ready for review
August 8, 2026 19:08
This was referenced Aug 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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@4bb6f01afwith the realSysMetadataRepositoryover this file's own in-memory double — anobjectsaved twice (v2 adds adue_datefield), then reverted:Reproduced verbatim, ownership survival included. Two further measurements the card predicted and this branch confirms:
objectoverlay at all — it passes no write intent, so restoreVersion defaults tooverride-artifactand the repository answers NOT_OVERRIDABLE #6563. An overlayviewon a control-plane kernel splits the same way — storedCases, registry stillRenamed. Overlay types have always been able to reach this limb;objectmerely makes it loud, because the registry copy is what data CRUD dispatches on.objectansweredsuccess: true, left zerosys_metadatarows for the name, and the registry kept serving it. Same for a createdview.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, androllbackToPackageCommitreverts 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:resolveOverlayPackageBinding— and read before the restore, per loadMetaFromDb object branch readsrecord.packageIdfrom a snake_case row — always undefined, every object overlay registers under the 'sys_metadata' sentinel at boot #4636's own reasoning: the row exists at that point, so a read failure still fails that item cleanly intofailed[]instead of putting a fallible query downstream of a write that already succeeded ([metadata-protocol] SysMetadataRepository 的 nextEventSeq()/nextItemVersion() 同样把读失败当「表还没建」,静默从 1 重新发号 —— #4825 在 canonical 路径上的同形缺陷 #4867).organizationIdis the row's own org, per item.applyRegistryWriteThroughnow requires it (PR fix(metadata-protocol): an org-scoped overlay row no longer reaches the process-wide SchemaRegistry (#6602) #6779), so an org-scoped row inherits An org-scoped overlay row reaches the process-wide SchemaRegistry on unscoped kernels — both the write-through and the read hydration gate only onenvironmentId, never on org #6602's refusal automatically rather than by a decision re-taken here.saveMetaItem's own write-through registers under — while the repo-facing reads keepit.type, the spelling the row is stored with. Two different keys, deliberately.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 (
removeOverlayEntryat 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), notnull.Chosen — reuse
deleteMetaItem's heal (restoreArtifactRegistryView, the #6687 three-tier walk). The soft-remove limb performs literally the samerepo.delete(ref, …)thatdeleteMetaItemperforms, anddeleteMetaItemfollows 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
hydrateOverlayIntoRegistryowns), 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 fromassertObjectRegisteredfailing 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 onobjectand on an overlay type; ownership survival (getObjectOwnerunchanged, noalready owned by packageclash); the org-scoped restore under #6602;rollbackToPackageCommitinheritance; 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
restoreVersionreturned{}whereSysMetadataRepository.restoreVersiondeclaresPutResult, and the stub engine carried noregistryat all. A double that answers less than the contract makes the caller look broken.The soft-remove pins assert parity with
deleteMetaItemrather 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
packageId(#4636)Object "myapp_invoice" is already owned by package "app.myapp"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 lintclean, plus all 36 gates enumerated one by one from.github/workflows/lint.yml(includingcheck:engine-double-contract,check:route-envelope,check:error-code-casing,check:nul-bytes) and the newcheck:adr-links. Re-run in full after mergingorigin/main.Scope
packages/metadata-protocol/src/protocol.ts(revertCommitregion only), the pin suite, one changeset.origin/mainmerged before opening; no overlap with #6562'sgetMetaItem/getMetaItemsread 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:restoreArtifactRegistryViewreaches themetadatamap but notobjectContributors, so a soft-removed runtimeobjectis still served bygetObject— the surface data CRUD dispatches on.SchemaRegistryhas no per-name object unregister at all, onlyunregisterObjectsByPackage, so closing it is a registry API change.deleteMetaItemcalls the heal ungated by org, which is the un-hydration counterpart of the hole An org-scoped overlay row reaches the process-wide SchemaRegistry on unscoped kernels — both the write-through and the read hydration gate only onenvironmentId, never on org #6602 closed on the two hydration seams.Generated by Claude Code