Commit ec390ec
Fixes #21511
Clause-②: no
## What this changes
On an unscoped (control-plane) kernel, registry hydration registers
every view a stored environment-wide view container expands, each under
its own name. `hydrateOverlayIntoRegistry` registers the container with
the tenant-authorship marker (`stateTenantAuthorship`, ADR-0010
`_provenance: 'org'`), and `hydrateExpandedViewItems` registered the
expansions without it. An expansion of a package-bound container
therefore sat under its bare name, wearing that package's `_packageId`
and no marker, and `SchemaRegistry.getArtifactItem`'s bare-key fallback
took it for a view the package ships.
This PR implements triage's ruling (comment 5964348889): each expansion
inherits its container's authorship.
- `hydrateExpandedViewItems` asks `expandRuntimeViewContainer` for
tenant-authored expansions (`tenantAuthored: true`).
- Under that option, `expandRuntimeViewContainer` applies
`stateTenantAuthorship` to each expansion BEFORE that expansion's own
artifact envelope is merged over it. This is the order the container
gets (`mergeArtifactProtection(stateTenantAuthorship(data), envelope)`),
so where the container's own package ships a view of that name, the
artifact's `_provenance`, `_packageId` and `_lock` still win (ADR-0010
§3.3).
- The two registry-free reads that call `expandRuntimeViewContainer`
(the list read's expansion pass and the by-name read's step 1b, through
`expandStoredViewContainers`) pass no such option and serve exactly what
they served before.
- No reader changed. `isArtifactBacked` (which `resettable` reads) and
the layered read's `code` arm already ask `isTenantAuthored`; they now
get the marker to read.
## Measured before the change
At base `a7ab047c`, with the #21508 harness (`showcaseHarness`) and the
card's probe (a container `os_qa_probe` on `showcase_task` with
`listViews.in_progress`, read as
`showcase_task.os_qa_probe.in_progress`):
| kernel | container | `getMetaItem(...).resettable` |
`getMetaItemLayered(...).code` |
|---|---|---|---|
| `env_local` | package-bound (`com.example.repairassets`) | `false` |
`null` |
| `env_local` | package-less, environment-wide | `false` | `null` |
| unscoped | package-bound | **`true`** | **the hydrated expansion** |
| unscoped | package-less, environment-wide | `false` | **the hydrated
expansion** |
At the base the registry held the container as `{ name: 'os_qa_probe',
_provenance: 'org' }` and the expansion as `{ name:
'showcase_task.os_qa_probe.in_progress', _packageId:
'com.example.repairassets' }`, with no `_provenance`. For the
package-less arm the expansion carried no `_packageId`, so `resettable`
was already `false`. Its `code` layer was still wrong, through the
layered read's runtime-only `getItem` arm, which drops only
tenant-marked entries.
After the change, both kernels give `env_local`'s answer for every
member kind in both arms.
## The save door (ruling: no save-door rule change)
The fix changes what `isArtifactBacked` answers for an expanded name on
the unscoped kernel, and the save door reads that predicate. The door's
acceptance is pinned rather than assumed. After the container is saved,
a write by the expanded name is accepted on both kernels and in both
arms, stored once in the container's scope, and that row then answers
the name on the by-name read and on the object door. The outcome is
identical across the two kernels. The same probe was accepted on all
four kernel and arm combinations at the base. Under reverse verification
leg 1 below, the save-door pins stay green, so the acceptance does not
move with the fix.
## Tests
`packages/metadata-protocol/src/view-container-runtime-expansion.test.ts`
gets a new describe block, `#21511 an expanded view of a stored
container answers as tenant-authored on both kernels`, with 15 tests in
#21508's harness. It covers the package-bound and package-less
environment-wide arms. Hydration never registers an organization-scoped
row.
- For each arm and each of the five member kinds: the expanded view is
not resettable and has no `code` layer on either kernel, and the
unscoped kernel's whole answer equals `env_local`'s (`resettable`,
`editable`, `deletable`, `lock`, `provenance`, `packageId`, `code`).
That is 10 tests.
- For each arm: on the unscoped kernel, every registered expansion
carries the container's marker, keeps the container's package, and is
not a code artifact (`isCodeArtifactBody`).
- For each arm: the save-door pin described above.
- CONTROL: a package-less overlay of the showcase's own `showcase_task`
container. Its expansions, `showcase_task.default` and
`showcase_task.in_progress`, stay resettable with the packaged `code`
layer on both kernels, and on the unscoped kernel the registered
expansion keeps the artifact's envelope (`_provenance: 'package'`,
`_packageId: com.example.showcase`) over the marker.
## Reverse verification (both runs recorded)
Each run starts from the committed fix, and each leg restores with `git
checkout HEAD -- ABSOLUTE_PATH`. Each restore is proven by blob hash
equal to the HEAD blob, an empty `git diff HEAD`, and a clean `git
status`, all inside a script armed with `trap restore EXIT INT TERM`.
The subject is imported from source (`./index.js`), so no `dist/` is
involved.
**Run 1, at `e8e00609`** (the fix commit, before merging main):
- Leg 1: `protocol.ts` was reverted to the base blob `3ac2573f` (`git
restore --source=a7ab047c`). The landing was proven by the on-disk blob
equalling the base blob; the base blob carries 0 occurrences of
`tenantAuthored: true`, and HEAD carries 1. Result: **12 failed, 132
passed (144)**. The 10 `resettable`/`code` pins failed with `unscoped:
no package ships it: expected true to be false` (package-bound) and
`unscoped: no artifact, so no code layer: expected {…} to be null`
(package-less). The 2 marker pins failed with `expected undefined to be
'org'`. The 2 save-door pins and the CONTROL stayed green.
- Leg 2: the marker was applied after the envelope instead of before,
through `scripts/ablation-replace.mjs`, with anchor 1→0, replacement
0→1, and blob `b106f11e` → `5d6e6263`. Result: **1 failed, 143 passed**.
Only the CONTROL failed: `showcase_task.default: the artifact's envelope
is merged over the marker, not under it: expected { _provenance: 'org' }
… { _provenance: 'package' }`.
- Restored HEAD: **144 passed**.
**Run 2, at `09033d87`** (after merging `origin/main` `6c5697df`, which
includes the landed #21545 as `eb9ef791`):
- Leg 1 reverted to the merged base's blob `24cd0629` (`6c5697df`): **12
failed, 132 passed**, with the same 12 tests and the same messages.
- Leg 2: **1 failed, 143 passed**, the CONTROL alone.
- HEAD: **144 passed**.
## Verification at `09033d87`
- `pnpm --filter @objectstack/metadata-protocol exec vitest run
--maxWorkers=2`: Test Files 207 passed, 3 skipped (210); Tests 3207
passed, 19 skipped (3226); `VERDICT command-exit 0`.
- `pnpm --filter @objectstack/metadata-protocol typecheck` (`tsc
--noEmit`) exited 0. Its `--listFiles` reaches 210 of the package's 210
test files, including the edited test file.
- Lint, as a proven narrowing: `eslint --no-inline-config --format json`
over the two edited `.ts` files gives 2 files, 0 errors and 0 warnings.
The changeset `.md` is outside every `files` glob of
`eslint.config.mjs`. `--print-config` shows no `parserOptions.project`
or `projectService` (type-aware linting is not enabled), so this diff
cannot move the verdict on any untouched file. The full `pnpm lint` is
CI's.
- Gates: `node scripts/pm/dispatch-gates.mjs --commands` (no paths)
derived 64 families for this change set. 63 exited 0. `pnpm
check:dual-build-cjs-loads` is **NOT MEASURED**: it exited 3
(PREREQUISITE NOT MET), because it needs every package's `dist/` and 67
had none. This diff changes no exports, entry points or build config.
`--ran` reconciliation: 64 accounted, 63 run, 1 NOT MEASURED. Two first
runs were prerequisite misses and were re-run green after the
prerequisite was met. `check-plugin-teardown-shape --self-test` needed
its pinned fixture commit fetched into the shallow clone.
`check:lean-entry-closure` needed `@objectstack/objectql` built.
## Region and surface
`protocol.ts` hunks: the `stateTenantAuthorship` docblock (its "ONE
caller" sentence now names both callers), `expandRuntimeViewContainer`'s
options type and its merge line, and `hydrateExpandedViewItems`' call
and docblock. The claim names the `hydrateExpandedViewItems` region.
`expandRuntimeViewContainer` sits in the same hydration block, and the
stamp must go there so that it precedes each expansion's own envelope
(the order above), without a second copy of the envelope rule. That is
the one widening of the region, declared here. The save door and the
data door's read region are not edited. #21545's hunks (landed as
`eb9ef791`, merged here) are disjoint from these.
## Acceptance notes
- **Card premise, refined:** for the package-less environment-wide arm,
only the `code` layer was wrong at the base; `resettable` was already
`false` (table above). Both values are pinned now.
- **Save-door intent on the unscoped kernel:** for a write by an
expanded name of a package-bound container, `isArtifactBacked` now
answers `false`, as on `env_local`. Reading the save path (not
separately measured), the write intent it derives is therefore the
runtime-only one rather than the artifact-override one. The ruling
expects this ("layered by the corrected predicate on both kernels").
Acceptance is unchanged, as measured above.
- **Not measured over REST.** The reads are pinned at the protocol
methods the by-name and `/layers` REST routes call.
- The branch was merged with `origin/main` at `6c5697df`. One later main
commit (`f6b75208`, spec conformance-case notes and a lint test) is not
merged. It touches none of this PR's files.
Changeset: `.changeset/21511-expansion-tenant-marker.md`, `patch` for
`@objectstack/metadata-protocol`.
---
_Generated by [Claude
Code](https://claude.ai/code/session_017ErfyP2Rx7XWHJA27QjyUi)_
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 9a4182a commit ec390ec
3 files changed
Lines changed: 173 additions & 5 deletions
File tree
- .changeset
- packages/metadata-protocol/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1717 | 1717 | | |
1718 | 1718 | | |
1719 | 1719 | | |
1720 | | - | |
1721 | | - | |
| 1720 | + | |
| 1721 | + | |
| 1722 | + | |
| 1723 | + | |
1722 | 1724 | | |
1723 | 1725 | | |
1724 | 1726 | | |
| |||
16790 | 16792 | | |
16791 | 16793 | | |
16792 | 16794 | | |
16793 | | - | |
| 16795 | + | |
| 16796 | + | |
| 16797 | + | |
| 16798 | + | |
| 16799 | + | |
| 16800 | + | |
| 16801 | + | |
| 16802 | + | |
| 16803 | + | |
| 16804 | + | |
| 16805 | + | |
| 16806 | + | |
16794 | 16807 | | |
16795 | 16808 | | |
16796 | 16809 | | |
| |||
16824 | 16837 | | |
16825 | 16838 | | |
16826 | 16839 | | |
16827 | | - | |
| 16840 | + | |
| 16841 | + | |
| 16842 | + | |
| 16843 | + | |
| 16844 | + | |
16828 | 16845 | | |
16829 | 16846 | | |
16830 | 16847 | | |
| |||
16991 | 17008 | | |
16992 | 17009 | | |
16993 | 17010 | | |
| 17011 | + | |
| 17012 | + | |
| 17013 | + | |
| 17014 | + | |
| 17015 | + | |
| 17016 | + | |
| 17017 | + | |
| 17018 | + | |
| 17019 | + | |
| 17020 | + | |
| 17021 | + | |
| 17022 | + | |
| 17023 | + | |
| 17024 | + | |
| 17025 | + | |
16994 | 17026 | | |
16995 | 17027 | | |
16996 | 17028 | | |
16997 | 17029 | | |
16998 | 17030 | | |
16999 | 17031 | | |
17000 | 17032 | | |
17001 | | - | |
| 17033 | + | |
17002 | 17034 | | |
17003 | 17035 | | |
17004 | 17036 | | |
| |||
Lines changed: 123 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1062 | 1062 | | |
1063 | 1063 | | |
1064 | 1064 | | |
| 1065 | + | |
| 1066 | + | |
| 1067 | + | |
| 1068 | + | |
| 1069 | + | |
| 1070 | + | |
| 1071 | + | |
| 1072 | + | |
| 1073 | + | |
| 1074 | + | |
| 1075 | + | |
| 1076 | + | |
| 1077 | + | |
| 1078 | + | |
| 1079 | + | |
| 1080 | + | |
| 1081 | + | |
| 1082 | + | |
| 1083 | + | |
| 1084 | + | |
| 1085 | + | |
| 1086 | + | |
| 1087 | + | |
| 1088 | + | |
| 1089 | + | |
| 1090 | + | |
| 1091 | + | |
| 1092 | + | |
| 1093 | + | |
| 1094 | + | |
| 1095 | + | |
| 1096 | + | |
| 1097 | + | |
| 1098 | + | |
| 1099 | + | |
| 1100 | + | |
| 1101 | + | |
| 1102 | + | |
| 1103 | + | |
| 1104 | + | |
| 1105 | + | |
| 1106 | + | |
| 1107 | + | |
| 1108 | + | |
| 1109 | + | |
| 1110 | + | |
| 1111 | + | |
| 1112 | + | |
| 1113 | + | |
| 1114 | + | |
| 1115 | + | |
| 1116 | + | |
| 1117 | + | |
| 1118 | + | |
| 1119 | + | |
| 1120 | + | |
| 1121 | + | |
| 1122 | + | |
| 1123 | + | |
| 1124 | + | |
| 1125 | + | |
| 1126 | + | |
| 1127 | + | |
| 1128 | + | |
| 1129 | + | |
| 1130 | + | |
| 1131 | + | |
| 1132 | + | |
| 1133 | + | |
| 1134 | + | |
| 1135 | + | |
| 1136 | + | |
| 1137 | + | |
| 1138 | + | |
| 1139 | + | |
| 1140 | + | |
| 1141 | + | |
| 1142 | + | |
| 1143 | + | |
| 1144 | + | |
| 1145 | + | |
| 1146 | + | |
| 1147 | + | |
| 1148 | + | |
| 1149 | + | |
| 1150 | + | |
| 1151 | + | |
| 1152 | + | |
| 1153 | + | |
| 1154 | + | |
| 1155 | + | |
| 1156 | + | |
| 1157 | + | |
| 1158 | + | |
| 1159 | + | |
| 1160 | + | |
| 1161 | + | |
| 1162 | + | |
| 1163 | + | |
| 1164 | + | |
| 1165 | + | |
| 1166 | + | |
| 1167 | + | |
| 1168 | + | |
| 1169 | + | |
| 1170 | + | |
| 1171 | + | |
| 1172 | + | |
| 1173 | + | |
| 1174 | + | |
| 1175 | + | |
| 1176 | + | |
| 1177 | + | |
| 1178 | + | |
| 1179 | + | |
| 1180 | + | |
| 1181 | + | |
| 1182 | + | |
| 1183 | + | |
| 1184 | + | |
| 1185 | + | |
| 1186 | + | |
| 1187 | + | |
1065 | 1188 | | |
1066 | 1189 | | |
1067 | 1190 | | |
| |||
0 commit comments