Repository navigation
Commit 5dbcee8
Fixes #21510
Clause-②: no
## What this changes
Triage's ruling on the card (comment 5964342087): **the stored row wins
on both doors.** ADR-0005 keys an overlay by its own name, so a row
stored under exactly a name is the sanctioned override for it. An
expansion is derived from its container, so it fills only names that
have no row of their own. The by-name read has applied that rule since
PR #21508. This PR makes the object door's list read apply it too,
through the same predicate.
- **The defect.** `readFlattenedMetaItems` (the list read behind `GET
/api/v1/meta/view?object=OBJECT`) upserted every name a stored view
container expands into its answer by bare name (`byName.set(vi.name,
vi)`). That ran after the package-aware merge had seated the stored
rows, so it replaced a stored row of the same name. The by-name read
(`getMetaItem`) answered that row. The two doors disagreed, against
#21334's ruling ("the object door and the by-name read answer the same
row").
- **One predicate, read by both doors.** The by-name read's test was
inline in `resolveRowlessExpandedView`: a `records.some(...)` that
compared each row's `name` with `request.name`, over the rows
`readActiveOverlayRows` selected for the caller. It is factored out
unchanged as `namesWithOwnStoredRow(records)`, which returns the names
that have a stored row of their own in that selection.
`resolveRowlessExpandedView` now asks it in place of the inline test,
with the same answer for every input: a non-string row name matched no
request name before and is left out of the set now. The list read asks
it over its own `records`, the same selection, and skips an expansion
whose name is in the set. There is no second test.
- **What still holds.** A name the container expands that has no row of
its own is still listed, and on both doors it still replaces a packaged
view of the same name (#21442's tenant-overlay case). The predicate
reads the caller's own rows, so a row stored for one organization wins
for that organization only, and every other caller still gets the
expansion on both doors. The by-name read, its layers, history and diff
answer as before. Nothing is persisted or registered, and no response
shape gains or loses a key.
- **Region.** The edits are the list read's expansion pass, the new
private method, and the one-line move in `resolveRowlessExpandedView`
(plus its docblock). `hydrateExpandedViewItems`, the save door and the
data door's existence gate are not touched.
## Measured, in-process at the protocol (the #21334 showcase harness)
The dev's setup, as the card describes it: a stored overlay of the
showcase's own `showcase_task` container, with a `list` (label
`FromContainer`) and a `listViews.in_progress` member (label
`FromContainer In Progress`), plus a stored ViewItem row named
`showcase_task.default` (label `ByNameRow`), written through the save
door.
| kernel | scope of both rows | write order | before: object door /
by-name, `showcase_task.default` | after: both doors | control
`showcase_task.in_progress`, before and after |
|---|---|---|---|---|---|
| `env_local` | environment-wide | container, then row | `FromContainer`
/ `ByNameRow` | `ByNameRow` | the expansion, both doors |
| `env_local` | environment-wide | row, then container | `FromContainer`
/ `ByNameRow` | `ByNameRow` | the expansion, both doors |
| `env_local` | organization-scoped | either order | `FromContainer` /
`ByNameRow` | `ByNameRow` | the expansion, both doors |
| unscoped | environment-wide | either order | `FromContainer` /
`ByNameRow` | `ByNameRow` | the expansion, both doors |
| unscoped | organization-scoped | either order | `FromContainer` /
`ByNameRow` | `ByNameRow` | the expansion, both doors |
"Before" is `origin/main` at `24dc7c1134`, read with a throwaway test
that was deleted afterwards. "After" is this branch.
**The public input (PM mechanism assumption 4).** In every cell above,
the save door accepts `saveMetaItem` with type `view`, the name
`showcase_task.default` and a body whose `name` is
`showcase_task.default`, and it stores the row under that name. Since
#21470 the save door judges the body `name` against the save name, and
these are equal. The pins assert that the row was stored.
## Tests
In
`packages/metadata-protocol/src/view-container-runtime-expansion.test.ts`,
nested in the #21334 block to reuse its faithful-registry harness, there
are 10 new cases (the file goes from 119 to 129):
- **8 cases: both kernels, environment-wide and organization-scoped,
both write orders.** In each, the stored row answers
`showcase_task.default` on both doors, and the by-name item equals the
listed one (`_diagnostics` excluded). The row's name keeps its own
history (every event's `ref.name` is the row's) and its own diff (`name`
is the row's), never the container's. The control,
`showcase_task.in_progress`, answers the expansion on both doors. Every
name the object door lists answers the same item by name.
- **2 cases, one per kernel: the predicate reads the caller's own
rows.** The container is environment-wide and the row is stored for
`org_acme`. `org_acme` gets the row on both doors. A caller with no
organization and `org_globex` get the container's expansion on both
doors.
Results:
- At the final head `6a41000f1e`, the full package suite (`vitest run`)
gives **206 files passed / 3 skipped, 3173 tests passed / 19 skipped**.
- `pnpm --filter @objectstack/metadata-protocol typecheck` is clean, and
`tsc --listFiles` includes the edited test file.
- Downstream, a narrowed and declared sample against the rebuilt `dist/`
(it carries `namesWithOwnStoredRow`): the test files that read the view
object door through the real protocol. `@objectstack/objectql` gives 5
files / 71 tests and `@objectstack/rest` gives 1 file / 24 tests, all
green. The rest of those packages, and the dogfood suites, are CI's.
## Reverse verification
Each mutation was made from committed state (`6a41000f1e`) through
`scripts/ablation-replace.mjs`, inside a script with an EXIT/INT/TERM
restore trap. After each leg, the restore was proven: blob
`f1622d5bdde2` equals HEAD, and `git diff HEAD` is empty. The subject
resolves through relative source imports (`./index.js`), so no `dist/`
leg applies. The predicted direction was red, and every leg went red.
- **A1, the list read's call off** (`… && false) continue;`): **10
failed / 119 passed**. All 10 new cases fail with `expected
'FromContainer' to be 'ByNameRow'`, the card's defect.
- **A2, the predicate's body off** (no name is ever added): **10 failed
/ 119 passed**, the same 10. The list read fails first in each case.
- **A3, only the by-name family's call off** (in
`resolveRowlessExpandedView`): **8 failed / 121 passed**. Both doors
still answer the row, but history now delegates to the container: `every
event names the row: expected false to be true`. So the by-name family
reads the same predicate and is pinned by it.
## Gates
- `dispatch-gates --commands --repo objectstack-ai/objectstack` at the
final head `d12a8f6256` derived **64** families, the same set as at
`6a41000f1e`. The PM's lead had 56; the changeset adds 8:
`check-adr-0087-registration` ×2, `check-empty-changeset` ×2,
`release-rehearsal-clone --self-test`, `release-pending-publish
--self-test`, `check:objectui-changeset` and
`check:pm-changeset-deadline-census`.
- After the final commit, **all 64 exited 0**. `pnpm
check:dual-build-cjs-loads` exited 3 (PREREQUISITE NOT MET, 44 package
entry points with no `dist/`) in the first run at `6a41000f1e`. By the
run at `d12a8f6256`, those `dist/` directories were present in this
worktree (created at 06:27Z, while the first run's
`check:type-check-debt` re-measure was running), and it measured 106
require entry points across 66 packages, which load.
- `--ran` reconciliation at `d12a8f6256`: 64 derived, 64 run, 0
NOT-MEASURED, 0 UNRUN.
- `d12a8f6256` changes only the changeset, so the test, typecheck and
ablation readings above (taken at `6a41000f1e`) read the same
`protocol.ts` and test file bytes.
- Lint, narrowed: `eslint --no-inline-config --format json` over the 2
changed TypeScript files gives 2 files linted, 0 errors and 0 warnings.
The config does no type-aware linting (no `parserOptions.project`; see
the note at `eslint.config.mjs` line 328), so this diff cannot move a
verdict on an untouched file. A repo-wide `pnpm lint` is CI's.
## Acceptance notes
- **A container saved under one of its own expanded names** (measured,
reported to the seat as a finding, not fixed here). The probe: the save
door accepts a container body `{ name: 'showcase_task.default', object:
'showcase_task', list: {...} }` saved under `showcase_task.default`, and
its bare `list` expands to that same name.
- **Before** (the list read's call off, which is the `origin/main`
list): the object door listed the self-expansion, and the by-name read
answered the raw container. The doors disagreed.
- **After:** the container row is the name's own row, so its expansion
does not fill that name. The canonical-shape filter (ADR-0017) never
enumerates a container, so the object door lists nothing under
`showcase_task.default`, while the by-name read still answers the raw
container. That is how every container's own name already behaves. The
doors still disagree, now in a different way.
- Both kernels were measured. The ruling's predicate gives this result.
Neither door can answer a ViewItem for that name while the stored row is
a container. The candidate fix is a save-door refusal, which is outside
this card's surface. The changeset states the case.
- **Declaration bytes.** `dist/index.d.ts` gains one private member line
(`private namesWithOwnStoredRow;`). No public member or exported type
changes.
- **Cost.** The list read builds one `Set` of row names per call, over
rows it already holds. There is no extra read.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01DDZNkDVwPQnevTFcYE47H3)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent f83d066 commit 5dbcee8
3 files changed
Lines changed: 172 additions & 2 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
8353 | 8353 | | |
8354 | 8354 | | |
8355 | 8355 | | |
| 8356 | + | |
| 8357 | + | |
| 8358 | + | |
| 8359 | + | |
| 8360 | + | |
| 8361 | + | |
| 8362 | + | |
| 8363 | + | |
| 8364 | + | |
8356 | 8365 | | |
8357 | 8366 | | |
8358 | 8367 | | |
8359 | 8368 | | |
8360 | 8369 | | |
| 8370 | + | |
8361 | 8371 | | |
| 8372 | + | |
8362 | 8373 | | |
8363 | 8374 | | |
8364 | 8375 | | |
| |||
8819 | 8830 | | |
8820 | 8831 | | |
8821 | 8832 | | |
| 8833 | + | |
| 8834 | + | |
| 8835 | + | |
| 8836 | + | |
| 8837 | + | |
| 8838 | + | |
| 8839 | + | |
| 8840 | + | |
| 8841 | + | |
| 8842 | + | |
| 8843 | + | |
| 8844 | + | |
| 8845 | + | |
| 8846 | + | |
| 8847 | + | |
| 8848 | + | |
| 8849 | + | |
| 8850 | + | |
| 8851 | + | |
| 8852 | + | |
| 8853 | + | |
| 8854 | + | |
| 8855 | + | |
| 8856 | + | |
8822 | 8857 | | |
8823 | 8858 | | |
8824 | 8859 | | |
| |||
8846 | 8881 | | |
8847 | 8882 | | |
8848 | 8883 | | |
8849 | | - | |
| 8884 | + | |
| 8885 | + | |
8850 | 8886 | | |
8851 | 8887 | | |
8852 | 8888 | | |
| |||
8868 | 8904 | | |
8869 | 8905 | | |
8870 | 8906 | | |
8871 | | - | |
| 8907 | + | |
8872 | 8908 | | |
8873 | 8909 | | |
8874 | 8910 | | |
| |||
Lines changed: 122 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
940 | 940 | | |
941 | 941 | | |
942 | 942 | | |
| 943 | + | |
| 944 | + | |
| 945 | + | |
| 946 | + | |
| 947 | + | |
| 948 | + | |
| 949 | + | |
| 950 | + | |
| 951 | + | |
| 952 | + | |
| 953 | + | |
| 954 | + | |
| 955 | + | |
| 956 | + | |
| 957 | + | |
| 958 | + | |
| 959 | + | |
| 960 | + | |
| 961 | + | |
| 962 | + | |
| 963 | + | |
| 964 | + | |
| 965 | + | |
| 966 | + | |
| 967 | + | |
| 968 | + | |
| 969 | + | |
| 970 | + | |
| 971 | + | |
| 972 | + | |
| 973 | + | |
| 974 | + | |
| 975 | + | |
| 976 | + | |
| 977 | + | |
| 978 | + | |
| 979 | + | |
| 980 | + | |
| 981 | + | |
| 982 | + | |
| 983 | + | |
| 984 | + | |
| 985 | + | |
| 986 | + | |
| 987 | + | |
| 988 | + | |
| 989 | + | |
| 990 | + | |
| 991 | + | |
| 992 | + | |
| 993 | + | |
| 994 | + | |
| 995 | + | |
| 996 | + | |
| 997 | + | |
| 998 | + | |
| 999 | + | |
| 1000 | + | |
| 1001 | + | |
| 1002 | + | |
| 1003 | + | |
| 1004 | + | |
| 1005 | + | |
| 1006 | + | |
| 1007 | + | |
| 1008 | + | |
| 1009 | + | |
| 1010 | + | |
| 1011 | + | |
| 1012 | + | |
| 1013 | + | |
| 1014 | + | |
| 1015 | + | |
| 1016 | + | |
| 1017 | + | |
| 1018 | + | |
| 1019 | + | |
| 1020 | + | |
| 1021 | + | |
| 1022 | + | |
| 1023 | + | |
| 1024 | + | |
| 1025 | + | |
| 1026 | + | |
| 1027 | + | |
| 1028 | + | |
| 1029 | + | |
| 1030 | + | |
| 1031 | + | |
| 1032 | + | |
| 1033 | + | |
| 1034 | + | |
| 1035 | + | |
| 1036 | + | |
| 1037 | + | |
| 1038 | + | |
| 1039 | + | |
| 1040 | + | |
| 1041 | + | |
| 1042 | + | |
| 1043 | + | |
| 1044 | + | |
| 1045 | + | |
| 1046 | + | |
| 1047 | + | |
| 1048 | + | |
| 1049 | + | |
| 1050 | + | |
| 1051 | + | |
| 1052 | + | |
| 1053 | + | |
| 1054 | + | |
| 1055 | + | |
| 1056 | + | |
| 1057 | + | |
| 1058 | + | |
| 1059 | + | |
| 1060 | + | |
| 1061 | + | |
| 1062 | + | |
| 1063 | + | |
| 1064 | + | |
943 | 1065 | | |
944 | 1066 | | |
945 | 1067 | | |
| |||
0 commit comments