Repository navigation
fix(metadata)!: a package-less object-bound action row is keyed per (object, name) on the stored side, so a package-less save never lands in another object's holder - #33318
Conversation
… (object, name) on the stored side DatabaseLoader's save keys rows as the in-memory store keys items (storeRowSlotKey), so storeRowWriteKey counts the holders of the written action's (object, name), never every package holding the name. Its slot read hands a package-less row bound to an object exactly one package binds the name to (under a name two packages hold) on as that package's customization, first in its slot, as the boot pass files it. Claude-Session: https://claude.ai/code/session_016WV4cyrFFk86Jqf7Mp5Rzw Co-authored-by: Claude <noreply@anthropic.com>
…es not carry Claude-Session: https://claude.ai/code/session_016WV4cyrFFk86Jqf7Mp5Rzw Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 16 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 344dfb668826d42ae886163477947fe1588170d4 && git checkout 344dfb668826d42ae886163477947fe1588170d4
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin e0ce1b523c9cdb625211fc866593f57aee9223ee 123588d87515bcb6c762cec4d1a0357d4d6d604f && git checkout -B drift-repro e0ce1b523c9cdb625211fc866593f57aee9223ee && git merge --no-ff 123588d87515bcb6c762cec4d1a0357d4d6d604f
node scripts/docs-audit/affected-docs.mjs --json e0ce1b523c9cdb625211fc866593f57aee9223ee
|
…ess action row Claude-Session: https://claude.ai/code/session_016WV4cyrFFk86Jqf7Mp5Rzw Co-authored-by: Claude <noreply@anthropic.com>
…lumn as stored Keyed per (object, name), the o1 row is another slot in a bucket the o2 save does not write; a package-less object-less save names it first. Claude-Session: https://claude.ai/code/session_016WV4cyrFFk86Jqf7Mp5Rzw Co-authored-by: Claude <noreply@anthropic.com>
…ver the stored rows, in both save orders Claude-Session: https://claude.ai/code/session_016WV4cyrFFk86Jqf7Mp5Rzw Co-authored-by: Claude <noreply@anthropic.com>
…age-less action row Claude-Session: https://claude.ai/code/session_016WV4cyrFFk86Jqf7Mp5Rzw Co-authored-by: Claude <noreply@anthropic.com>
|
Contract-review-claim: session_016WV4d1PGptBW3QRQNmy5e6 · head df6d261 · 2026-11-14T16:56:44Z review#3. |
Contract reviewServed-tier: ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgmentSeat ruling A on #32681 (ADR-0150 D4: a customization is keyed per Implemented-by: VERDICT: PASS |
…h the engine dispatch asserts, bound before copy Claude-Session: https://claude.ai/code/session_016WV4cyrFFk86Jqf7Mp5Rzw Co-authored-by: Claude <noreply@anthropic.com>
Review carry — PASS standsHead: From my PASS at
The PASS carries to this head. The ACCEPT cites this note as Regen-provenance and re-reads every check at the landing head. The mirror and single-holder boot fold stays a follow-up on #32681 ( |
|
domain:server#5 · os-elon-musk · session_016WV4cyrFFk86Jqf7Mp5Rzw
|
Requested by Elon · project thread
Part of #32681
Clause-②: yes (narrowing)
Metadata-protocol: none
维护者速读
DatabaseLoader存一条不属于任何包、绑在某对象上的action行时,按(对象, 名字)数持有者(和内存注册表一致),不再按名字数所有包;它按槽读行时,把"名字被两个包持有、只有一个包绑了同一对象"的包外行当作那个包的定制,排在该包自己的行前面交出。(对象, 名字)归属。之前 ALPHA 绑account、BETA 绑contact时,包外的account存档写成包外行(而注册表把它当 ALPHA 的定制);只有 BETA 绑contact时,包外的account存档被写成 BETA 的account绑定(BETA 从没声明过);启动预载前后点名 ALPHA 的读答案不同。DatabaseLoader的宿主(工作区内 0 处);库里已有的行不改不迁移,只改新写入落在哪一行、以及冷读怎么归属;回滚 = revert 本 PR。packages/metadata/src/loaders/database-loader.ts的slottedSaveTarget(风险 hunk:按storeRowSlotKey分槽、按包桶命名未命名行)与loadManySlots;packages/metadata/src/package-slotted-store.ts的storeRowWriteKey与pluralEntryCustomizedHolders。pnpm --filter @objectstack/metadata exec vitest run src/loaders/database-loader.test.ts -t 32681,预期新增用例全绿。account、包外行绑contact)的启动预载修复依赖 fix(metadata)!: MetadataManager.loadMany takes a by-package name's bodies and packages off the loader's keyed entries; loadManyKeyed entries carry their package #33299 先合入,见 Acceptance notes。What changes
Seat ruling A on #32681: a package-less action row bound to an object is keyed per
(object, name)everywhere, as ADR-0150 D4 keys an action by(package, objectName, name).DatabaseLoader.savethroughslottedSaveTarget): rows are keyed as the in-memory store keys items (storeRowSlotKey), andstoreRowWriteKeycounts the holders of the written body's identity (slotIdentityOf: an object-bound action's object and name), asstoreWriteKeyalready does in memory. A package binding the name to another object is a different key, never a holder of this one. The unique-key bucket a pending object-naming acts on is still the package's (every row of the package the write's slots belong to).DatabaseLoader.loadManySlots, andloadSlots, which filters it): a package-less action row bound to an object that exactly one package binds the name to, under a name two or more packages hold, is handed on held for that package, just before that package's own row of the object (pluralEntryCustomizedHolders). That is where the boot priming pass files it: the unscoped plural read keeps it apart from both holders, and the pass registers it after them, so the store's(object, name)write rule puts it in that package's slot. Readers that keep a slot's first item (slotStoreOf,list) now answer it without the pass too. The row is not rebound: no write, itspackage_idstays empty.Every moved answer, FROM to TO (host wiring a
DatabaseLoader; ALPHA and BETA are two packages,approvethe action name):approveonaccount, BETA's oncontact; a package-less save ofapproveonaccountaccountaccountrow (its customization, D4)approveoncontactalone; a package-less save ofapproveonaccountpackage_idBETA bound toaccount(a binding BETA never declared)accountapproveonaccountalone; a package-less save of an object-lessapprovepackage_idALPHA, object-lessapproveoncontactand a package-less row onaccount; ALPHA's first save ofapproveonaccountaccount, BETA's oncontact, a stored package-less row onaccount(any version order)MetadataManager.get)list('action')without the pass409naming ALPHA and BETA; BETA's row; as listedo1, then a package's save ono2o1row overo1row keeps its column (moved #32540 pin)account, BETA oncontact, package-less save (A-B-save / B-save-A)/actions/account/approvedeclarationambiguous-in-environment(andcontact: no declaration)The second row is the narrowing: an address naming BETA no longer reaches an
accountbinding a package-less save wrote into BETA's bucket.Not changed: types other than
action(their identity is the name, and the slot read's new rule never fires for them); a name one package holds; stored rows (no migration: a stored package-less row is read as its holder's customization, never rebound; rows earlier saves wrote are listed by the ADR-0150 D10 report as before).Tests
packages/metadata/src/loaders/database-loader.test.tsdescribe[#32681](11 cases); the moved metadata: one package's action name bound to two objects keeps ONEDatabaseLoaderrow, so the later save overwrites the earlier binding (ADR-0150 D3/D4) #32540 pin inmetadata-manager-match-endpoint.test.ts;packages/runtime/src/action-package-addressing.test.tsdescribe[#32681](3 cases: real ObjectQL registry, protocol over a MetadataManager from the real MetadataPlugin boot pass, DatabaseLoader over a writablesys_metadatadouble routed through the engine dispatch asserts).accountrouteambiguous-in-environmentand found nocontactdeclaration; the single-holder save stored a[BETA, account]row.Acceptance notes
approveonaccount, a package-less row oncontact) is not in this PR.DatabaseLoader's unscoped plural read folds the package-lesscontactrow into ALPHA by name, so after the boot pass a read naming ALPHA answers thecontactrow where the read without the pass answers ALPHA'saccountrow. Counting per(object, name)there hands both rows on, butmain'sMetadataManager.loadManythen pairs bodies by content and stamps neither, the pass files ALPHA's body in the bare slot, and a read naming BETA would answer ALPHA's item. That half needs fix(metadata)!: MetadataManager.loadMany takes a by-package name's bodies and packages off the loader's keyed entries; loadManyKeyed entries carry their package #33299's keyed-entry stamping first; it is not stacked here (AGENTS.md: a stacked series is not a supported form).approveoncontactalone and a package-less row onaccount, a read naming BETA after the boot pass still answers theaccountrow and/actions/contact/approvefinds no declaration — as onmain. Carried by the follow-up on this card.packageLessRowOwners(area E1) still counts holders per name, so a package-less row this PR reads as ALPHA's customization is still listed as ambiguous there. Noted for E1.packages/metadata-protocol/src/**orpackages/metadata/src/metadata-manager.ts; indatabase-loader.tsonlyslottedSaveTarget,loadManySlots, the imports and one doc paragraph move (open PR fix(metadata-protocol)!: history and rollback doors refuse a bare two-holder name and address one holder by ?package= #33193 edits the history functions).Verification
Local runs
At c7e35e3:
@objectstack/metadatatest 77/77 files, 2325 tests;@objectstack/objectql595/595 files, 12942 tests;@objectstack/runtime599/599 files, 10678 passed, 19 skipped; metadata + runtime typecheck exit 0; dogfood battery 464 passed / 3 skipped files, 3927 passed / 42 skipped tests, no failures. Derived gates: 70 families, 69 exit 0, 1 NOT MEASURED (check:dual-build-cjs-loads, no dist in the worktree; CI Build Core covers it). Ablation (stored-side files reverted to the merge base, landing proven by grep): metadata[#32681]10 failed / 1 CONTROL green, the moved o1 pin red; runtime (metadata dist rebuilt, preflight--absentexit 0) 3 of 3 red. Restored to the HEAD blobs, tree clean.After merging
origin/main(with #33299) at 123588d, clean merge:@objectstack/metadatatest 78/78 files, 2412 tests; typecheck exit 0.Generated by Claude Code