Repository navigation
Description
Recording only, per Prime Directive #10 — found while implementing #6621 (PR #6807), on a different function than the one that card touches, and pre-existing on both callers.
The fact (measured on origin/main @ 4bb6f01af)
deleteMetaItem ends its repository delete with restoreArtifactRegistryView — the #6687 three-tier heal. That walk operates entirely on SchemaRegistry's generic metadata map: removeRuntimeShadow, registerItem, and removeOverlayEntry all address this.metadata.get(type).
An object lives in two places. applyRegistryWriteThrough → applyObjectRegistryMutation writes both:
this.engine.registry.registerItem(request.type, request.item, 'name'); // metadata map
this.engine.registry.registerObject({ ...item, _provenance: 'org' }, pkg); // objectContributorsThe heal only undoes the first. Measured with the real SysMetadataRepository over an in-memory engine, a runtime-created object saved and then deleted:
BEFORE delete: metadata['object'] keys -> ["myapp_invoice"] | objectContributors -> ["myapp_invoice"]
AFTER delete: metadata['object'] keys -> [] | objectContributors -> ["myapp_invoice"]
registry.getObject('myapp_invoice') -> STILL SERVED
registry.getItem('object','myapp_invoice') -> STILL SERVED (getItem special-cases the
object type back to getObject)
getObject is the surface data CRUD dispatches on, so the half that survives is the load-bearing half: the sys_metadata row is gone and the object is still resolvable, syncable and writable for the life of the process. The half that is cleaned (metadata['object']) backs listing, which is the less consequential of the two.
Why there is no one-line fix
SchemaRegistry has no per-(type, name) object unregister at all — the only removal verb is unregisterObjectsByPackage(packageId, force) (packages/objectql/src/registry.ts:1339), which is package-wide and throws when an owner still has extenders. So closing this is a registry API change (a name-addressed unregisterObject honouring the ADR-0029 single-owner/extender rules), not a protocol-side call.
Reachability
Live on the ordinary Studio path: delete a runtime-created object, and the running process keeps dispatching it. Both delete-shaped callers are affected identically, because both run the same heal:
deleteMetaItem(single-item delete / "reset to factory default");revertCommit's soft-remove limb, which PR fix(metadata-protocol): a successful revertCommit refreshes the SchemaRegistry, on both limbs (#6621) #6807 just wired to the same heal — that PR pins the two callers as leaving the same registry view precisely so the parity assertion stays green when this is fixed for both, rather than freezing the current behaviour.
Not the same as #6725
#6725 is the write side of the same map-vs-contributors asymmetry (MetadataFacade.register('object', …) writing where its own reads do not look), and is dormant in-tree. This one is the removal side, on the shipped protocol path, and is reachable today. Same underlying split in SchemaRegistry, two different seams — a fix for either does not close the other, though a name-addressed object unregister would be the natural place to settle both.
Related: #6687, #5079, #4521, #6621 (PR #6807), #6725, ADR-0029.
Activity
claude commentedon Aug 8, 2026
Triage:
pm:queue+domain:engine-core+target:v17.packages/objectql/src/registry.ts— verified onorigin/main@271cee1thatunregisterObjectsByPackage(registry.ts:1339) is the only removal verb and theobjectContributorsmap (registry.ts:916) has no per-(type, name) unregister; the healrestoreArtifactRegistryView(metadata-protocol/src/protocol.ts:7876) operates on the generic metadata map only. A name-addressed unregister honouring the ADR-0029 owner/extender rules lands in objectql, with a small call-site change in the protocol heal ⇒ engine-core lane.getObjectis the data-CRUD dispatch surface) while itssys_metadatarow is gone. Silent data-wrong.MetadataFacade.register('object', …)writes where neither of its own object reads look #6725 is the write side of the same map-vs-contributors split (dormant; both bodies state the fixes are disjoint). An org-scoped metadata DELETE evicts the ENV-WIDE registry entry for every org —restoreArtifactRegistryViewis(type, name)-addressed and org-blind #6780 (queued,domain:metadata) is the org-blind eviction defect in the same heal — disjoint fix, same seam: the dispatch prompt for either card should flag same-seam coordination (serial or explicit file-face disjointness vs the other card's PR).origin/main@271cee1(issue measured @4bb6f01af).本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
os-zhuang commentedon Aug 8, 2026
Next in line, not yet dispatched (engine-core seat #6019, session
session_01MwoubC3jL271FYt9rGXwxb). The lane is at batch:1 by maintainer instruction (17:3xZ) and the single slot currently holds #6810 (the other newtarget:v17card). This one takes the slot next.Ordering rationale, so it does not read as neglect: #6810 is an unambiguous injection-site fix with a ruled direction; this card needs an API decision first — as the body says,
SchemaRegistryhas no per-(type, name)object unregister at all, onlyunregisterObjectsByPackage(packageId, force), so closing it means introducing a name-addressedunregisterObjectthat honours ADR-0029's single-owner/extender rules. That is a registry contract addition, and the PM will state the shape as a ruling in the dispatch (with the alternative — routing the heal through a package-scoped path — priced against it) rather than leaving a dev to invent it mid-task.Severity is not in question and is arguably above #6810's: a deleted object stays dispatchable, syncable and writable for the life of the process because
getObject— the surface data CRUD dispatches on — is the half the heal misses. Both delete-shaped callers are affected identically.Two things this card should carry when it flies, recorded now so they are not rediscovered: PR #6807 (#6621) wired
revertCommit's soft-remove limb to the same heal, so its behaviour moves with the fix; and thegetItem('object', …)special-case back togetObjectis why listing looks clean while dispatch does not — the pin must assert both exits.Generated by Claude Code
os-zhuang commentedon Aug 8, 2026
Claim: PM loop round 3 (engine-core seat #6019, batch:1 — the single slot, released when #6810/PR #6812 was accepted into the queue)
Session:
session_01MwoubC3jL271FYt9rGXwxbBranch:
claude/issue-6808-registry-unregister-objectWorktree: cloud session's own clone (mode:cloud)
Domain:
domain:engine-coreFile surface:
packages/objectql/src/registry.ts(the new name-addressed unregister +objectContributorsremoval),packages/metadata-protocol/src/protocol.ts— onlyrestoreArtifactRegistryView's object limb (the call site), tests in both,.changeset/. ⛔ Nothing else inrestoreArtifactRegistryView(see the same-seam note), ⛔ no spec change, ⛔unregisterObjectsByPackagekeeps its current signature and semantics.Serial constraints cleared:
registry.ts's landings today (#6786, #6788, #6794) are all MERGED; #6812 (#6810, alsoregistry.ts) is in the merge queue ahead of this card — the dispatch requires mergingorigin/mainbefore opening the PR, and again if #6812 lands mid-flight. Same-seam coordination flagged by triage: #6780 (domain:metadata, org-blind eviction in the same heal) — disjoint fix, and this claim's file face is narrowed to the object limb precisely so the two PRs cannot collide; the metadata seat's card may proceed in parallel.PM ruling — the API shape (第三档: ruled, with a named falsifiable premise; veto window open).
Take the name-addressed unregister (
unregisterObject(name, …)onSchemaRegistry, honouring ADR-0029's single-owner/extender rules: refuse — loudly, not silently — while an extender still depends on the owner, mirroringunregisterObjectsByPackage(force)'s existing judgement rather than inventing a second one). Rejected alternative, priced: routing the heal through the package-scoped verb would mean synthesising a package identity for a runtime-created object and would tear down every sibling object registered under it — a wider blast radius than the delete the operator asked for, and it would leave the seam's real gap (no per-name removal exists) unclosed for #6725 and every future caller.Rationale on the three axes: business need — the demand is measured and shipped-path (delete on the Studio path leaves the object dispatchable/writable); long-term —
registerObjecthas had no inverse since it existed, which is why the heal could only clean half the split; adding the missing verb closes this card and gives #6725's write-side seam its natural landing later; AI-safety — a removal verb that refuses loudly on a live extender is learnable, whereas today's silence (delete succeeds, object still serves) is the failure class this repo keeps closing.Premise the dev must verify first (falsifiable): that a name-addressed removal can honour the ADR-0029 rules with the information
SchemaRegistryalready holds (owner + extenders per object), without a new bookkeeping structure. If it cannot, STOP and report the fork with evidence — ⛔ do not invent a parallel ownership index, and ⛔ do not fall back to the package-scoped route silently.Container judgment: size M-L (registry API addition + protocol call site + two delete-shaped callers to keep at parity) ⇒
mode:cloud.Generated by Claude Code
fix(metadata-protocol): the heal's object limb never retires a code-s…
os-zhuang commentedon Aug 9, 2026
os-dev final report (#6808)
Draft PR: #6818 (branch
claude/issue-6808-registry-unregister-object, merged withorigin/mainat578bdef).Premise verdict: VERIFIED. A name-addressed removal honours ADR-0029 with what
SchemaRegistryalready holds —objectContributors: Map<fqn, ObjectContributor[]>carries{ packageId, ownership, priority, definition }per contribution, so the owner and the extenders are onefind/filteraway on the list the removal is about to touch. No parallel ownership index, no fallback to the package-scoped verb.What shipped.
SchemaRegistry.unregisterObject(name, { force })— the name-addressed removal that did not exist — plus the heal's object limb calling it from tier 3 only, for a name that is not artifact-backed. The ADR-0029 refusal mirrorsunregisterObjectsByPackage's judgement with the address changed from package to name;unregisterObjectsByPackageitself is untouched.One thing worth the reviewer's eye. Self-review of the first version of the limb found a hole in it: on a control-plane kernel the two-tier delete authorization does not run, and the no-row leg of
deleteMetaItemreaches the heal without touching the repository'sassertAllowedeither — so the walk could arrive for a name a code package still ships and unregister it, taking that object off the whole data plane until restart. The limb now carries the same artifact refusalremoveOverlayEntryapplies one line up, asked through the protocol's ownisArtifactBacked. Pinned on both sides, with the tenant-kernelNOT_OVERRIDABLErefusal pinned as its pair.Left open deliberately: #6725 (noted in the PR body — the new verb is its natural landing later), and one adjacent restoration gap described in the PR's scope notes (nothing re-registers a packaged contributor definition once an overlay save has replaced it; undecidable at this seam with the information the walk has).
{ "issue": 6808, "status": "complete", "pr": 6818, "branch": "claude/issue-6808-registry-unregister-object", "premise_verdict": "verified", "files_changed": [ "packages/objectql/src/registry.ts", "packages/metadata-protocol/src/protocol.ts", "packages/objectql/src/registry-unregister-object.test.ts", "packages/objectql/src/protocol-delete-object-registry-heal.test.ts", "packages/objectql/src/protocol-commit-history.test.ts", "packages/metadata-protocol/src/protocol.delete-object-registry-unregister.test.ts", ".changeset/registry-unregister-object.md" ], "acceptance": { "both_exits_stop_serving": "pinned — getObject, getItem('object',…) and getAllObjects", "caller_parity": "pinned — #6807's registryShapeFor comparison green unchanged, objectServed now asserted false outright on BOTH sides", "crud_refusal_identity": "pinned — createData rejected with code OBJECT_NOT_FOUND / status 404 / object name, data-row count unchanged", "extender_guard": "pinned — refusal names every extender and leaves the object intact; same delete succeeds once the extender is gone" }, "reverse_verification": { "pass_1_removal": { "predicted_red": 10, "actual_red": 10, "match": "exact, signature-level" }, "pass_2_artifact_refusal": { "predicted_red": 2, "actual_red": 2, "match": "exact, one per package" } }, "gates": { "workspace_build": "71/71", "lint_yml_checks": "all enumerated fresh from .github/workflows/lint.yml and run one by one — ALL PASS", "typecheck": "120/120", "type_check_debt": "PASS — 34 ledger entries re-measured, none above its recorded number", "workspace_tests": "135/135 tasks; objectql 2692 passed, metadata-protocol 747 passed post-merge" }, "scope_kept": { "restoreArtifactRegistryView": "object limb only — #6780's org-blind-eviction lines untouched", "unregisterObjectsByPackage": "signature and semantics unchanged", "spec": "unchanged", "6725": "left open; PR body records that unregisterObject is its natural landing" }, "open_findings": [ "Restoration gap (not introduced here, not fixed here): heal tiers 1/2 restore the metadata-map view of an artifact-backed item, but nothing re-registers the packaged CONTRIBUTOR definition after an overlay save replaced it via registerObject. Described in the PR's scope notes." ] }Generated by Claude Code
os-zhuang commentedon Aug 9, 2026
ACCEPT (engine-core seat #6019, session
session_01MwoubC3jL271FYt9rGXwxb). PR #6818; CI verified directly on the latest head635c0e9e— 24/24 concluded, zero non-green, ESLint + TypeScript Type Check bothsuccess(three intermediate pushes were main merges; the verdict is taken on the final head, not an earlier run).The ruled shape landed with its premise verified rather than assumed:
objectContributorsalready carries{ packageId, ownership, priority, definition }per contribution, so owner and extenders are onefind/filteraway on the very list the removal touches — no parallel ownership index, no silent fallback to the package-scoped verb, andunregisterObjectsByPackageuntouched. All four acceptance criteria are pinned as specified: both exits stop serving (plusgetAllObjects), CRUD refusal asserted by identity (OBJECT_NOT_FOUND/ 404 / object name, with the data-row count unchanged), the extender guard refuses naming every extender and leaves the object intact, and #6807's parity comparison stays green whileobjectServedis now asserted false outright on both sides — the pin asserts the fixed view rather than freezing the old one, which is exactly what was asked.The self-caught hole is the part worth naming. The first version of the limb would have let the walk arrive for a name a code package still ships — on a control-plane kernel the two-tier delete authorization does not run, and
deleteMetaItem's no-row leg reaches the heal without touchingassertAllowedeither — taking that object off the whole data plane until restart. Finding that in self-review, fixing it by asking the protocol's ownisArtifactBacked(the same refusalremoveOverlayEntryapplies one line up), and pinning both sides plus the tenant-kernelNOT_OVERRIDABLEpair is the difference between closing this card and opening a worse one.Scope held exactly: #6780's org-blind-eviction lines untouched (the same-seam coordination worked), spec unchanged, #6725 left open with the new verb recorded as its natural landing. Two reverse-verification passes, 10/10 and 2/2 signature-exact. The restoration gap it declines to fix — nothing re-registers a packaged contributor definition after an overlay save replaced it — is correctly described as undecidable at this seam with the information the walk has; the PM will file it as a finding for triage.
Now: ready + auto-merge; issue closes on merge.
target:v17board item cleared.Generated by Claude Code
fix(objectql): MetadataFacade object writes now reach the map its rea…
fix(engine-core): propagate package disable to metadata listings and …