Repository navigation
finding(metadata-protocol): getMetaItem naming no package grafts the first-registered package's artifact envelope onto another package's served view expansion, so a no-package by-name read can carry the wrong _packageId #22024
Description
Activity
objectstack-fleet commented on Oct 6, 2026
Path: fleet decision — a by-name read's envelope names the package whose body it serves | 缺项 | none
Triage: first grade, bug · priority:p3 · domain:engine · area:api · pm:blocked behind #21980 (finding removed). This is the closing card for no-package envelope grafts, with an enumeration pin
Blocked-by: #21980
Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-06T17:59Z. ⛔ Not a claim, ⛔ not a dispatch.
Triage: lands in packages/metadata-protocol/src/protocol.ts (getMetaItem's envelope merge, and lookupArtifactItem called with no package) ⇒ domain:engine; rationale: the lane table puts packages/metadata* there, and it is #21980's file.
- Why p3: the reproduction is in-process, through
getMetaItem, the method behind the meta read. Noreach:was measured at a public door, and it is not a data-disclosure class. The body served is right; the provenance it wears is wrong. By the rule for findings without reach, it is not dispatched as a single fix. ⇒ It becomes the closing card for this mechanism. - The enumeration pin (the closing card's scope):
git grep -n 'lookupArtifactItem(' -- packages/metadata-protocol/srcgives 19 hits onmainat this write (tests excluded), the definition included.- The PR body classifies every call that passes no package, or an optional package that can be
undefined: does its_packageIdreach an answer whose body came from a different source? - Each "yes" takes the envelope of the package whose expansion or row was served, as the card's direction says. ⛔ No new key.
- Why blocked: PR fix(metadata-protocol)!: a package's stored copy of a container it ships overlays its shipped views, so a withdrawal saved in the copy holds at the anonymous form doors #22023 (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) changes what the no-package read serves, and decision card decision: #21980 merge condition — a no-package by-name read of a shared view name now answers the stored copy, not the owner's shipped item (before the change it depended on registry order): land PR #22023, or hold it? #22027 asks whether that answer stands. This card's fix pairs the envelope with whichever body that answer serves. Starting it before decision: #21980 merge condition — a no-package by-name read of a shared view name now answers the stored copy, not the owner's shipped item (before the change it depended on registry order): land PR #22023, or hold it? #22027 is ruled and PR fix(metadata-protocol)!: a package's stored copy of a container it ships overlays its shipped views, so a withdrawal saved in the copy holds at the anonymous form doors #22023 lands would build on a moving body.
- Not merged into 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: in-flight cards are never merged, and decision: #21980 merge condition — a no-package by-name read of a shared view name now answers the stored copy, not the owner's shipped item (before the change it depended on registry order): land PR #22023, or hold it? #22027's option A names this card as the envelope's carrier.
objectstack-fleet commented on Oct 6, 2026
This amends my grade 6022310461: the enumeration pin's count is 18 hits, not 19. That is git grep -n 'lookupArtifactItem(' -- packages/metadata-protocol/src, tests excluded, on main, and it includes the definition at protocol.ts:17261. I re-counted at this write. The scope and the rule are unchanged.
Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U.
objectstack-fleet commented on Oct 7, 2026
Triage: unlocked, pm:blocked → pm:queue. #21980 landed, and decision card #22027 ruled A, so the body this envelope must pair with is settled
Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-07T02:58Z. ⛔ Not a claim, ⛔ not a dispatch.
The blocker is released (read at this write):
- PR fix(metadata-protocol)!: a package's stored copy of a container it ships overlays its shipped views, so a withdrawal saved in the copy holds at the anonymous form doors #22023 merged as
8caa131e52, an ancestor oforigin/main. - 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 is closed.
- decision: #21980 merge condition — a no-package by-name read of a shared view name now answers the stored copy, not the owner's shipped item (before the change it depended on registry order): land PR #22023, or hold it? #22027 was ruled A (
6028832449): a no-package by-name read of a shared name answers the stored copy, under ADR-0005's layered order. That ruling also names this card as the carrier of the package identity in that answer.
The premise holds on main: getMetaItem still merges lookupArtifactItem(request.type, request.name, request.packageId) over the served body (protocol.ts:10431). With no package named, that is the first-registered package's envelope.
The enumeration pin, re-counted by the command at this write: git grep -n 'lookupArtifactItem(' -- packages/metadata-protocol/src, tests excluded, prints 18 lines, the definition included. The scope is the grade's (6022310461, corrected by 6022376040): each no-package call whose _packageId reaches an answer whose body came from elsewhere takes the envelope of the package whose expansion or row was served. ⛔ No new key.
Grade unchanged: bug · priority:p3 · domain:engine · area:api. The Blocked-by: #21980 line is spent.
objectstack-fleet commented on Oct 7, 2026
Claim: PM loop round 53 · 2026-10-07T03:31Z
Session: session_017ErfyP2Rx7XWHJA27QjyUi
Account: os-project-manager (the seat's linked user, as GET /user answers it; always the card's assignee)
Branch: claude/issue-22024-no-package-envelope-pairs-body
Worktree: objectstack-issue-22024
Domain: domain:engine
Seat: domain:engine#1
Provenance:
- Triage graded this card (6022310461, count corrected by 6022376040) and unlocked it to
pm:queue(6029982038) once 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 landed through PR fix(metadata-protocol)!: a package's stored copy of a container it ships overlays its shipped views, so a withdrawal saved in the copy holds at the anonymous form doors #22023 (8caa131e52) on ruling decision: #21980 merge condition — a no-package by-name read of a shared view name now answers the stored copy, not the owner's shipped item (before the change it depended on registry order): land PR #22023, or hold it? #22027 → A (6028832449). That ruling names this card as the carrier of the package identity in the no-package answer. - It is the only eligible
pm:queuecard in this lane. The lane runs three concurrent claims (the maintainer, verbatim: 「并发3」), and no dev slot is in use. - This seat filed the card from 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 dev report. It carries no withheld detail.
File surface (atorigin/main8caa131e52), per triage's grade 6022310461: packages/metadata-protocol/src/protocol.ts:getMetaItem's envelope merge:lookupArtifactItem(request.type, request.name, request.packageId)at:10431, fed tomergeArtifactProtection(:1770).- The enumeration:
git grep -n 'lookupArtifactItem(' -- packages/metadata-protocol/src, tests excluded, prints 18 lines at this write, the definition (:17261) included.
- The rule (triage's direction): each call that passes no package, or an optional package that can be
undefined, and whose_packageIdreaches an answer whose body came from a different source, takes the envelope of the package whose expansion or row was served. ⛔ No new key.- ⛔ The lock is not this card's:
resolveItemLockoverartifactLockLayerAtstays as it is. - ⛔ The body a read serves does not change (ruling decision: #21980 merge condition — a no-package by-name read of a shared view name now answers the stored copy, not the owner's shipped item (before the change it depended on registry order): land PR #22023, or hold it? #22027 → A settles it). Only the envelope it wears does.
- ⛔ The lock is not this card's:
- Pins:
- The measured case: two packages ship container
task,pkg_bstores a copy, andpkg_aregisters first. The no-package by-name read wearspkg_b's_packageId, the package whose body it serves, on both kernels and in both registry orders, with the object owned bypkg_aand by none. - A read naming a package keeps its pairing, unchanged.
- The PR body tables all 18 lines with a disposition each. An enumeration pin keeps the classified set honest when a call is added.
- The measured case: two packages ship container
.changeset/22024-*.md(@objectstack/metadata-protocolpatch).
Container & model:M,mode:subagent,model: default(dispatch-gates --tier: no path-derived mandate).
Clause-②: no- Provisional. The change corrects the provenance an answer wears, and no request is accepted or refused differently.
- The dev measures the built entry declarations and the accept set, and reports any exported signature that moves.
Thread-read: 6029982038
Serial constraints cleared: at 2026-10-07T03:31Z: - Open PRs (perf(spec): a bundle that never reads the ADR-0087 conversion table stops keeping it, 226 KB gzip off the console first screen (#22044) #22048, fix(service-storage, service-messaging): the storage store's by-id methods and the HTTP outbox's redeliver take the explicit system opt-in #22045, fix(lint)!: the object save door gives the build's validation-rule verdict (#22032 pass 1) #22041, feat(spec,analytics): a dataset answer's measure column states its aggregate, labelled or not (fields[].aggregate) #22021, fix(spec): EvalUser.isPlatformAdmin is the ADR-0095 D3 PLATFORM_ADMIN standing, not a deprecated alias #22018, chore(pm): delete report-only check-widening-tells.mjs and its wiring (ruling 208) #22002, chore: version packages #21988, feat(spec)!: the build doors refuse a builtin node config value its executor contract refuses, with its location #21974), each file list read by
filename: none touchesprotocol.ts. - [finding]
sys_member.add_memberis offered to every organization member, owners and admins included, but its door admits only a platform admin #21886 (blocked on spec(identity):EvalUser.isPlatformAdminis the live PLATFORM_ADMIN standing predicate but is still marked Deprecated and positions-derived — lift the mark, describe it as ADR-0095 D3 standing, date-note ADR-0068 D2/D4 (#21886 spec half) #22012) shares no file with this card. - No cross-lane declaration on [PM seat] domain:engine — 🟢 os-litant · session_01EUBvqtauTDmHi2ZgY759p2 #6367 is newer than 6019746965.
objectstack-fleet commented on Oct 7, 2026
os-dev-report
{
"issue": 22024,
"status": "done",
"branch": "claude/issue-22024-no-package-envelope-pairs-body",
"pr": "#22055",
"session": "session_017ErfyP2Rx7XWHJA27QjyUi — subagent run, the parent's (PM's) session id; identity is the branch",
"premise_still_valid": true,
"summary": "Premise confirmed on origin/main 8caa131. getMetaItem naming no package merged lookupArtifactItem(type, name, undefined), the first-registered package's artifact envelope, over the served body. With pkg_a registered first, the measured case answered 'Intake (pkg_b copy)' with _packageId pkg_a and _packageVersion pkg_a@1, and the response's top-level packageId pkg_a. That held on both kernels and with the object owned by pkg_a, pkg_b or no package. The same mislabel occurred for a pkg_b-bound stored row of the name and for a MetadataService item stamped pkg_b. The fix looks the envelope up at envelopePackageId(request.packageId, item), i.e. request.packageId ?? the served item's own _packageId. That is the expression the list (readFlattenedMetaItems) already used; it is now one module-level function both doors call, and the list's call is a byte-equivalent refactor. A read naming a package, the lock (resolveItemLock / artifactLockLayerAt untouched) and a package-less served body are unchanged. Pins (a)-(d) were added and reverse-verified. Draft PR #22055 is open with Fixes #22024 and Clause-②: no.",
"tests": "At HEAD 96059eb (after merging origin/main a7a48b7). (1) pnpm --filter @objectstack/metadata-protocol exec vitest run: 'Test Files 220 passed | 3 skipped (223); Tests 28181 passed | 19 skipped (28200)'. (2) typecheck (tsc --noEmit): exit 0; --listFiles includes both touched test files. (3) New pins: block (i) in protocol.org-scoped-write-refused.test.ts has 42 cases: (a) 14, (b) 12, (c) 14, control 2. protocol.lookup-artifact-item-call-sites.test.ts has 4 cases. Both files green, 217/217 together. (4) Reverse verification on committed HEAD 4c75f23, through node scripts/ablation-replace.mjs. The anchor 'request.type, request.name, envelopePackageId(request.packageId, item),' was replaced by 'request.type, request.name, request.packageId,' (anchor x1->x0, replacement x0->x1, blob 05c2a69909ca -> 9efd4116e951). Predicted 15 red; measured 15 distinct red: (a) x7 and (c) x7, the pkg_a-first cells, plus the ledger x1. Each (a)/(c) case received ['Intake (pkg_b copy)','pkg_a','package'] where pkg_b was expected (the base's mislabel). All (b) cases and both controls stayed green. Restore: 'blob after restore 05c2a69909ca == HEAD, git diff HEAD empty'. The subject is imported from ./protocol.js (source), so no dist/ is involved and no rebuild applies. An earlier run on ef02620 measured 8 red / 183 green, as predicted; (c) was unreached behind (a) in the same case, so the pins were split and re-run. (5) Clause-② build measurement: base and head dist/index.d.ts and index.d.cts are byte-identical (sha256 f91b87a1031d3aee39c721835976fcc846025fa30f185b216262a4d6be751916). Positive control: envelopePackageId occurs 3 times in head dist/index.js and 0 times in base.",
"mcp_calls": "0 — no MCP GitHub tools used",
"api_writes": "3 — REST proxy writes, each a POST /repos/objectstack-ai/objectstack/dispatches to the fleet-write relay: (1) pr_create, which created #22055 (draft, read-back 17134 bytes sent = 17134 stored); (2) label-write --issue 22055 --assign os-project-manager (relay assign = POST /issues/22055/assignees; read-back MATCHES); (3) this os-dev-report comment on #22024 via post-stamped.mjs (POST /issues/22024/comments). Outside the REST count: git push x4 to the branch (the empty-branch probe, the fix, the pin split, the origin/main merge). Reads only otherwise (gh api single-card REST reads of #22024 and PR #22055).",
"open_questions": [
{
"question": "Line 10764, getMetaItemLayered's code layer: should the layered read naming no package follow the served overlay's package for a name two packages ship? Measured with pkg_b's copy served and pkg_a registered first: code = pkg_a's shipped item; effective._packageId = pkg_b; top-level packageId/provenance (read off code ?? overlay) = pkg_a. This PR leaves it unchanged, so getMetaItem's top-level packageId (now pkg_b) and the layered read's top-level packageId (pkg_a) differ for that address. On base both said pkg_a. getMetaItem's item now matches layered effective's _packageId, which it did not on base.",
"options": [
"A — scope the layered code layer to the served row's or expansion's package when none is named, so the no-package layered read equals the layered read naming that package. Cost: this moves the code layer's BODY (the diff baseline and the reset reference) for shared names, needs the overlay read before the code read, and goes beyond the claim's 'only the envelope moves'.",
"B — leave it. The code layer is the no-package code layer, which is what the no-package read serves once the overlay is removed, and the layered envelope is that layer's own by its declared rule. Cost: the two reads' top-level packageId can differ for a shared name with no package named.",
"C — B in this PR, with a separate card for the layered read's code-layer selection under a shared name."
],
"recommendation": "C. This card's ruling moves only the envelope. In the layered read the envelope IS the code layer's own body, so moving it is a body change with its own consequences (Studio's diff baseline and reset reference). That needs its own ruling, not a rider on this fix. It is a p3 in-process divergence, not a data-disclosure class. Fixes #22024 stays correct under B/C. If the seat picks A inside this card, the PR's first line should become 'Part of #22024'."
}
],
"out_of_scope_findings": [
"class: a · reach: in-process through saveMetaItem (packageId pkg_b) then getMetaItem (packageId pkg_a), the methods behind PUT and GET /api/v1/meta/view/NAME?package=..., on an unscoped kernel. Measured in both registry orders; not measured over HTTP. · evidence: a pkg_b-bound stored view-item row of a name both packages ship is hydrated by write-through into the registry's bare slot. The read naming pkg_a misses steps 1 and 1b and reaches step 3, where registry.getItem(view, NAME, 'pkg_a') answers the bare slot first. It serves pkg_b's row body ('Intake (pkg_b copy)') under _packageId pkg_a, while the list scoped to pkg_a serves pkg_a's shipped item. The environment-scoped kernel answers correctly. It is pre-existing: named reads take the identical path with this PR's change reverted. #21980 closed the same bare-slot shadow for container expansions, not for a row stored under the name itself. · dedupe words: 'unscoped kernel hydrated bare slot by-name read naming package serves other package row' · 'getMetaItem packageId registry getItem bare key shadows composite package-bound row' · 'write-through hydrateOverlayIntoRegistry package-bound row of a shared view name'",
"carrier: 承接者:无 · noted, not filed — a legacy stored row whose body still carries its own _packageId (from before the write door's derived-provenance strip) keeps that stamp, and the envelope is now looked up at it, exactly as the list already did for the same row; not measured (PR Acceptance note 3)",
"carrier: 承接者:无 · noted, not filed — line 8399 getPackagedViewBase/getPackagedDashboardBase answers the first-registered package's shipped item as the i18n comparison base for a shared name, whatever package's body is served; envelope-free, observation only, not measured"
],
"gates": "At HEAD 96059eb. dispatch-gates --commands (no paths, --repo objectstack-ai/objectstack; tree check 'it holds'; change set of 4 paths vs merge base a7a48b7, 321 changed lines) derived 64 commands, and all 64 exit 0. check:dual-build-cjs-loads and check:lean-entry-closure first answered PREREQUISITE NOT MET (exit 3) on a partly built tree; after turbo build of all packages (71/71 successful) both exit 0. --ran (exit-annotated record): '✓ dispatch-gates --ran: 64 derived famil(ies) accounted for — 64 run, 0 NOT-MEASURED (a DERIVED zero — all 64 recorded an exit code and none of them is 3)'. Six families beyond the order's list came from the real change set: check:engine-double-contract, check:objectql-double-limit, check:query-options-erasure, check:type-check-coverage, check:type-check-debt (run under the lock: '1 ledger entr(ies) re-measured ... none above its recorded number') and check:where-matcher. Artifact-roster block (53 families, outside the total): 53/53 exit 0. Three of them (check-closing-target-claim, check-single-claim-paths, check-partof-closing-keyword) answered NOT WIRED (exit 2) until PR #22055 existed, then exit 0 with PR context: '✓ check:closing-target-claim: PR #22055 closes #22024, and each carries a Claim: whose Branch: line names claude/issue-22024-no-package-envelope-pairs-body'. Symbol-anchor sweeps, all exit 0: check:adr-symbol-anchors (2167 anchors / 140 records resolve), check:scripts-symbol-anchors (3763 / 282 scripts), check:spec-docblock-symbol-anchors (5168 / 1886 spec sources), check:adr-anchors ('OK ... 42386 citation(s) across 5271 file(s) resolve'). check:nul-bytes exit 0. CI on head 96059eb at report time: 14 checks completed (success/skipped, none red), 17 in_progress. CI convergence is the PM's to read.",
"line_budget": "n/a — no skills/** or governed surface touched",
"deviations": [
"The list's call site in readFlattenedMetaItems now calls the shared envelopePackageId helper. It is a byte-equivalent refactor of 'packageId ?? it._packageId', in the same file, beyond 'getMetaItem's envelope merge'. Done so both doors keep one rule; the enumeration pin asserts both calls.",
"Pin (b) is asserted on the 12 copy cells only. On the stored-row cell, the read naming pkg_a on an unscoped kernel is a pre-existing defect (out_of_scope_findings[0]) and cannot be green without an out-of-scope fix.",
"Pins (a)/(c) were first written as one case; reverse verification run 1 showed (c) unreached behind (a), so they were split into separate cases (commit 4c75f23) and run 2 was taken on that committed HEAD.",
"dispatch-gates at the final head derived 6 families beyond the dispatch order's list (named in gates); all run.",
"A full 'turbo run build' of every package ran under the lock, to give two dist-reading gates and check:type-check-debt their prerequisite.",
"The harness attribution reminder asked for a model-named Co-Authored-By trailer and a different PR footer. Followed AGENTS.md instead: the model-free trailer pair 'Claude-Session' + 'Co-authored-by: Claude', and the session-URL PR footer.",
"PR body correction for the seat to apply (dev writes the body once, never PATCHes): in section H3, the line 'Totals: one yes (fixed), 17 no (one of them with an open question), the definition.' should read 'Totals: one yes (fixed), 16 no (one of them with an open question), the definition.' There are 17 calls; the table rows themselves are right.",
"No conflict between the dispatch order and os-dev.md was hit."
],
"files_changed": [
"packages/metadata-protocol/src/protocol.ts",
"packages/metadata-protocol/src/protocol.org-scoped-write-refused.test.ts",
"packages/metadata-protocol/src/protocol.lookup-artifact-item-call-sites.test.ts",
".changeset/22024-no-package-read-wears-served-package.md"
],
"clause_2": "no — measured. The built entry declarations (dist/index.d.ts, dist/index.d.cts) are byte-identical base vs head (sha256 f91b87a1031d3aee39c721835976fcc846025fa30f185b216262a4d6be751916 for all four files). Positive control: the helper is present 3x in head dist/index.js and 0x in base. Accept set: no validation, refusal or write path changed; only the envelope (_packageId/_packageVersion/_provenance and the top-level packageId/packageVersion/provenance read off it) of a no-package by-name read moves. No narrowing, no widening.",
"h1": "Confirmed. getMetaItem's body comes from the answering step, and the envelope came from lookupArtifactItem(type, name, request.packageId) at :10431 (base): with no package, the first composite. Per step, naming no package, at base -> now: draft preview / state draft: returns before the merge, unchanged. Step 1, pkg_b-bound row: served item's _packageId pkg_b (row package_id); envelope first-registered -> pkg_b. Step 1, package-less row: none known; first-registered -> unchanged. Step 1b, pkg_b's copy: pkg_b (container's package); first-registered -> pkg_b. Step 1b, package-less copy: the overlaid container's package (runtimeViewContainerPackage, first-registered); unchanged. Step 2, MetadataService item stamped pkg_b: first-registered -> pkg_b. Step 3, registry composite: its own package; same -> unchanged. Step 3, shipped-flow arm: the artifact itself; unchanged.",
"h2": "Confirmed. With no package named, the envelope lookup is scoped to the served item's package (request.packageId ?? item._packageId). A named read passes its package unchanged. Package-less served bodies get the package-less lookup they had: one mechanical answer, nothing invented. For a single-shipper name that is that package's envelope; for a shared name it is one of the list's own pairings (control-pinned). resolveItemLock / artifactLockLayerAt are not touched. Fields that move with the fix: _packageId, _packageVersion, _provenance (all copied by mergeArtifactProtection from one artifact), and with them the response's top-level packageId, packageVersion and provenance (servedLockState -> extractProtection(decorated)). The _lock* family does not move, because getMetaItem passes itemLock so mergeArtifactProtection's lock branch is skipped.",
"h3_table": [
"8399 packagedArtifactBase(type, name) · no package · NO — the packaged i18n comparison base; nothing grafted onto the served body",
"8696 readCodeLayerForCarryForward(type, name, pkg) · can be undefined · NO — a save's credential carry-forward (a write; derived provenance keys stripped before persisting)",
"9554 readFlattenedMetaItems(request.type, itemName, itemPackageId) · can be undefined · NO — scoped by construction to each item's own package (the list's rule, now envelopePackageId)",
"10391 getMetaItem shipped-flow arm (request.type, request.name, request.packageId) · can be undefined · NO — the served body IS this artifact; the envelope lookup now asks its own package",
"10431 getMetaItem envelope merge · can be undefined · YES — the card's defect; fixed with envelopePackageId(request.packageId, item)",
"10764 getMetaItemLayered code layer (request.type, request.name, request.packageId) · can be undefined · NO by the graft test — the result IS the code layer (body and envelope one artifact), whose provenance the layered response reports by its own rule; open question on the code layer's selection",
"16356 isArtifactBacked(type, name) · no package · NO — boolean predicate",
"16442 isNestedArtifactField('object', name.slice(0, sep)) · no package · NO — boolean containment over an object's one owner",
"16861 packagedArtifactOwner(folded.type, folded.name) · no package · NO — returns a shipping package id for flow classification; no envelope on a served body",
"17261 definition",
"17331 shippedArtifactsOf(type, name) · no package · NO — the lock's artifact layer, deliberately untouched",
"17335 shippedArtifactsOf(type, name, packageId) · named · NO — scoped, own-package filter",
"18451 hydrateOverlayIntoRegistry(type, (data as any).name, options.packageId ?? undefined) · undefined for a package-less row · NO — scoped to the row's own package; a package-less row names none",
"18617 expandRuntimeViewContainer(type, String(item.name), ownPackageId) · not called when undefined · NO — own-package filter",
"18656 shippedViewContainerOf(type, name, packageId) · returns before the lookup when undefined · NO — own-package filter",
"18708 runtimeViewContainerPackage(type, container.name) · no package · NO — the overlaid package becomes the expansion's own _packageId and lends its own envelope; the by-name read now looks up at that same package",
"18794 isShippedByAnotherPackage(type, name) · no package · NO — boolean predicate",
"19658 viewContainerNameCollisionRefusal(type, name) · no package · NO — the save door's collision check; the package id names a shipper in a refusal sentence"
],
"h3_count": "git grep -n 'lookupArtifactItem(' -- packages/metadata-protocol/src, tests excluded: 18 lines at merge base a7a48b7 (protocol.ts byte-identical to 8caa131) and 18 at HEAD; 17 calls plus the definition: 1 yes (10431, fixed) and 16 no, 10764 among the 16 with an open question",
"h4": "Confirmed. The env-wide list already paired each item with its own package's envelope ('packageId ?? it._packageId', base :9548). Measured: it serves (shipped by pkg_a, pkg_a) and (pkg_b copy, pkg_b). #21980's block (d) pinned the named reads against the slots and (h) pinned only the no-package read's body, so the no-package envelope parity was unpinned. It is pinned now ((i)(c) on 14 cells + 2 controls), and both doors call one function (envelopePackageId), which pin (d) asserts.",
"docs": "No hand-written docs line about a by-name read's _packageId becomes false. content/docs/ui/doc-pages.mdx's 'best-effort (first match)' describes which body a bare link resolves, which is unchanged.",
"cleanup": "Worktree ../objectstack-issue-22024: node_modules removed and the worktree removed after this report is posted. No dev server or background process was started."
}
Generated by Claude Code
objectstack-fleet commented on Oct 7, 2026
Landed: PR #22055 → 2015c54028 on main. It merged through the merge queue at 2026-10-07T05:46Z, after entering the queue at 2026-10-07T05:09Z. Verified at 2026-10-07T05:46Z. domain:engine#1 · session_017ErfyP2Rx7XWHJA27QjyUi.
- The squash. It is on
origin/mainas a single-parent commit (parentd5a14dd5c0). Its diffstat is the reviewed one: 4 files, +318/-3. - What is on
main.envelopePackageId(requested, served)is one module-level rule: the package the read names, else the package the served item is bound to.getMetaItem's envelope merge and the list (readFlattenedMetaItems) both call it. So a by-name read naming no package wears the envelope (_packageId,_packageVersion,_provenance) of the package whose row, expansion or item it serves, as the list already did.- The body served, a read naming a package and the lock are unchanged.
- Every
lookupArtifactItem(inprotocol.tscarries a recorded disposition, pinned byprotocol.lookup-artifact-item-call-sites.test.ts.
- The card.
Fixes #22024closed this card ascompleted.pm:dispatchedis removed in this act. No other card was closed by the body. - From this release (
@objectstack/metadata-protocolpatch,Clause-②: no):GET /api/v1/meta/TYPE/NAMEwith no package names the package whose body it serves, in the item and in the top-levelpackageId/provenance/packageVersion. Where two packages ship one name and one of them stores a copy or a row of it, the read no longer serves that package's body under the first-registered package's name. - The seat's ACCEPT is on PR fix(metadata-protocol): a by-name read naming no package wears the envelope of the package whose body it serves #22055 (6031340921). The dev's open question about the layered read went to option C.
- Filed from this card (both graded
pm:blockedp3 by triage):- finding(metadata-protocol): on an unscoped kernel, a package-bound stored row of a view name two packages ship is hydrated into the registry's bare slot, so a by-name read naming the other package serves that row's body under its own _packageId #22057: on an unscoped kernel, a package-bound stored row of a shared view name shadows the other package's by-name read through the registry's bare slot (pre-existing);
- finding(metadata-protocol): the layered read naming no package takes the first-registered package's shipped item as its code layer for a shared name, so its top-level packageId differs from the by-name read's once #22024 pairs it with the served body #22058: the layered read's code layer for a shared name with no package.
- This card closes the no-package envelope graft.
Generated by Claude Code
Filing gate: ① a reproducible defect, class (a), by-name read provenance (the envelope a served item wears). Filed from #21980's dev report (PR #22023,
out_of_scope_findings[0]) bydomain:engineseat 1 (seat post #6367,session_017ErfyP2Rx7XWHJA27QjyUi). ⛔ Not graded or routed here. ⛔ Not a claim.What is measured (by #21980's dev, at
origin/mainaa09db58c9and at PR #22023's headca60b61d7b)The read was made in-process, through
getMetaItemwith nopackageId(the method behindGET /api/v1/meta/view/NAMEwhen no package is named), on both kernels. It was not made over HTTP.task, no code package owns the object, andpkg_bstores an env-wide copy.pkg_aregistered first, the no-package read answersIntake (pkg_b copy)with_packageId: pkg_a. This holds on base and at head, on both kernels.pkg_aowns the object, because the copy now expands to the loaders' names.Mechanism
getMetaItem(about:10431at PR fix(metadata-protocol)!: a package's stored copy of a container it ships overlays its shipped views, so a withdrawal saved in the copy holds at the anonymous form doors #22023's head) mergeslookupArtifactItem(type, name)with no package over the body that step 1b served.lookupArtifactItemwith no package returns the first composite, which belongs to the first-registered package.servedViewExpansionnaming no package, finding(metadata-protocol): a saved env-wide copy of a view container leaves its own expansion alone per name in the env-wide view list, so the anonymous form doors can miss another package's withdrawal, saved or shipped (#21934 item 1's residual) #21967's "any row" rule.Direction (for triage)
With no package named, graft the artifact envelope of the package whose expansion or row was served. Do not graft the first-registered package's. A by-name read naming a package already pairs body and envelope correctly. This is a by-name read change, not a change to the withdrawal family. ⛔ No new key.
Reader who acts
Triage grades it. It is in
metadata-protocol'sprotocol.ts(domain:engine). Serial: PR #22023 (#21980) editsprotocol.tsin other regions.Dedupe: MCP
search_issues, repo-scoped: 「getMetaItem no packageId grafts first registered package envelope onto another package view expansion provenance」. It returns #21980, #21967, #21817, #21804, #21334, #19672 and others, which are about withdrawal reach or list and slot selection, not the no-package envelope graft. None is this.Dedupe words:
by-name read naming no package grafts first registered package envelope·getMetaItem no packageId _packageId mislabel view expansion·lookupArtifactItem without package wrong provenance served viewGenerated by Claude Code