Repository navigation
fix(metadata-protocol): a .custom pair's published override of a page, app, dataset, doc or book is served from its row by every read - #29707
Conversation
… page, app, dataset, doc or book from its row Claude-Session: https://claude.ai/code/session_01JQwxbXCAvuF6biLpPaEcJ5 Co-authored-by: Claude <noreply@anthropic.com>
…tocol's served list Claude-Session: https://claude.ai/code/session_01JQwxbXCAvuF6biLpPaEcJ5 Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JQwxbXCAvuF6biLpPaEcJ5 Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JQwxbXCAvuF6biLpPaEcJ5 Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 package(s): 10 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 7 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 18 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 008b892a8c91953e8b94ff160d020f32d3e87254 && git checkout 008b892a8c91953e8b94ff160d020f32d3e87254
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin c9636f1e50e48a2fee5205f0a9978c2a63f2dc07 bdfd50d5da015bd6877318bf4c8169f5b14ab996 && git checkout -B drift-repro c9636f1e50e48a2fee5205f0a9978c2a63f2dc07 && git merge --no-ff bdfd50d5da015bd6877318bf4c8169f5b14ab996
node scripts/docs-audit/affected-docs.mjs --json c9636f1e50e48a2fee5205f0a9978c2a63f2dc07
|
…erride for page, app, dataset, doc and book too Claude-Session: https://claude.ai/code/session_01JQwxbXCAvuF6biLpPaEcJ5 Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JQwxbXCAvuF6biLpPaEcJ5 Co-authored-by: Claude <noreply@anthropic.com>
|
domain:server#4 · os-warren · session_01JQwxbacSXJQqAztgt4UMrS 席位核对ACCEPT ·
|
Requested by Warren · project thread
Fixes #29613
Clause-②: no
Metadata-protocol: none
维护者速读
.custom配对包对基础包页面、应用、数据集、文档、文档书的已发布覆盖,所有读取都从覆盖自己的存储行提供,和仪表盘、报表一样;注册表裸槽位不再放一份穿着基础包信封的副本。/me/apps改读协议的有效列表。packages/metadata-protocol/src/protocol-lock-state.ts的declinesStoredRow;protocol-package-bindings.ts的两个类型清单;plugin-hono-server的servedAppsOf。pnpm --filter @objectstack/objectql exec vitest run src/protocol-custom-override-bare-slot-envelope.test.ts,预期 24 个用例全绿(其中五种类型各两种启动形态、重启前后)。What changes
A
.custompair's published override of a base'spage,app,dataset,docorbookis now served from its stored row by every read, the way a dashboard's or report's already is. The registry's bare slot no longer holds a copy of it wearing the base's envelope.These five types are sealed (ADR-0131 D6:
allowEnvironmentOverlay: false). Every read askeddeclinesStoredRow, which declined their stored rows as overlays of managed content (isUnservedSealedOverlay), and that included the pair's declared override. The only place the override was served from was the bare slot. With a metadata service (every real boot), the by-name read answered the service's copy of the base's body before it reached the registry.packages/metadata-protocol/src/protocol-lock-state.ts:declinesStoredRowadmits, for these five types, the shipping package's declared override, judged byisServedOverrideRowOfon the row's ownpackage_idand body againstpackagedArtifactOwner. This is the same admission flow got in service-automation: a declared override in <base>.custom is the packaged flow — armed over the code package, rebound in one reload, one ledger switch #28412, and the same predicate the boot's sealed-overlay census already uses to leave the override out of its warning. Every other sealed row of the name stays declined. Every reader already passes the row it serves (by-name, both lists, layered, the per-package customizations read), so no reader is edited.packages/metadata-protocol/src/protocol-package-bindings.ts: the closed listSEALED_ROW_SERVED_OVERRIDE_TYPESandservesSealedOverrideRow; the five types joinBORROWED_ENVELOPE_DECLINED_TYPES, so the 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 guard keeps their row out of the bare slot under metadata-protocol: a published .custom override of a dashboard or report is registered in the registry's bare slot wearing the base's envelope (the #22057 guard spares only views) #29600's condition (the row's package ships no artifact of the name while another package does). The loadMetaFromDb boot hydration keeps a third inline copy of the overlay→registry rule with an UNSCOPED artifact lookup (ADR-0048 gap) #4624 two-shipper case keeps its bare entry.mapping(overridable and sealed) is left out: no card measured its reads.actionandflowwait for ADR-0146 D9.packages/metadata-protocol/src/protocol.ts: comments only. Three docblock sentences that said these five types still register in the bare slot are corrected. No code line changes.packages/plugins/plugin-hono-server/src/current-user-endpoints.ts:GET /me/appslists the protocol's served apps (getMetaItems({ type: 'app' }), whatGET /meta/appserves) when a protocol service with that read is present. With no protocol service, today's registry path is unchanged. A protocol list read that throws is warned once and answered from the registry. File surface confirmed with the seat first..changeset/29613-sealed-pair-override-row-served.md: patch for both packages.Why
ADR-0146 D8 item 2: every read that serves a base's name serves the
.customitem that declares it. Item 3: the pair's per-package layer carries the overriding item, and the base's carries the base unchanged. Measured on main744211fc(table below), these five types broke both items, and on a real boot the by-name read and the list disagreed about the same name.The card's two hypotheses reconcile: the #29600 harness has no metadata service, so its by-name read fell to the bare slot (override); the #29649 real-boot reading has one, so its by-name read answered the shipped body. Both readings are reproduced below, one harness run each way.
Tests
Before and after, per type. Identical for
page,app,dataset,docandbook. S = the shipped body, O = the pair's published override. Before = main744211fc, the #29600 harness, without and with a metadata service holding the code package's bodies. After = this branch, both shapes, and after a restart.getMetaItem(no package, BASE, PAIR)getMetaItems(no package, BASE)getMetaItemLayered(BASE).effectiveregistry.getItem(bare, BASE, PAIR)registry.getAllApps(app)GET /me/apps(app)Each moved reading is seat-approved as code aligning to D8 (no protocol change, so
Clause-②: noandMetadata-protocol: none).New and extended pins:
packages/objectql/src/protocol-custom-override-bare-slot-envelope.test.ts: a second top-level describe in metadata-protocol: a published .custom override of a dashboard or report is registered in the registry's bare slot wearing the base's envelope (the #22057 guard spares only views) #29600's shared harness file (the rule on main since docs(agents): a new pin for a component with a shared harness file goes into that file #29633). Per type, with and without a metadata service, every reader in the table at once and after a restart over the same stores (10 tests). CONTROL per type: a package-less environment row of the base's name (pre-seal hatch residue) stays unserved: by-name and layered answer S (5 tests). The driver now takes optional shared stores and a module-level id counter so a restart works; the metadata-protocol: a published .custom override of a dashboard or report is registered in the registry's bare slot wearing the base's envelope (the #22057 guard spares only views) #29600 tests are untouched otherwise. 24/24 green.packages/metadata-protocol/src/protocol-package-bindings.sealed-override.test.ts(new; the module had no test file): the two closed lists agree, plural spellings fold,mapping/action/flow/regime-O types are outside, the loadMetaFromDb boot hydration keeps a third inline copy of the overlay→registry rule with an UNSCOPED artifact lookup (ADR-0048 gap) #4624 case keeps its bare entry. 20/20 green.packages/plugins/plugin-hono-server/src/current-user-endpoints-delegated-resolution.test.ts: a[#29613]describe beside the file's other/me/appspins. With a protocol service the served list is the source and the registry is not read; with none, or with a protocol that has no list read, the registry as before; a failing list read is warned and answered from the registry. 14/14 green.Ablation (both mechanism lines back to main's behaviour)
The fix was committed first.
servesSealedOverrideRowwas mutated to return false (markerABLATION29613) and the five types were removed fromBORROWED_ENVELOPE_DECLINED_TYPES(markerABLATION29613B),grep -c= 2 on disk, thenpnpm --filter @objectstack/metadata-protocol build.The preflight's presence leg printed a refusal about an untracked path at that moment instead of a marker count. The 10 objectql reds read the protocol from
dist/, which is the evidence the mutation reached it./me/appsablation (ablation-replace.mjs,servedAppsOf(ctx)replaced byundefined, source-imported): 2 failed | 2 passed (the two served-list cases red; the two registry cases green by design); restored, blob equals HEAD.Package suites (on the branch before the merge of main)
After the merge of main (closure rebuilt): runtime
packages-export-base-unchanged-per-type.integration.test.ts,packages-customizations.test.ts,packages-ensure-customization.test.ts: 56/56, exit 0.typecheckfor metadata-protocol, objectql, plugin-hono-server: exit 0.#29649's showcase-boot export pin:
pairServedwidened to all eight types (f40dd0e68); 8/8 green, ablation 5 red.Acceptance notes
packagedArtifactOwner(the first package the artifact lookup answers), as flow's admission does. A pair override of the OTHER shipper would stay declined, and with the guard its bare entry goes too. Not measured; no shipped stack has two packages shipping one page, app, dataset, doc or book name.mappingis overridable and sealed and has the same shape. Left out on purpose (no measurement).33ee9aa1); its pin re-run on the merged head, green, and widened.packages/runtime/src/domains/packages.ts) is not touched. With this PR the base's per-package layer lists S alone for these types, which is what packages: a published dashboard or report override removes the item from the base's export, where ADR-0146 D8 item 3 says the base export is unchanged #29649's filter assumes.Verification
Local subset on head
90267e800(after the merge of main), derived withnode scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(8 paths), each exit captured before any pipe, then reconciled:pnpm check:dual-build-cjs-loads, reason: PREREQUISITE NOT MET (it reads every package'sdist/, and only this change's closure is built here). CI'sLint & Repo Gatesruns it.node scripts/check-plugin-teardown-shape.mjs --self-testfirst exited 3 (its pinned fixture commit was outside the shallow clone); aftergit fetch origin 621a4876it exits 0.pnpm lint: exit 0.Recorded exit codes
Human Minimum
packages/specholds no runtime logic.pnpm lintpasses.main, one pull request.packages/spectouched: its generated artifacts are regenerated and committed. (Not touched.)content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.Generated by Claude Code