Repository navigation
fix(metadata-protocol): a row bound to one package is not registered under a name another package ships - #22066
Conversation
…under a name another package ships The registry's bare slot answers every package's read of a name ahead of that package's own entry. A stored row bound to one package was hydrated there, so the by-name read naming another package that ships the name served the row's body under that package's envelope, on an unscoped kernel after a save and on either kernel after a cold boot. The hydrator now skips that registration (the shape the expansion registration already takes), and the delete's heal does not re-register a package-bound baseline under such a name. The reads answer the row from its row. Claude-Session: https://claude.ai/code/session_017ErfyP2Rx7XWHJA27QjyUi Co-authored-by: Claude <noreply@anthropic.com>
…ies a recorded disposition; changeset Claude-Session: https://claude.ai/code/session_017ErfyP2Rx7XWHJA27QjyUi Co-authored-by: Claude <noreply@anthropic.com>
… another package stays registered as the tenant row Claude-Session: https://claude.ai/code/session_017ErfyP2Rx7XWHJA27QjyUi Co-authored-by: Claude <noreply@anthropic.com>
…re-slot-shared-name
📓 Docs Drift CheckThis PR changes 1 package(s): 15 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 8 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 11 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 5c443c2497b701628c61956850b3c085bd090b58 && git checkout 5c443c2497b701628c61956850b3c085bd090b58
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 56bf27affbe5e3f5aca8001ed0218d013b04bf22 130b8bf9ec4351e2c47ce0526a03b624e144a1ce && git checkout -B drift-repro 56bf27affbe5e3f5aca8001ed0218d013b04bf22 && git merge --no-ff 130b8bf9ec4351e2c47ce0526a03b624e144a1ce
node scripts/docs-audit/affected-docs.mjs --json 56bf27affbe5e3f5aca8001ed0218d013b04bf22
|
|
CI:
Generated by Claude Code |
…re-slot-shared-name
… view Every other type registers as before: a row bound to one package, of a name two packages ship, keeps the bare entry with its own body and envelope (the boot-hydration pin in objectql). The page control pins that here, the enumeration records each skip as view only, and the flow case for the removed declined-row exception is dropped. Claude-Session: https://claude.ai/code/session_017ErfyP2Rx7XWHJA27QjyUi Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ErfyP2Rx7XWHJA27QjyUi Co-authored-by: Claude <noreply@anthropic.com>
ACCEPT (seat review) — PR #22066 at head
|
Fixes #22057
Clause-②: no
A stored view row bound to one package is no longer registered under the registry's bare name when another package ships that name. So the by-name view read naming the other package serves that package's own view and envelope. This is triage's closing card for shared-name selection in the registry's bare slot (grade 6031193380, amended by 6032067270). Triage ruled the scope
viewonly in 6034488359. #22058 stays folded here with its default reading B (see H4), and triage re-reads that card when this one closes.All source edits are in
packages/metadata-protocol/src/protocol.ts. There is nopackages/specedit and no governed path. ⛔ No new key.What changes (
viewonly)Both new guards are judged on the canonical type, so every spelling of
viewthat reaches these doors is covered.hydrateOverlayIntoRegistryis the one door that the list's hydration, the write-through and the boot'sloadMetaFromDbshare. A view row bound to a package is no longer registered under its name when another package ships that name. This is finding(metadata-protocol): a stored copy of a view container a package ships on another package's object expands under its own name (#21334's arm), so a form withdrawn in that copy does not reach the package's shipped form of that name #21980's shape for an expansion (hydrateExpandedViewItems,8caa131e52), and it is safe for the same reason: no reader outsidemetadata-protocolreads a view's bare entry. An aggregated container row still has its expansions judged one by one.restoreArtifactRegistryView, tier 2 (the delete's registry heal). A metadata-service view baseline bound to a package is not re-registered under a name another package ships. Without this guard, deleting a row the hydration skipped reaches tier 2, because tier 1 finds no bare entry to drop. Tier 2 then put one package's view under the bare name.anotherPackageShips(type, name, own), overshippedArtifactsOf, which covers every package that can ship the name.hydrateExpandedViewItemsnow asks it too, with no behaviour change; it was view-only already.getMetaItem's registry step is unchanged. With the bare entry gone,getItemnaming the other package answers that package's own entry. The row's own package, and a read naming no package, are answered from the row at step 1 (findServedOverlayRow), as the environment-scoped kernel already did.loadMetaFromDbis not gated onenvironmentId, so at base that kernel had the same shadow after a boot.Registered under the bare name as before:
view. A row bound to one package, of a name two packages ship, keeps the bare entry with its own body and its own package's envelope, as loadMetaFromDb boot hydration keeps a third inline copy of the overlay→registry rule with an UNSCOPED artifact lookup (ADR-0048 gap) #4624 rules (ADR-0048, pinned bypackages/objectql/src/protocol-boot-hydration-scoped.test.ts, which is not edited here);The four registration sites (H1)
git grep -n "registerItem(" -- packages/metadata-protocol/src/protocol.tsprints 8 lines at head, as on base: 3 comments, 1unregisterItem(and 4 calls. Line numbers are at130b8bf9ec.:18221applyObjectRegistryMutationapplyRegistryWriteThroughforobject(save, publish, rollback, revert, a replica's mutation); every kernelgetItem,listItemsandgetArtifactItemread the object contributors forobject. An object has one code owner (ADR-0029 D3), and a row bound to another package is refused (D9.9,OBJECT_OVERLAY_PACKAGE_MISMATCH).:18564hydrateOverlayIntoRegistryloadMetaFromDbon every kernelviewonly: skips the name:18990hydrateExpandedViewItemshydrateOverlayIntoRegistry, for each expansion of a stored containerviewonly: unchanged, shares the predicate:19284restoreArtifactRegistryView, tier 2deleteMetaItem,revertCommit's removal, a replica's removal); unscoped kernel:18564skip leaves. Measured: pin (j)(d) goes red when the guard is taken out.viewonly: skips the nameThe enumeration pin,
protocol.register-item-call-sites.test.ts, records each call with its disposition and, for each "yes", the source text that keeps it toview. It uses the idiom ofprotocol.lookup-artifact-item-call-sites.test.ts(#22024).The hypotheses (Partition 2)
:18564is the "yes" the seat expected.:19284becomes a "yes" once:18564skips. See the table.view.getMetaItem, bygit grepofgetItem(,listItems(andgetArtifactItem(over non-testpackages/**. Outsidemetadata-protocol, no non-test code calls them onview; the only two hits are comments inprotocol.ts. Inside it, the list is pinned by (j)(a)(b). The layered read's code layer islookupArtifactItem, which reads package entries first. The heal is pinned by (j)(d).view-only; see the Acceptance notes.loadMetaFromDbregistered the row under the bare name there too. That is 2 of the 6 base reds. Head fixes it at the same door.pagerow bound to one package, of a name two packages ship, still holds the bare entry with its own body and envelope, through the boot and through the list read. That is the new control (j)(e), and loadMetaFromDb boot hydration keeps a third inline copy of the overlay→registry rule with an UNSCOPED artifact lookup (ADR-0048 gap) #4624's own pin inobjectql.packageIds for one address. The layered read's code layer is not moved.git grep -nE "getMetaItemLayered|getItemLayered|/layers\b|layers'|getLayers|Layered\("over non-test, non-doc files. The REST/layersdoor serves the layered answer on its own route. plugin-security reads thecodeandeffectivelayers and never the top-levelpackageId. No reader compares the two.d50f7241b4(shallowmain; the.objectui-shapin isa58626c88d):git grep -nE "\.layered[(]"over non-test*.ts/*.tsxfinds 7 files.ResourceEditPage.tsxshowslayered.packageIdin its lock banner. Its onlyclient.getreads another object's field catalog. None of the 7 readsgetMetaItem'spackageIdfor the same address.Pins
All behaviour pins are in
protocol.org-scoped-write-refused.test.ts, block (j), inside #21980's describe. They reuse its registry double, which gains the two heal verbs (removeRuntimeShadow,removeOverlayEntry) asSchemaRegistryholds them.pkg_aandpkg_bboth ship view itemtask.intake_form, and apkg_b-bound row of it is saved through the realsaveMetaItem. Each case runs in both registry orders, after a save on the running kernel and after a cold boot (loadMetaFromDbover the stored rows into a fresh registry).pkg_aservespkg_a's own view underpkg_a's envelope. Its slot in the environment-wide list agrees, and so does a registry read namingpkg_a.pkg_b, the read naming no package and the list scoped topkg_bserve the row underpkg_b's envelope.pkg_b-bound baseline for the heal's no-package read.pagerow bound topkg_b, of a name both packages ship, holds the bare entry with its own body andpkg_b's envelope, through the cold boot and through the list read, in both registry orders. It uses a small registry double for any type.protocol.register-item-call-sites.test.ts.protocol.flow-stored-row-shipped-name.test.ts. It pinned an exception to an every-type skip, and with the skip kept toviewthat exception no longer exists. The file is unchanged frommain.Reverse verification
All legs ran on committed head
130b8bf9ecthroughscripts/ablation-replace.mjs. Each prediction was written before its run. Each leg's anchor hit once, went 1 to 0, and changed the blob. Each restore was proven: the blob equals HEAD's8de5265bea, andgit diff HEADis empty.viewskip taken out (const bound = canonicalType === 'view' ? boundPackageOf(options.packageId) : undefined;replaced byundefined). Source-imported subject, so no dist leg. Predicted 7 red / 256 green over the threemetadata-protocolpin files; measured 7 / 256.pkg_b's row underpkg_a's envelope:expected [ 'Intake (pkg_b row)', 'pkg_a', … ] to deeply equal [ 'Intake (shipped by pkg_a)', … ].viewgate taken out, which is the first round's shape).metadata-protocolwas rebuilt.ablation-dist-preflightfound the widened line indist/.metadata-protocolpins: predicted 3 red / 251, measured 3 / 251. The reds are (j)(e) in both orders (expected [ undefined, undefined, undefined ] to deeply equal [ 'Intake (pkg_b row)', 'pkg_b', … ]) and the view-only row.objectql'sprotocol-boot-hydration-scoped.test.ts(loadMetaFromDb boot hydration keeps a third inline copy of the overlay→registry rule with an UNSCOPED artifact lookup (ADR-0048 gap) #4624): 1 red / 2 green,expected 'A Home' to be 'B Home (customized)'. That is the CI red of the first round.ablation-dist-preflight --absentexit 0; the loadMetaFromDb boot hydration keeps a third inline copy of the overlay→registry rule with an UNSCOPED artifact lookup (ADR-0048 gap) #4624 pin back to 3 / 3 green.45ffd806d8, before this round's narrowing). The heal guard taken out reddened (j)(d) in both orders. The base reading of block (j) against base56c88446eawas 6 red / 241 green.Clause-②
no, as the claim said.dist/index.d.tsanddist/index.d.ctsfrom base56c88446ea'sprotocol.tsand from head, and diffed them. Apart from comments, the only difference is one line,private anotherPackageShips;, onObjectStackProtocolImplementation. No exported signature moves.boundPackageOfis module-private.Measured (head
130b8bf9ec, which mergedorigin/mainat56bf27affb)@objectstack/metadata-protocolsuite: 221 files passed, 3 skipped; 28222 tests passed, 19 skipped (os-verify-lockVERDICT command-exit 0).@objectstack/objectqlsuite (both thelocalandrepoprojects, against a rebuiltmetadata-protocoldist): 379 files and 7513 tests passed. loadMetaFromDb boot hydration keeps a third inline copy of the overlay→registry rule with an UNSCOPED artifact lookup (ADR-0048 gap) #4624's pin is green, with no edit to it.pnpm --filter @objectstack/metadata-protocol run typecheck: green. The two changed test files are in the tsc program (--listFiles).turbo run buildoverpackages/*(71 of 71 tasks, 30 cached).dispatch-gates --commandswith no paths derives 64 commands, and all 64 exit 0.--ranreports 64 derived, 64 run, 0 NOT-MEASURED, 0 UNRUN.check-closing-target-claim,check-partof-closing-keywordandcheck-single-claim-paths. They were run against PR fix(metadata-protocol): a row bound to one package is not registered under a name another package ships #22066 with this body asPR_BODY, using GET reads only.check:adr-symbol-anchors,check:scripts-symbol-anchors,check:spec-docblock-symbol-anchorsandcheck:adr-anchors.eslint.config.mjs: its TS glob covers the three.tsfiles, and no glob matches.md..tsfiles with 0 errors and 0 warnings, and the changeset reported as "File ignored".Acceptance notes
The guards stay
view-only. For every other type, a row bound to one package, of a name two packages ship, keeps the bare entry (#4624). Which body a registry reader that names no package should see for such a name is not ruled here, and no reader has a measuredreach::plugin-security'sbootstrap-declared-permissions.tsandbootstrap-declared-positions.ts,packaged-permission-set-lock.ts, andplugin-sharing'sbootstrap-declared-sharing-rules.ts.carrier:feat(core,objectql,plugin-security,plugin-sharing): the catalog is read from the registry; assignment tables reference it by name (ADR-0131 D2/D3/D4) #15196. Its ADR-0131 D2–D4 design is where "which body a by-name reader resolves for a shared name with a package-bound override" is answered.objectql/src/plugin.tsabout:2748,runtime/src/action-execution.tsabout:2702,objectql/src/registry.tsabout:4875).承接者:无.承接者:无.getMetaItemasks the metadata service (step 2) before the registry, andMetadataManager.get(type, name)(metadata-manager.ts:908) is keyed by name alone.anotherPackageShipslists the type's registry entries (shippingPackagesOf) once for each package-bound view row the hydration loops over, ashydrateExpandedViewItemsalready does once per container. Not measured.Generated by Claude Code