Skip to content

Commit d5cbb44

Browse files
os-muskclaude
andauthored
fix(metadata-protocol): gate getMetaItem's overlay read on the metadata registry (#14908)
* fix(metadata-protocol): gate `getMetaItem`'s overlay read on the metadata registry The singular `/meta` read verb applied no organization gate of its own: whatever `organizationId` arrived was spent on whatever `type` arrived. Its two overlay reads combine with `??` — precedence, not the plural verb's union — so an ungated organization could SUBSTITUTE rather than merely add: on a type the registry declares `allowOrgOverride: false`, a pre-#6190 phantom org-scoped row was served instead of the live env-wide document. Resolve the scope through `organizationIdForMetaRead` once, after the canonical type fold, for both the active-overlay read and the ADR-0033 `previewDrafts` read. ADR-0005's overlay-wins precedence is deliberately unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68 * test(objectql): move the two singular-verb overlay fixtures onto an overridable type `getMetaItem` now resolves its own read scope through `organizationIdForMetaRead`, so on `app` — `allowOrgOverride: false` since #6483 — the org partition is never queried. The two `per-organization overlay isolation` fixtures that exercised the singular verb on `app` were therefore measuring the phantom read this branch removes: one went red, and its sibling stayed green while asserting a fall-through that could no longer fail. Both move to `view`, the whitelisted specimen, matching the re-spellings #6190 and #14683 already made in the same block, and each gains the call assertion that makes its claim non-vacuous. Prose corrections in the same edit, from the contract review: - the changeset no longer says the plural gate shipped "in the previous release" — both changesets are unconsumed, so both land in the same one; - the phantom row is never deleted, so a restart does not clear the substitution; what a restart drops is the row's registry presence; - the "what moves" enumeration names four raw-org callers across two files, with the method that establishes the population; - the write-side read/write-scope identity is qualified under the `OS_METADATA_WRITABLE` operator hatch, and the ADR-0005 / ADR-0029 citations are narrowed to what those documents actually decide. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68 --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent f8e8f03 commit d5cbb44

4 files changed

Lines changed: 638 additions & 14 deletions

File tree

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
---
2+
"@objectstack/metadata-protocol": patch
3+
---
4+
5+
fix(metadata-protocol): `getMetaItem` gates its own overlay read, so a phantom org-scoped row can no longer replace the live env-wide document
6+
7+
The singular `/meta` read verb applied no organization gate of its own — whatever
8+
`organizationId` a caller passed was spent on whatever type it passed. On the
9+
plural verb (`getMetaItems`, gated by #14683 / PR #14767, which lands in the
10+
SAME release as this change) the two overlay reads are UNIONed, so an ungated
11+
organization could only add rows. On this one they
12+
combine with `??` — precedence — so it could **substitute**: for a type the
13+
registry declares `allowOrgOverride: false`, a pre-#6190 phantom org-scoped row
14+
was served *instead of* the live env-wide document, to a caller that asked for
15+
the live one. Those rows are the ones boot hydration deliberately walks past and
16+
`reportUnhydratableOrgScopedRows` warns about — and nothing deletes them, so a
17+
restart did NOT clear the substitution: the same phantom was served again. What
18+
a restart drops is the row's *registry* presence, and this door does not consult
19+
the registry while a `sys_metadata` row answers.
20+
21+
`getMetaItem` now resolves its read scope through `organizationIdForMetaRead`
22+
itself — the same registry-derived predicate the REST `/meta` doors and the
23+
plural verb already apply — once, for both the active-overlay read and the
24+
ADR-0033 `previewDrafts` read.
25+
26+
Read-scope resolution is unchanged for callers that already gated (the predicate
27+
is idempotent, and the gate sits after the same canonical type fold the REST
28+
by-name door gates on) and for callers that name no organization at all. What
29+
changes is a caller that hands this verb a raw active organization: for a type
30+
with no per-org read channel it now reads the env-wide row, as the REST doors
31+
already did.
32+
33+
ADR-0005's overlay-wins resolution order is deliberately untouched: an
34+
organization that legitimately has a per-org channel still sees its own overlay
35+
row win outright, whole, with no merge against the env-wide row.

0 commit comments

Comments
 (0)