Repository navigation
Commit 7dae3b3
fix(metadata-protocol): a .custom pair's published override of a dashboard, report or email template no longer wears the base's envelope in the registry's bare slot (#29640)
<!-- claude-projects-attribution: {"github_login":"os-steve"} -->
_Requested by **Steve** · [project
thread](https://claude.ai/code/project/chan_01FdL1XeUyvVNxLreh3F4Qi9?thread=cmsg_01FdL1XeUyvVNxLreh3F4Qi92Y3Af8UHfhCRCgvN9XxUC5)_
Fixes #29600
Clause-②: no
Metadata-protocol: none
## 维护者速读
- 改了什么:`.custom` 配对包对基础包仪表盘、报表、邮件模板的已发布覆盖,不再以基础包的信封注册进注册表的裸槽位(沿用 #22057
对 view 的规则,扩到这三种类型)。
- 为什么改:基础包的按包层(导出读取它)会把覆盖项和基础包自己的项并列列出,注册表直读也会把覆盖项当成基础包的项。
- 风险与回滚:所有协议服务读取(按名读、有效列表、分层读)前后逐项测量不变;只有注册表直读不再看到覆盖。回滚即还原本 PR,无需迁移。
- 先看:`packages/metadata-protocol/src/protocol-package-bindings.ts` 中的
`declinesBorrowedEnvelope`,以及新测试
`protocol-custom-override-bare-slot-envelope.test.ts`。
- 两分钟验证:`pnpm --filter @objectstack/objectql exec vitest run
src/protocol-custom-override-bare-slot-envelope.test.ts
src/environment-overlay-email-locale-restart.test.ts`
- 需要你做的:无,席位落地
## What changes
A published override of a base's dashboard, report or email template,
saved in the base's `.custom` pair, was registered in the registry's
bare slot wearing the BASE's artifact envelope (`_packageId` = BASE,
`_provenance: 'package'`). The base's per-package layer, which the
base's export reads, then listed the override beside the base's own
item, and every registry-direct read of the base's item answered the
override.
The hydrator (`hydrateOverlayIntoRegistry`) now declines the bare slot
for a row of these three types when the row's bound package ships no
artifact of the name while another package does. This is the #22057 view
rule, widened per seat ruling A to dashboard, report and email_template.
The rule lives in `declinesBorrowedEnvelope` in
`protocol-package-bindings.ts`, a closed list of three types.
- View is unchanged (it keeps its own #22057 limb).
- The #4624 case keeps its bare entry: a row of a package that ships its
own artifact of the name.
- page, app, dataset, doc and book are left out on purpose. For them the
bare slot IS the served override (getMetaItem, getMetaItems, GET
/me/apps), so they need a row-served path first. That is follow-up
**#29613**, which also carries the two class-b findings from the
measurement.
- flow is left out: no envelope (Regime C), and the boot pull arms its
declared override from the bare slot.
- The delete-heal limb has no twin of the rule, because it registers the
baseline's own body and grafts no artifact envelope (docblock note only,
no code change).
Files (5, +424 / −17):
- `packages/metadata-protocol/src/protocol-package-bindings.ts`:
`declinesBorrowedEnvelope` and its type list.
- `packages/metadata-protocol/src/protocol.ts`: one guard line in the
hydrator, plus the header and heal docblocks.
-
`packages/objectql/src/protocol-custom-override-bare-slot-envelope.test.ts`
(NEW): the per-type pins. It sits in objectql because the pins need the
real ObjectQL engine, the in-memory driver and the real registry, and a
metadata-protocol test cannot import objectql (that would be a
dependency cycle). It reads metadata-protocol from `dist/`, like the
sibling `protocol-per-package-layer-provenance.test.ts` whose harness it
copies. No objectql source is edited.
-
`packages/objectql/src/environment-overlay-email-locale-restart.test.ts`:
forced edits, listed below.
- `.changeset/29600-pair-override-bare-slot-envelope.md`: patch for
`@objectstack/metadata-protocol`.
## Tests
New file
`packages/objectql/src/protocol-custom-override-bare-slot-envelope.test.ts`,
9 tests. Per type (view, dashboard, report, email_template) two tests:
- (a) The base's per-package layer lists the base's item alone, and the
pair's lists the override alone. The bare slot holds no entry for the
name (`name@en-US` for the email template). `registry.getItem` answers
the base's own item.
- (b) The served reads still serve the override: getMetaItem with no
package, BASE and PAIR; getMetaItems with no package and BASE;
getMetaItemLayered(BASE).effective.
A control pins #4624: OTHER also ships the dashboard and owns a stored
row, so its bare entry stays with OTHER's envelope and `_provenance:
'package'`.
Forced edits in
`packages/objectql/src/environment-overlay-email-locale-restart.test.ts`
(seat-approved, on condition that no non-test email_template registry
reader moves; the audit is under Acceptance notes). These four cases
pinned the registry entry of a pair's override of a SHIPPED member,
which is exactly the entry this PR removes. Each now pins the served
read, and keeps the registry pin on the shipped body:
1. "DELETE by member: resetting zh-CN removes its row only, and the
en-US overlay stays served — the registry included": the registry pins
become `PKG en` / `PKG zh`. The served reads go through the protocol
(`subjectOf(protocol, 'en-US')`), and after the restart
`subjectOf(after.protocol, 'en-US')` = `ADMIN en`.
2. "DELETE by member of a member NO artifact ships: its overlay leaves
the registry, and the canonical overlay stays": adds the by-name read
`getMetaItem({ type, name, member: 'en-US' })` = `ADMIN digest en`; the
registry entry for en-US is `PKG digest en`.
3. "DELETE with no member": the registry pin on zh-CN `ADMIN zh` is
replaced by `subjectOf(protocol, 'zh-CN')` = `ADMIN zh`.
4. "the cluster fan-out: a peer converges on the member a write changed,
and a member reset heals only that member": the peer converges through
`vi.waitFor` on `subjectOf(peer.protocol, 'zh-CN')` = `ADMIN zh`; the
registry pins are `PKG zh` / `PKG en` and stay `PKG zh` on both nodes
after the delete.
Ablation of the guard back to view-only. The anchor line in
`declinesBorrowedEnvelope` is replaced so that it returns false for
every type. The marker is confirmed in `dist/`, and the restore is
proved by an empty `git diff HEAD` and `ablation-dist-preflight
--absent` exit 0.
- New pin file alone: 3 red (dashboard, report, email_template
per-package tests), 6 green.
- New pin file plus the email-locale file: 6 red (those 3 plus
forced-edit cases 1, 2 and 4), 44 green.
- The (b) served-read tests stay green under the ablation by design:
those reads never came from the bare slot. The view pin stays green
because the #22057 limb guards view on its own.
- A first ablation attempt failed the build and ran no tests. It was
restored and is not counted.
<details><summary>Ablation reading (both files)</summary>
```
✓ dist/: marker present in 2 built files -- the ablation is live in the artifact the suite consumes.
× dashboard: the base's per-package layer lists the base's item alone, the pair's the override alone
× report: the base's per-package layer lists the base's item alone, the pair's the override alone
× email_template: the base's per-package layer lists the base's item alone, the pair's the override alone
× DELETE by member: resetting zh-CN removes its row only, and the en-US overlay stays served — the registry included
× DELETE by member of a member NO artifact ships: its overlay leaves the registry, and the canonical overlay stays
× the cluster fan-out: a peer converges on the member a write changed, and a member reset heals only that member
Test Files 2 failed (2)
Tests 6 failed | 44 passed (50)
DIFF-HEAD-EMPTY=yes
✓ dist/: marker absent from all 24 built files
ABSENT-EXIT=0
```
</details>
## Acceptance notes
Bar from the ruling: no protocol read moves. Every reader was measured
before (main 0ab4a09) and after (this branch) with the same harness:
real ObjectQL, the in-memory driver and the protocol from `dist/`. The
base is registered by `registerApp`, the pair installed by
`installPackage`, and the override is a draft save into the pair
declaring the override, then the publish. S is the shipped body, O the
override; the envelope is label / `_packageId` / `_provenance`.
| Reader | view | dashboard / report / email_template | page / app /
dataset / doc / book |
|---|---|---|---|
| bare slot entry | none → none | O/BASE/package → **none** | O/BASE →
O/BASE |
| registry.getItem (bare, BASE, PAIR) | unchanged | O → **S** |
unchanged |
| registry.listItems(BASE) | unchanged | [S, O] → **[S]** | unchanged |
| per-package layer, BASE | [S] → [S] | [S, O] → **[S]** | [S, O] → [S,
O] |
| per-package layer, PAIR | [O] → [O] | [O] → [O] | [] → [] |
| per-package layer, unscoped | unchanged | [O, O] → **[S, O]** |
unchanged |
| getMetaItem (none, BASE, PAIR) | O → O | O → O | O → O |
| getMetaItems (none, BASE) | [O] → [O] | [O] → [O] | [O] → [O] |
| getMetaItemLayered(BASE).effective | O → O | O → O | S → S |
| GET /me/apps | n/a | n/a | O → O (app) |
Only the bold cells move, and none of them is a protocol served read.
translation was not measurable through the pair (the save is refused
NOT_OVERRIDABLE in this harness). flow and action are outside the card.
email_template non-test registry readers, per the condition on the
forced edits:
| Reader | Reads | Moves? |
|---|---|---|
| plugin-email `template-loader.ts` | the protocol first (by-name read
with member); the registry only when no protocol is wired | no: the
protocol read serves O before and after |
| cli `organization-ownership.ts` TemplatePort | stored rows plus
`getMetaItemsForExecution` | no: neither reads the bare slot |
| objectql registry direct (`getItem`, `listItems`) | the registry |
yes, O → S (the intended change, bold above) |
| protocol by-name / list / layered | stored rows via
`findServedOverlayRow` and the list merge | no |
The plugin-email suite (48 files) and the cli ownership tests pass on
the fixed head, as listed under Verification.
Follow-up: **#29613** holds page, app, dataset, doc and book (row-served
override path first, then the guard), and the two class-b findings: the
pair's per-package layer is empty for those types, and their layered
effective serves S while getMetaItem serves O.
## Verification
Head c58da12 (`c58da128a9b58e5d34788a9cabe48c1a87c1590f`) (main
merged in). All heavy runs went through `scripts/pm/os-verify-lock.sh`,
and each VERDICT is command-exit 0.
- Full build `./packages/**` and `./examples/**`: 79/79 tasks.
- `@objectstack/metadata-protocol` test: 308 files passed (4 skipped),
22257 tests passed (26 skipped).
- `@objectstack/objectql` test: 542 files, 10517 tests passed.
- Typecheck of metadata-protocol plus 11 dependents (cli, client,
cloud-connection, objectql, plugin-approvals, plugin-security, rest,
runtime, service-automation, service-messaging, service-package): 12/12
tasks.
- Dogfood, 17 files touching email templates, metadata reads, package
customization and overrides: 17 files, 138 tests passed.
- Cross-package pair-override suites: runtime 6 files / 59 tests
(including `packages-export-base-unchanged-per-type.integration.test.ts`
from #29597), cloud-connection 3 / 27, cli 2 / 118
(organization-ownership apply and withdrawal-pair), plugin-email 48 /
785.
- eslint `--no-inline-config` on packages/metadata-protocol and
packages/objectql: exit 0.
- `dispatch-gates --commands`: 62 derived, 62 run, every one exit 0;
`dispatch-gates --ran` reports 0 NOT-MEASURED, 0 UNRUN.
<details><summary>Dogfood files</summary>
- `test/email-template-materialization.dogfood.test.ts`
- `test/email-template-overlay-survives-boot.dogfood.test.ts`
- `test/email-template-two-locales-survive-boot.dogfood.test.ts`
- `test/meta-read-doors-live.dogfood.test.ts`
- `test/package-commit-revert-path-package.dogfood.test.ts`
- `test/public-form-withdrawal-walled.dogfood.test.ts`
- `test/showcase-public-form-redirect.dogfood.test.ts`
- `test/showcase-public-form-walled-intake.dogfood.test.ts`
- `test/showcase-public-form-withdrawal-layers.dogfood.test.ts`
- `test/showcase-public-form-withdrawal.dogfood.test.ts`
- `test/email-template-boot-sweep.test.ts`
- `test/email-template-dependent-unshipped-locale-read.dogfood.test.ts`
- `test/email-template-override-unshipped-locale.dogfood.test.ts`
- `test/managed-content-sealed.dogfood.test.ts`
- `test/view-container-cross-package-default.dogfood.test.ts`
- `test/package-first-authoring.dogfood.test.ts`
- `test/wall-authoring-trail-doors.dogfood.test.ts`
</details>
<details><summary>Gate reconciliation</summary>
```
Run reconciliation — 62 derived, 62 run, 0 NOT-MEASURED, 0 UNRUN.
✓ dispatch-gates --ran: 62 derived famil(ies) accounted for — 62 run, 0 NOT-MEASURED (a DERIVED zero — all 62 recorded an exit code and none of them is 3).
```
</details>
_Generated by [Claude
Code](https://claude.ai/code/session_01ADHkmQWkCUmHFTi97EGNu1)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent b391feb commit 7dae3b3
5 files changed
Lines changed: 424 additions & 17 deletions
File tree
- .changeset
- packages
- metadata-protocol/src
- objectql/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
296 | 296 | | |
297 | 297 | | |
298 | 298 | | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
299 | 334 | | |
300 | 335 | | |
301 | 336 | | |
| |||
422 | 457 | | |
423 | 458 | | |
424 | 459 | | |
| 460 | + | |
425 | 461 | | |
426 | 462 | | |
427 | 463 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
533 | 533 | | |
534 | 534 | | |
535 | 535 | | |
| 536 | + | |
536 | 537 | | |
537 | 538 | | |
538 | 539 | | |
| |||
10820 | 10821 | | |
10821 | 10822 | | |
10822 | 10823 | | |
10823 | | - | |
10824 | | - | |
10825 | | - | |
10826 | | - | |
| 10824 | + | |
| 10825 | + | |
| 10826 | + | |
| 10827 | + | |
| 10828 | + | |
10827 | 10829 | | |
10828 | 10830 | | |
10829 | 10831 | | |
10830 | 10832 | | |
10831 | 10833 | | |
10832 | 10834 | | |
| 10835 | + | |
| 10836 | + | |
| 10837 | + | |
| 10838 | + | |
| 10839 | + | |
| 10840 | + | |
| 10841 | + | |
| 10842 | + | |
| 10843 | + | |
| 10844 | + | |
| 10845 | + | |
| 10846 | + | |
| 10847 | + | |
| 10848 | + | |
| 10849 | + | |
| 10850 | + | |
| 10851 | + | |
| 10852 | + | |
| 10853 | + | |
| 10854 | + | |
| 10855 | + | |
| 10856 | + | |
10833 | 10857 | | |
10834 | 10858 | | |
10835 | 10859 | | |
| |||
10984 | 11008 | | |
10985 | 11009 | | |
10986 | 11010 | | |
| 11011 | + | |
| 11012 | + | |
| 11013 | + | |
| 11014 | + | |
| 11015 | + | |
| 11016 | + | |
10987 | 11017 | | |
10988 | 11018 | | |
10989 | 11019 | | |
| |||
11821 | 11851 | | |
11822 | 11852 | | |
11823 | 11853 | | |
11824 | | - | |
| 11854 | + | |
| 11855 | + | |
| 11856 | + | |
11825 | 11857 | | |
11826 | 11858 | | |
11827 | 11859 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
401 | 401 | | |
402 | 402 | | |
403 | 403 | | |
404 | | - | |
| 404 | + | |
| 405 | + | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
405 | 410 | | |
406 | 411 | | |
407 | 412 | | |
| |||
445 | 450 | | |
446 | 451 | | |
447 | 452 | | |
448 | | - | |
449 | | - | |
| 453 | + | |
| 454 | + | |
| 455 | + | |
| 456 | + | |
| 457 | + | |
| 458 | + | |
450 | 459 | | |
451 | 460 | | |
452 | 461 | | |
453 | 462 | | |
454 | | - | |
| 463 | + | |
455 | 464 | | |
456 | | - | |
| 465 | + | |
457 | 466 | | |
458 | 467 | | |
459 | 468 | | |
460 | 469 | | |
461 | 470 | | |
462 | | - | |
| 471 | + | |
463 | 472 | | |
464 | 473 | | |
465 | 474 | | |
| |||
479 | 488 | | |
480 | 489 | | |
481 | 490 | | |
482 | | - | |
| 491 | + | |
| 492 | + | |
| 493 | + | |
| 494 | + | |
483 | 495 | | |
484 | 496 | | |
485 | 497 | | |
| |||
491 | 503 | | |
492 | 504 | | |
493 | 505 | | |
494 | | - | |
| 506 | + | |
| 507 | + | |
495 | 508 | | |
496 | 509 | | |
497 | 510 | | |
| |||
739 | 752 | | |
740 | 753 | | |
741 | 754 | | |
742 | | - | |
743 | | - | |
| 755 | + | |
| 756 | + | |
| 757 | + | |
| 758 | + | |
| 759 | + | |
| 760 | + | |
| 761 | + | |
744 | 762 | | |
745 | 763 | | |
746 | | - | |
747 | | - | |
| 764 | + | |
| 765 | + | |
| 766 | + | |
748 | 767 | | |
749 | 768 | | |
750 | 769 | | |
| |||
0 commit comments