Repository navigation
Commit 3237b4a
Fixes #21804
Clause-②: no
## What changes
A package's slot in the metadata list now serves the stored row that
`getMetaItem` naming that package serves, whatever order the store
returns the rows in.
- **One resolution, two readers.** The order is one function,
`servedOverlayRowCandidates(address)` in
`packages/metadata-protocol/src/protocol.ts`. Its input is the address
`{ organizationId, packageId }`. It returns the ordered candidates `{
scope, organizationId, spelling, packageId }`: the organization's scope,
then env-wide (ADR-0005); within each, the canonical spelling, then the
other (#4432); within each, the package's own row, then the package-less
row, never another package's (ADR-0048). With no package, any row.
- `findServedOverlayRow` (`getMetaItem`, its draft-preview arm,
`getMetaItemLayered`) reads it from the store: one `findOne` per
candidate, first hit served. This is the same `findOne` sequence as
before, now spelled once.
- `mergePackageAwareOverlay` reads it over the rows the list already
holds (`servedStoredRow`). Each stored row travels into the merge with
its place (`stored: { organizationId, type }`), and the caller passes
`rowsRead: { organizationId }`. The list's active-overlay merge and its
`previewDrafts` merge both pass them.
- No second resolver: the list iterates the same candidate list; it does
not restate the order.
- **What stays.** The lower layer (registry artifact) still loses to any
stored row of the package or package-less. A package with no row of its
own still falls back to the package-less row, stamped as the package's.
The MetadataService merge, whose records are already-resolved items,
keeps its latest-wins rule. The list item's lock still comes from
`resolveOverlayLockLayer` (#21761); that block is untouched.
- **Landing point**, as the claim said: `mergePackageAwareOverlay` in
`readFlattenedMetaItems`. `StoredOverlayEntry` gains `type` (the row's
stored spelling). No `packages/spec` change.
## H1: the two resolutions at the merge base (`18c2ddc1ec`)
- `findServedOverlayRow`: per scope (org, then env), per spelling,
`findOne` with `package_id` = the package, then `package_id` = null.
First hit served.
- `mergePackageAwareOverlay`: per slot and per package P, the LATEST
contribution (registry items first, then the stored rows in row order)
whose package is P or none. The stored rows come from
`readActiveOverlayRows`: env rows, then the org's, merged by (package,
name) with an org row replacing an env row in place. So "latest"
depended on the store's order and on that map's insertion order.
- Slot body for package A, engine double, unscoped list, env-wide rows:
- A's row and the package-less row, order [package-less, A]: A's row.
Order [A, package-less]: the package-less row, stamped A. `getMetaItem`
naming A: A's row in both orders.
- Only A's row: A's row. Only the package-less row (under A's artifact):
the package-less row, stamped A. Both agree with `getMetaItem` naming A.
## Census: list slot vs getMetaItem, base vs this head
Engine double, `view` rows. 16 arrangements over five rows (env
package-less, env A, env B, org package-less, org A, including no row,
one row, both, and a third package), every row order, with and without
A's artifact. Each case compares the list's slot for A with
`getMetaItem` naming A.
| list request | disagreements at `18c2ddc1ec` | at this head |
|---|---|---|
| no package, no organization | 39 / 120 | 0 / 120 |
| organization | 41 / 120 | 0 / 120 |
| package A | 5 / 120 | 5 / 120 |
| organization + package A | 20 / 120 | 20 / 120 |
The 25 that remain are all in a list scoped to one package. That is the
row read, not this merge (Acceptance notes, first item).
## H3: everything else in the list is unchanged
- **Content dumps.** Over those arrangements, orders and artifacts, with
five list requests (no package, package A, package B, organization,
organization + package A) and two `getMetaItem` requests per case: 840
dumps. Base vs head: 748 byte-identical, 92 differ.
- All 92 are in the two unscoped lists (no package: 45, organization:
47).
- Every one is a package slot moving to the row `getMetaItem` naming
that package serves:
- A: package-less → A's row, 42;
- B: package-less → B's row, 12;
- org package-less → org A, 26;
- env package-less → org A, 6;
- env A → org package-less, 6.
- Outside package slots: 0 differences. Slot count changes: 0.
`getMetaItem` dumps: 0 differences.
- **Lock family.** Same arrangements with every lock level on two of the
rows: 4032 dumps of each list item's `_lock` family and each
`getMetaItem` envelope. 4014 identical, 18 differ. All 18 are the
organization list over [org package-less, env package-less, env A].
There the base list item stated env A's `_lock` (`no-overlay`,
`no-delete` or `full`, 6 each) while the item's envelope said `none`,
because the org's package-less row is the scope. At this head the list
item says `none`. The envelopes are identical at base and head.
- Across all 992 slot comparisons at this head, the list item's `_lock`
equals `getMetaItem`'s envelope lock (base: 18 disagree).
- The lock is still selected by `resolveOverlayLockLayer`. What moved is
the body it is laid over when no overlay row binds.
## H4: organization scope
The organization's rows come before the env-wide rows, then the
package's own row. The org list and `getMetaItem` with an organization
agree in every order: 0 / 120 above, and pin 3.
## Pins (`protocol.list-slot-prefer-local.test.ts`, 85 tests)
1. Generated: every subset of the five rows, every row order, with and
without an organization, on `dashboard` (per-organization overridable,
no list expansion of its own). For package A and package B, the list's
slot serves the row an oracle written from the rule names, and
`getMetaItem` naming the package serves the same row. A package with no
row in scope has no slot.
2. A's row beside the package-less row, both env-wide, both orders, with
and without A's artifact, on `dashboard` and `view`: the slot is A's
row, listed once.
3. Organization scope, every order: the org's package-less row beats an
env-wide row of A; the org's row of A beats the env-wide package-less
row.
4. Control: only the package-less row, under A's artifact. The slot
falls back to it, stamped A, as `getMetaItem` does. Lit control: the
artifact alone is served as itself.
5. The draft preview: A's draft and a package-less draft, both orders.
The previewed slot is A's draft, as `getMetaItem` with `previewDrafts`
serves.
## Reverse verification
- Committed first (`5c7330e64b`). The row-order pick was restored
through `scripts/ablation-replace.mjs` (wrap mode, trap armed): the
latest-wins loop back in place of the served-row call. Anchor 1 → 0,
replacement 0 → 1, blob `c3958342651e` → `5261196669fb`.
- The tests resolve `./protocol.js` from `src`, so no `dist` was
involved.
- Predicted, then measured on the pin file: **36 failed / 49 passed** of
85.
- Pin 2: red when A's row comes back first, green when the package-less
row does, on both types and both artifact states (4 red / 4 green).
- Pin 3, org package-less over env A: 3 red / 3 green. Pin 3, org A over
env package-less: green in both orders (the org's rows are read after
the env rows, so "latest" happened to coincide).
- Pin 4: green. Pin 5: 1 red / 1 green. Pin 1: 28 red / 37 green.
- Restored with `git checkout HEAD -- ABS_PATH` (the tool, and the
trap): blob after = blob at HEAD = `c3958342651e`; `git diff HEAD`
empty.
## Tests
At `978945aecd` (after merging `origin/main` at `75ddcd1b41`, which
touches `cloud-connection`, `metadata-core`'s protocol handshake and
`runtime`; install refreshed and the workspace rebuilt):
- `pnpm --filter @objectstack/metadata-protocol exec vitest run
--maxWorkers=2`: 214 files passed, 3 skipped; 20 037 tests passed, 19
skipped.
- `pnpm --filter @objectstack/metadata-protocol typecheck`: exit 0. `tsc
--listFiles` includes the new test file.
- objectql, against the rebuilt `metadata-protocol` dist: the 20 test
files that call `getMetaItems`, 343 tests passed.
## Gates
All at `978945aecd`, after the final commit.
- `node scripts/pm/dispatch-gates.mjs --commands` (no paths) derived
**72** commands. All 72 ran, plus the **54**-row artifact-roster block
(53 beyond the derived set; `check:engine-double-contract` is in both)
and the four symbol-anchor sweeps. **129 commands, 126 exit 0.**
- `dispatch-gates --ran`: "72 derived famil(ies) accounted for — 72 run,
0 NOT-MEASURED (a DERIVED zero — all 72 recorded an exit code and none
of them is 3)".
- The three non-zero results are roster gates that read a pull request,
which a local run has none of (exit 2, not wired):
- `check-partof-closing-keyword`: re-run with this body as `PR_BODY`;
- `check-closing-target-claim` and `check-single-claim-paths`: **NOT
MEASURED** locally (each needs a PR number and a token); their guard
workflows run on this PR.
- Named readings:
- `check:engine-double-contract`: OK, after recording the new pinned row
through its own `--write` (`740c063f1f`).
- `check:durability-log-level`, `check:nul-bytes`,
`check:dual-build-cjs-loads`, `check:lean-entry-closure`,
`check:adr-0087-registration`: green. The last three ran after the full
build; the first pass at `5c7330e64b` read PREREQUISITE NOT MET (exit 3)
on the two build readers, and that pass is not counted.
- The four symbol-anchor sweeps (`check:adr-symbol-anchors`,
`check:scripts-symbol-anchors`, `check:spec-docblock-symbol-anchors`,
`check:adr-anchors`): green.
- **Lint, a proven narrowing.**
- Command: `eslint --no-inline-config --format json` over the 2 touched
`.ts` files, at `978945aecd`: 2 files, 0 errors, 0 warnings.
- Population, read from eslint's own `calculateConfigForFile` /
`isPathIgnored`: both are linted (neither ignored).
- Invariance: no type-aware linting (`parserOptions.project` and
`projectService` read null for both), so this diff cannot move any
untouched file's verdict.
- The repo-wide `pnpm lint` is CI's.
- **NOT MEASURED here, CI's:** Test Core shards beyond the two packages
above; Temporal Conformance, Dogfood, Build Core; the workspace
type-check lane.
## Acceptance notes
- **A list scoped to one package never falls back to the package-less
row.** `getMetaItems({ type, packageId: A })` (`GET
/meta/:type?package=A`) reads only A's rows (`readActiveOverlayRows`),
so a package-less row never reaches the merge.
- Under A's artifact with only the package-less row stored, that list
serves A's artifact while `getMetaItem` naming A serves the package-less
row.
- With an org: the org's package-less row against an env row of A, the
scoped org list serves env A while `getMetaItem` serves the org's
package-less row.
- These are the 25 census rows left. `mergePackageAwareOverlay`'s
docblock scopes its promise to the unscoped list, and ADR-0048 states no
package-scoped list rule, so whether a package-filtered list should show
package-less stand-ins is a semantics question. Unchanged here.
- **`view` folds two packages' same-name slots into one.** The list's
view-container expansion (#13407) upserts every item by bare name, after
the merge. With env rows of A and B for one view name, the unscoped list
holds only B's. That is why pin 1 runs on `dashboard`. Measured on the
engine double; not changed here.
- **A served body can state a lock its envelope does not report.**
Engine double, at `18c2ddc1ec` and at this head alike. The organization
holds only package B's row; an env-wide row of A declares `full`.
`getMetaItem` naming A in that organization answers `lock: none,
editable: true`, and the served body (env A's row) carries `_lock:
full`. The content scope and the lock scope (#21761) differ there, and
`withOverlayLockFamily` leaves the body unchanged when no overlay row
binds. Reported to the seat; not changed here.
- **Spelling.** The list's other-spelling fallback is per type and scope
(`readActiveOverlayRows`), the by-name read's per item. The candidate
order is shared; the list holds one spelling per scope, so the spelling
step never splits a list slot. Unchanged.
---
_Generated by [Claude
Code](https://claude.ai/code/session_017ErfyP2Rx7XWHJA27QjyUi)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 9059082 commit 3237b4a
4 files changed
Lines changed: 494 additions & 55 deletions
File tree
- .changeset
- packages/metadata-protocol/src
- scripts
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
Lines changed: 319 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
0 commit comments