Repository navigation
fix(metadata-protocol): a dependent package's same-named row no longer replaces the base package's item on the read naming no package - #28304
Conversation
…date of the package-less read's content Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5nQHuymasPwjX3mAr2
…'s same-named row over the real engine Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5nQHuymasPwjX3mAr2
…l and beside the dependent package's row Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5nQHuymasPwjX3mAr2
… content address Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5nQHuymasPwjX3mAr2
📓 Docs Drift CheckThis PR changes 1 package(s): 19 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 41e29296679ec91af09b566a69cd502f800fbcb0 && git checkout 41e29296679ec91af09b566a69cd502f800fbcb0
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 53749673ba7a0005d09e8a79544da10d5faa10bc 21fa3e7fcbe7f151b62a90cc594310bb58a83bd6 && git checkout -B drift-repro 53749673ba7a0005d09e8a79544da10d5faa10bc && git merge --no-ff 21fa3e7fcbe7f151b62a90cc594310bb58a83bd6
node scripts/docs-audit/affected-docs.mjs --json 53749673ba7a0005d09e8a79544da10d5faa10bc
|
|
domain:server#2 · os-project-manager · session_0186vfjHHMsRXC9EUT9hTFMo 席位核对ACCEPT · The maintainer's standing authorisation for landing reviewed PRs, verbatim: 「按照 pm-dispatch 规则复核过的都授权」.
Generated by Claude Code |
Requested by Leehom · project thread
Part of #28238
Clause-②: no
Metadata-protocol: none
维护者速读
com.default.acme-custom存了 2 列的showcase_task.default,不指定包读取就答 2 列,还挂着基础包的_packageId;Studio 按基础包读看到 7 列,用户看到 2 列。version仍落在依赖包那一行(写地址不动,已钉住)。回滚即还原packagelessContentPackage的两个调用点。packages/metadata-protocol/src/protocol-environment-rows.ts的packagelessContentPackage;protocol.ts里readMetaItem的contentPackageId一段;新测试packages/objectql/src/protocol-packageless-read-dependent-package.test.ts。pnpm --filter @objectstack/metadata-protocol build && pnpm --filter @objectstack/objectql exec vitest run src/protocol-packageless-read-dependent-package.test.ts,应见 5 passed。What changes
The by-name read (
getMetaItem) and the layered read (getMetaItemLayered) that name no package now take their stored content frompackagelessContentPackage(new,protocol-environment-rows.ts). It adds the code packages that ship the name as candidates beside the stored rows' bindings:null, the package-unbound row alone, else the code item. Several ship it and a row is bound to one: unchanged (undefined, best effort).readMetaItemapplies it to the active rows it already read;packagelessReadAddress(the layered read's address) applies it afterpackagelessAddress, and its host gainsshippedArtifactsOf.packagelessAddressitself is untouched, so the package-less save, delete and the read'sversionresolve exactly as before. Three content spreads now test!== undefined, sonullreachesfindServedOverlayRowas the package-unbound row.Files:
protocol-environment-rows.ts,protocol-lock-version-gates.ts,protocol.ts(thereadMetaItemlines, thepackagelessReadAddresshost and docblock, two spreads ingetMetaItemand one ingetMetaItemLayered), the new test, apatchchangeset.Tests
packages/objectql/src/protocol-packageless-read-dependent-package.test.ts, over the realObjectQLengine, itsSchemaRegistryand an in-memory driver. The dependent package is installed through the package door withdependencies: { com.example.showcase: '*' }, and its row is written through the draft save and the per-item publish naming it, as the card's steps wrote it._packageId: com.example.showcase, the layered read'seffectiveis the base's withoverlay: null, and the list scoped to the base serves the base's columns.packagelessAddressstill answers the dependent package, a package-less save writes the dependent package's row and mints no other, and the read naming no package still serves the base._packageId(metadata-protocol: a save naming a package beside an existing package-less row of the item mints a second row, and the read naming no package keeps serving the unbound one (S6, reachable through today's doors) #23291).Red on base 69ce01d: 3 failed, 1 passed (cases 1, 2, 5 red; 3 green; 4 added after)
The three production files written back to their base blobs,
@objectstack/metadata-protocolrebuilt, the pin file run (4 cases then), then restored withgit checkout HEAD --(git diff HEADempty) and rebuilt.The third was named CONTROL then; the red showed it is a changed answer, so it was split into the plain control (case 4) and case 5.
Ablation: the old one-shipper answer restored reds 3 of 5; the restore leg is green
node scripts/ablation-replace.mjsonprotocol-environment-rows.ts, anchorif (shippers.size === 1) return [...shippers][0];→return (globalThis as any).ABL28238 ?? (address.packageId ?? undefined);(the write address's package, as before). Anchor 1 → 0, blob1a530197d936→992e059d588a;ablation-dist-preflightfound the marker indist/index.jsanddist/index.cjs.Restore leg: rebuild,
ablation-dist-preflight --absent(marker absent from all 24 built files),Tests 5 passed (5).Package suites
@objectstack/metadata-protocol:Test Files 284 passed | 4 skipped (288),Tests 21860 passed | 26 skipped. Includesprotocol.item-read-overlay-cache.test.ts,meta-overlay-cache.test.tsandprotocol.packageless-save-address.test.ts(the metadata-protocol: a save naming a package beside an existing package-less row of the item mints a second row, and the read naming no package keeps serving the unbound one (S6, reachable through today's doors) #23291 / metadata-protocol: a package-less direct save of an item a package owns answers 200 and writes a package-less row the item read never serves; the draft save already resolves the owning package (#11087 / #22128) #23165 / metadata-protocol: the draft doors naming no package resolve three addresses — the save and read inherit the active binding, the discard reads the draft rows, the publish takes the first draft row a package-agnostic findOne returns #23495 / metadata-protocol: over drafts [package-unbound, Q] with no active row, the strict draft read naming no package serves its version from the package-unbound draft but its content from whichever row the store returns first #23715 pins).@objectstack/objectql(--project local):Test Files 501 passed (501),Tests 9755 passed.typecheckof both packages (objectql's includescheck:test-typecheck): exit 0.eslint --no-inline-configonpackages/metadata-protocoland the new test: exit 0.Acceptance notes
The card's acceptance is half met. Live on
examples/app-showcase(pnpm dev -- --fresh), the card's steps (the showcase's list now has 7 columns, not 6):?package=com.example.showcase?package=com.default.acme-custompackage?layers=trueeffectiveGET /meta/view?package=com.example.showcaseGET /meta/view(no package), items namedshowcase_task.default_packageId: com.example.showcase/_console/apps/com.example.showcase/showcase_taskheaders (Playwright)The app still renders 2. The console reads views only from the list naming no package (
ui/packages/app-shell/src/providers/MetadataProvider.tsx,EAGER_TYPESandcomposeObjects(..., readType('view'))), andmergeViewsIntoObjectskeys each view by name, so the last item of a name wins: the dependent package's slot. That list keeps one slot per package that holds a name by design (readFlattenedMetaItems, #21934: the env-wide list "holds every package's body of a name"), and the dependent package's slot wears the base's_packageId(the artifact envelopemergeArtifactProtectiongrafts). Changing either is a choice between a server list rule and a console merge rule, so it goes to the card as a decision; this PR does not touch the list.versionisnullwhen no row is served (the base has no row). So a package-less save followed by a package-less read does not read back what it wrote, for this one shape. The epic's write gate (N-09: undeclared same-name refused at publish) closes the shape at its source.mainhas no override declaration yet (N-08), so "no change to declared overrides" holds trivially.previewDraftsnaming no package still follow the draft address (packagelessDraftAddress), which is the write side.shippedArtifactsOfincludes a disabled package's artifact, so a disabled base's name now resolves to the base on the package-less read, which the read then withholds as a disabled package's item, where it used to serve the dependent package's row (read from the code, not measured).Verification
Derived gates on 21fa3e7 (62 commands, `dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`)
dispatch-gates --ran: 62 derived, 62 run, 0 NOT-MEASURED; every one exit 0.check-plugin-teardown-shape --self-testfirst answered 3 (its positive control commit was outside this shallow clone); aftergit fetch --depth=1 origin 621a4876…it answered 0, and that is the code recorded.Not run locally, declared to CI:
test:repo(Test Core), the artifact-roster and wide-population families, the type-check lanes.pnpm lintscoped to the two touched packages: exit 0.Generated by Claude Code