Skip to content

Where does the allowOrgOverride read gate belong for metadata sweeps that read MORE THAN ONE type per request? getMetaItems applies none of its own #14683

Description

@os-trump

Split out of #13753 by the domain:cli execution seat, because the answer lands in packages/metadata-protocol — another lane's package — and #13753 fences protocol.ts off. ⛔ Ungraded and unrouted on purpose: no pm:* state and no domain:*, so triage grades it. It lands in packages/metadata-protocol (and would touch getMetaItems), which the lane table puts in domain:engine.

Filed with the measurement already done, so grading does not need a dispatch first.

The measured fact this rests on

getMetaItems applies no registry gate of its own. Whatever organization it is handed is used for whatever type it is handed. The only organizationIdForMetaRead call inside @objectstack/metadata-protocol is the page read in protocol.ts; nothing on this path.

The scope of a metadata sweep is decided per type, by the caller. A request that carries one organizationId can therefore only be correct when it sweeps one type.

Two live doors sweep more than one:

door what it sweeps why one request-level organization cannot be right
GET /meta/diagnostics with no ?type= the whole registry one organization applied across types whose allowOrgOverride flags disagree
GET /meta/:type/:name/references REFERENCE_SITES.byTarget → each matcher.fromType req.params.type is the target; the organization is spent on the sources, so the target's own flag says nothing about the types actually read

The ?type= arm of /meta/diagnostics — the one case where one organization is the whole truth — is repaired in PR #14677. These two are not repairable from the call site, which is why they are here rather than there.

The harm, in both directions

Gating on the wrong type is wrong both ways round, and the two failures look nothing alike:

⚠️ One correction to #13753's premise, carried here because it changes the harm class. That card assumed naming the tenant unconditionally would hide an allowOrgOverride: false type's rows. That is true of /historySysMetadataRepository.history() filters organization_id by strict equality — and it is not true on this path, where the union can only add rows. The registry-gated predicate is still the right instrument here; the reason is resurrection, not concealment.

Both consumers are in-tree and live: findReferencesToMeta backs the admin "Used by" panel, and the diagnostics sweep backs the Studio governance directory. An org-scoped view referencing a packaged object is the ordinary shape of an org overlay, so the false clearance is reachable on any deployment that uses overlays.

The options, as the reporting dev framed them

A — apply organizationIdForMetaRead(request.type, request.organizationId) INSIDE getMetaItems, and let every caller pass the session's raw active organization. One predicate governs read scope for all callers; the six gating call sites in rest-server.ts become redundant but idempotent.

B — apply the predicate per swept type inside each sweep: per t in getMetaDiagnostics, per matcher.fromType in findReferencesToMeta. getMetaItems untouched.

C — fan out at the REST call site: one getMetaDiagnostics call per org-overridable type plus one env-wide sweep, re-aggregating total / stats / scannedTypes / scannedItems in packages/rest. No protocol change.

D — leave both env-wide, document the limit on the doors, and accept the behaviour.

The dev recommended A, on these grounds, recorded here so the grading seat has them: the gate belongs where the type being read is known, and getMetaItems is the single seam every one of these reads already passes through, so read scope and write scope sit on one predicate and cannot drift for any caller. B fixes these two doors and leaves the rule per-caller — the shape that produced #9454, #9727, #13406 and #13753 in sequence, one door at a time. C is the only option that adds a mechanism, and it makes packages/rest a second owner of the sweep's arithmetic. D leaves a false clearance rendered to an operator at an irreversible action.

The cost of A, stated by the same dev rather than glossed: it touches a hot read path, it needs a proof of idempotence for the six call sites that already gate, and it needs pins for both sweeps. It is not a one-liner.

For the grading seat

The seat filing this has no opinion to record on A/B/C/D — it does not own packages/metadata-protocol. Two things worth weighing while grading:

Refs

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions