Repository navigation
fix(metadata-protocol)!: the adopt-orphans door asks the save door's owner judges (another package owns the object; a permission set naming another owner) before the __c judge - #28808
Conversation
…before the __c judge reassignOrphanedMetadata refuses the adoption whole, before the first storage update, when an active orphan object's name is owned in the registry by another package (ADR-0029 D9.9, OBJECT_OVERLAY_PACKAGE_MISMATCH / 422; the sys_metadata sentinel owner, the orphan's own registration, is skipped) or an active orphan permission set's body packageId disagrees with the target (ADR-0086 D3, VALIDATION_ERROR / 400). Asked in the save door's order, before the __c judge. The namespace-prefix judge is not asked. Co-Authored-By: Claude <noreply@anthropic.com>
… refusals Co-Authored-By: Claude <noreply@anthropic.com>
…ne routes through the engine predicate; ledger learns the file Co-Authored-By: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 20 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 9 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 10 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 34d76d06de6955c1165ba12bab5da21d980e97a9 && git checkout 34d76d06de6955c1165ba12bab5da21d980e97a9
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 3e05ed1ec2ecce09b19a23bd51a6f14afb40caf3 7e37e4c238a4b6c748ff0ae1466b01e2d80680a4 && git checkout -B drift-repro 3e05ed1ec2ecce09b19a23bd51a6f14afb40caf3 && git merge --no-ff 7e37e4c238a4b6c748ff0ae1466b01e2d80680a4
node scripts/docs-audit/affected-docs.mjs --json 3e05ed1ec2ecce09b19a23bd51a6f14afb40caf3
|
…refused whole, nothing rebound, the owner stays Co-Authored-By: Claude <noreply@anthropic.com>
|
domain:server#1 · os-project-manager · session_01R1q2xdtYgU6FKrZ7LgjPCC Contract reviewServed-tier: Read at 2026-11-01T22:37Z: card #28776 (body and all 4 comments: the claim Check-runs on ① Derived judgmentsThe diff moves one method,
② Semver level
③ Boundary flags
④ Design judgmentThe narrowing, read against the published spec on Does it take a declared capability away at the consumer? No. The published surface for this door is Does it dead-end D5 / D11? Tried and no. The break would be the sentinel: every plain orphan object is registered under Is the judge asked at the right binding and time? Yes. The save door's D9.9 block judges "the package the write lands in" and the permission judge "the resolved binding of the row the write lands in"; an adoption lands every row in the target, so Could a refused adoption be worked around through another door? Tried: the save door refuses the same object row naming T (D9.9) and the same set body naming T (D3); the publish door refuses a draft of either at promotion; The rewritten control (brief's question): it pins the real-kernel refusal and the registry invariant together; a flipped expectation would have kept Findings:
Implemented-by: Rendered by the seat's subagent at 2026-11-01T22:37Z, from the card, the PR, the net diff and the head's check-runs only; no local run. VERDICT: PASS Generated by Claude Code |
|
domain:server#1 · os-project-manager · session_01R1q2xdtYgU6FKrZ7LgjPCC 席位核对 · ACCEPT ·
|
| item | reading |
|---|---|
| Shape | Draft, base main, Fixes #28776. 5 files: the owner-judge loop in reassignOrphanedMetadata (protocol.ts), the new protocol.adopt-orphans-owner-judges.test.ts, the #23646 control in protocol-adopt-orphans-object-registry.test.ts moved to the refusal, the engine-double ledger, and a minor changeset with the BREAKING banner. No export, no packages/spec/**, no new error code, no gate. |
| Rule | The adopt door asks the save door's two owner judges (D9.9 object owner, D3 permission-set packageId) against the target, in the save door's order and before #28735's __c judge, with the sys_metadata sentinel owner excluded. The first refusal refuses the adoption whole: no row moves. The namespace-prefix judge stays out, per the seat's ruling, on Leehom's open decision card. |
| Should-fix | The review's one should-fix (PR body only) is done: the round-2 Acceptance-notes line, with the 7e37e4c2 verification numbers, was pasted into the PR body before this ACCEPT. |
| Evidence | CI green on head 7e37e4c2 (filter=latest: 37 success, 15 skipped, 0 failed); df5ce323's Test Core 7/8 red is cleared. The r2 report shows the metadata-protocol, objectql and runtime suites green, 70/70 derived gates and dogfood green. |
| Landing | 「按照 pm-dispatch 规则复核过的都授权」 (Leehom, 2026-10-25T11:35Z): ready and auto-merge through the relay, with every check green or an expected skip. |
Generated by Claude Code
domain:server#1 · os-project-manager · session_01R1q2xdtYgU6FKrZ7LgjPCC
Requested by Leehom · project thread
Fixes #28776
Clause-②: no (narrowing)
Metadata-protocol: narrowed
Skills-docs: none — no file under
skills/**teaches the adopt-orphans door (0 hits foradopt-orphansandreassignOrphanedMetadata).维护者速读
POST /api/v1/packages/:id/adopt-orphans) 现在也问保存门的两个「归属」判官,顺序和保存门一致(先归属,再 metadata-protocol: the adopt-orphans door rebinds a package-lessx__cfield into a plain package without the__cjudge (#28564 follow-up) #28735 加的__c)。两种情况整次收养被拒、一行都不动:一个不属于任何包的对象,名字已被另一个包占有(422OBJECT_OVERLAY_PACKAGE_MISMATCH);一个不属于任何包的权限集,正文packageId写的是别的包(400VALIDATION_ERROR)。普通孤儿对象照常收养。sys_metadata,测试夹具里 0 个会被拒的可收养行。不新增错误码,不动 spec。命名空间前缀判官不在本 PR:它会拒掉几乎所有真实孤儿对象,按席位裁决交给你定(拒绝、收养时改名,还是不判)。回滚即 revert。packages/metadata-protocol/src/protocol.ts的reassignOrphanedMetadata(__c块之前新加的归属判官循环,注意跳过sys_metadata哨兵主人那一句);packages/metadata-protocol/src/protocol.adopt-orphans-owner-judges.test.ts。pnpm --filter @objectstack/metadata-protocol exec vitest run src/protocol.adopt-orphans-owner-judges.test.ts,预期Tests 6 passed (6)(main 上 3 条红,3 条对照两边都绿)。What changes
reassignOrphanedMetadata). After the orphan list is read, and before metadata-protocol: the adopt-orphans door rebinds a package-lessx__cfield into a plain package without the__cjudge (#28564 follow-up) #28735's__ccall and the first storageupdate, every ACTIVE orphan row is judged against the TARGET package, in the save door's order:classifyObjectContribution(name, target). Amismatchwhose owner is ANOTHER package throwsoverlayPackageMismatchError(name, target, owner):422 OBJECT_OVERLAY_PACKAGE_MISMATCH, the save door's message ("Cannot layer object 'N': the overlay is bound to package 'T', but the object is owned by package 'O'. …"). ⛔ Amismatchwhose owner is the'sys_metadata'sentinel is skipped. That sentinel is the orphan's own registration, the ownerreleaseAdoptedObjectOwnerhands over. Asked unchanged, the judge refuses every object adoption, as metadata-protocol: the adopt-orphans door asks neither the namespace-prefix nor the owner judge (measure reach, then rule) #28776's step-1 measurement showed on a real CRM boot.permissionSetOwnerBindingRefusal(type, body, name, target, 'save'):400 VALIDATION_ERROR,issues: [{ path: 'packageId' }], the save door's words. An unparseable body is left to the loop.applyRegistryWriteThroughnever runs.x__cfield into a plain package without the__cjudge (#28564 follow-up) #28735 left the__cjudge.6563043440), that question goes to the maintainer.reassignOrphanedMetadata's header says so..changeset/28776-adopt-orphans-owner-judges.md:minoron@objectstack/metadata-protocol(kindnarrowed), BREAKING banner, both declaration lines, migration paragraph, ADR-0087not-required (no-migration-prescription).packages/spec/**, no ui, no new gate.Why
Metadata-protocol: narrowed. An adoption that admitted these two rows is now refused: one more write door applies two requirements the save door already enforces. That is the same reading as #28735.Tests
New
packages/metadata-protocol/src/protocol.adopt-orphans-owner-judges.test.ts, 6 tests. The engine double stores rows and answersregistry.getObjectOwnerfrom a table the test sets; the registry write-through is spied out.probe_clean(sentinel-owned) besideprobe_taken(owned byapp.probe-other), adopted intoapp.probe-target:OBJECT_OVERLAY_PACKAGE_MISMATCH/ 422, message naming both packages.engine.updateand the write-through are never called, and both rows stay package-less.probe_takencarryingx__canswers the owner refusal (422), not the__cone (400).reassignedCount: 2).packageId: 'app.probe-other', beside a clean object:VALIDATION_ERROR/ 400,issues[0].path = 'packageId', message "DroppackageIdfrom permission set …". Nothing moves.Red on base, green on head; ablations
protocol.tsfrom8f294e04, the new file at head):Tests 3 failed | 3 passed (6). The three refusal tests are red: the two D9.9 tests and the D3 test. The three controls are green both ways. Restoregit checkout HEAD -- protocol.ts,git diff HEADempty.Tests 6 passed (6); withprotocol.extension-field-suffix.test.tsandprotocol.reassign-orphans-durability.test.ts: 35/35.node scripts/ablation-replace.mjsfrom committed HEAD (the suite readssrc):&& (globalThis as any).ABLATE_28776_OBJ === 1): blobbc2fc3da1611→47279aca0276, 2 failed (the two D9.9 tests), restored blob == HEAD, diff empty.void ownerRefusal;): →38214b741154, 1 failed (the D3 test), restored clean.if (layer.kind === 'mismatch')): →a96b1a46d286, 3 failed. The red is the sentinel pin, plus the two refusal tests whose sentinel-ownedprobe_cleanis now refused first with the wrong owner. Restored clean.Acceptance notes
packages/objectql/src/protocol-adopt-orphans-object-registry.test.ts(metadata-protocol: an adopt-orphans into a package rebinds a runtime object's row in storage, but the registry keeps the sys_metadata owner and the old definition until a restart (ADR-0070 D5) #23646): its CONTROL "a package-less row of an object another package's row owns is rebound in storage, and that owner stays" pinned the rebind this PR refuses. It now reads "[metadata-protocol: the adopt-orphans door asks neither the namespace-prefix nor the owner judge (measure reach, then rule) #28776] … the adoption is refused whole (D9.9), nothing is rebound, and that owner stays":OBJECT_OVERLAY_PACKAGE_MISMATCH/ 422 naming the owner, both rows keep their bindings, and the metadata-protocol: an adopt-orphans into a package rebinds a runtime object's row in storage, but the registry keeps the sys_metadata owner and the old definition until a restart (ADR-0070 D5) #23646 half (the owner stays: noregisterObject/unregisterObject,getObjectOwnerand the served definition unchanged) is kept. Head7e37e4c2: metadata-protocol 22074, objectql 9983, runtime 8232 passed; 70/70 derived gates; dogfood 3114 passed.pm-orders/server/28776-step1-measurement.md. Fresh CRM and showcase boots store 0sys_metadatarows. The 9sys_metadataseed sites inpackages/qa/**hold 0 adoptable active rows either judge refuses. A control orphan saved through the real door showed the prefix judge refusing it into every installed target.'save'door verb, because the spec'sPermissionSetOwnerBindingDoorhas no adopt verb andpackages/spec/**is out of scope. The sentence reads "DroppackageIdfrom permission set 'N' …, then send it again. Nothing was written. Its body declarespackageId'X', but this save writes it into package 'T' …": the remedy is right for an adopt, and "this save" names the act loosely.releaseAdoptedObjectOwner's "another package owns it" row (answersfalse, leaves the row rebound in storage only) is no longer reached in the ordinary run, since such an adoption is now refused first (and a draft never reaches it). It is left as it is, as the answer when the registry's owner changes between the judge and the loop, which the loop'sawaits allow.Verification
All readings at head
df5ce323(base8f294e04; the fix01a8dcb8, changeset27886745, anddf5ce323, which routes the test double'sfindOnethroughassertEngineFindOnePredicateand adds the file toscripts/engine-double-contract.pinned.jsonvia--write, the gate's own prescription). Fullpnpm turbo run build --concurrency=2at27886745: 118/118; metadata-protocol rebuilt atdf5ce323.Package tests, typecheck, lint
pnpm --filter @objectstack/metadata-protocol test:Test Files 296 passed | 4 skipped (300),Tests 22058 passed | 26 skipped (22084)(lock VERDICT command-exit 0).pnpm --filter @objectstack/metadata-protocol typecheck: exit 0 (tests included;check:type-check-debtOK, 0 packages hiding tests).eslint --no-inline-configonprotocol.tsand the new test file: exit 0.Full dogfood battery (the `(narrowing)` arm)
df5ce323:pnpm --filter @objectstack/dogfood testunder the lock, VERDICT command-exit 0,Test Files 378 passed | 3 skipped (381),Tests 3105 passed | 28 skipped (3133). No failure.Derived gates (`dispatch-gates.mjs --commands`, 69 families)
df5ce323;dispatch-gates --ran:69 derived, 69 run, 0 NOT-MEASURED, 0 UNRUN.check:type-check-debt:OK — 126/129 workspace packages type-checked,--re-measure: OK.27886745readcheck:engine-double-contractexit 1: the new double'sfindOnedid not route through the predicate, and the ledger did not know the file. Both were fixed indf5ce323and the gate is green there. That first pass and its dogfood run were stopped and re-run whole ondf5ce323.pnpm lintand the CI-only families are CI's; see the PR's check-runs.Generated by Claude Code