fix(rest): the /references door forwards the caller's organization, so the "Used by" panel stops clearing a delete an org-scoped item depends on - #15689
Merged
Conversation
… raw `GET /api/v1/meta/:type/:name/references` backs the admin "Used by" panel, whose empty case renders "Nothing in the metadata graph points at this item. Safe to delete." to an operator about to delete something. The door named no organization, so `findReferencesToMeta` swept the environment partition only and an org-scoped `view` pointing at the item was invisible — a false clearance on a destructive action (the ADR-0110 D3 harm this route's own 501 refusal exists to prevent). The door now resolves the memoised exec ctx and passes `ctx?.tenantId` RAW. Not pre-gated on `canonicalMetaUrlType(req.params.type)` the way the sibling `/meta` doors are: that type is the reference TARGET, while the organization is spent on the SOURCES, so gating on it would suppress the organization for exactly the `object` / `flow` / `app` deletes this is about. Raw is safe because `getMetaItems` applies `organizationIdForMetaRead` to its OWN request type since #14683 — the per-source-type decision is already the callee's, and `request.organizationId` has exactly one use inside `findReferencesToMeta`. Pins in `rest-server-meta-read-org-scope.test.ts` drive real routes over a real protocol: the org-scoped `view` is now found (red on the base commit), and the narrowness control proves a non-overridable SOURCE is still read env-wide with no phantom row resurrected. The `resolveExecCtx` census ledger moves 76 → 77 sites / 97 → 98 mentions, 23 → 24 locally caught. No new parameter, response field or contract surface: ADR-0131 D6/D7 retires this partition in v18, so nothing is built on it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…s door shifted `check:check-system-context-census --fix` output: the four `rest-server.ts` elevation-read anchors on the system-context page moved by the same +12 lines the door's comment added. Pure line rot, no row content changed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…l pin The pin reads the refusal code through both of this route's envelopes because the two disagree; that disagreement is now a filed card rather than an unattributed observation in a comment. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
📓 Docs Drift Check
What this run could not see
Coarse fallback — 13 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
hotlong
deleted the
claude/issue-13753-references-door-org-forwarding
branch
September 5, 2026 04:02
This was referenced Sep 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #13753
GET /api/v1/meta/:type/:name/referencesbacks the admin "Used by" panel, whose empty case renders "Nothing in the metadata graph points at this item. Safe to delete." to an operator about to delete something. The door supplied no organization, so the reference sweep read the environment partition only: an org-scopedviewpointing straight at the object being deleted was invisible and the panel issued a false clearance — the ADR-0110 D3 harm this route's own 501 refusal (#9326) was added to prevent, delivered by the door after the protocol had refused to deliver it.The door now resolves the memoised exec ctx and passes the caller's organization RAW.
Why raw is correct and sufficient — verified on this branch, not inherited
The card's⚠️ note prescribed the sibling doors' pre-gate,
organizationIdForMetaRead(canonicalMetaUrlType(req.params.type), ctx?.tenantId). That is not what landed, and the reason is measured here rather than recalled:findReferencesToMetaspends the organization on the SOURCES. It resolvesREFERENCE_SITES.byTarget.get(target), groups the sites byfromType, and reads each throughthis.getMetaItems({ type: matcher.fromType, ...organizationId })—packages/metadata-protocol/src/protocol.ts:21343, the spread at:21490.req.params.typeis the TARGET, so pre-gating on it would suppress the organization for exactly theobject/flow/appdeletes this card is about.getMetaItems(protocol.ts:6876) computesconst orgId = organizationIdForMetaRead(request.type, request.organizationId);on its OWN request type, after the canonical fold. That landed with thegetMetaItemsread gate whose stated purpose is multi-type sweeps scoped per type, and its in-code note namesfindReferencesToMetaas one of the three sweeps it repairs.organizationIdForMetaRead(type, org)(packages/metadata-core/src/meta-write-org-scope.ts) returnsorgonly whendeclaresOrgOverride(type), elseundefined.request.organizationIdhas exactly one use insidefindReferencesToMeta— thatgetMetaItemsspread. Counted over the function body (lines 21343 to the next method declaration at 21550): two occurrences of the identifier, one being the request-type declaration itself. No second use, so passing it raw carries no other consequence.⇒ An overridable source (
view,dashboard,report,translation,email_template) honours the organization; every other source drops it and stays environment-wide, so no pre-#6190 phantom row is resurrected into a destructive-action clearance.ADR-0131 D6/D7 retires the per-organization metadata partition in v18 (#15206, C5), so this is a repair inside a mechanism being removed — an existing value handed to an existing parameter, no new contract surface, and nothing is built on it.
Pins — red-first, and the red is the card's own defect
packages/rest/src/rest-server-meta-read-org-scope.test.ts, driving the real REST routes against a realObjectStackProtocolImplementationover a stub engine, on one boot:objecttarget with an org-scopedviewreferencing it (the view'sobjectbinding is a derived reference site). The write's partition is proved first (1 row under the org, 0 environment-wide), then the sweep must return the view.objectSOURCE is read environment-wide: a planted pre-org 作用域的 flow overlay 只在「本进程内发布后」绑定触发器,重启后静默失绑——冷启动两条读路径都把 organization_id 非空的行滤掉了 #6190 phantom org row is NOT returned, while an environment-wideobjectsource that references the target IS — so the exclusion is scope, not a broken walk.referenceskey with the unchanged row shape, and the [finding] Thefieldmetadata type can never match as a reference TARGET — items are keyed<object>.<field>while every reference site holds a bare field name #9327 unanswerable-target refusal keepsstatus: 501/code: NOT_IMPLEMENTED.Ablation (fix committed first, mutation and restoration both proved on disk by blob hash): restoring only
rest-server.tsto the base commitfa125f3bf— worktree blob3c66d49db= the base blob,referencesCtxoccurrences 2 → 0 — turns 3 of the 37 cases in that file red, and they are the card's: the org-scoped view is absent (expected [] to include 'task_list'), the narrowness pair's org half, and the row-shape pin that reads the same row. Restoration proved bygit diff HEADempty and worktree blob back to2ad2be625= the HEAD blob; the file is the package's own source, read directly by vitest, so nodistsits between the mutation and the measurement.Ledger the change moves
packages/rest/src/execctx-consumer-census.test.ts: 76 → 77resolveExecCtxinvocation sites, 97 → 98 mentions, 23 → 24 locally caught (the new site uses the continuation-line.catch(rethrowAuthzStoreUnavailable)spelling the sibling read doors use — this door does not sit behind the shared anonymous floor, so it decides an authz-store outage for itself rather than laundering it into an org-unscoped 200). Both numbers moved by +1 because the door's new comment states the memoised resolution in prose without naming the symbol; the block records that so the +1/+2 shape of the older entries is not read as a lost site.content/docs/permissions/system-context.mdx: fourrest-server.tselevation-read anchors moved by the same +12 lines the comment added — pure line rot, rewritten bynode scripts/check-system-context-census.mjs --fix, no row content changed.Verification
Run at
f90984fa1, the branch head:node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstackderived its own change set (5 paths) and 83 runnable families; all 83 run green. The 7 families whose argv takes a value from the workflow ($RUNNER_TEMP,${{ matrix.shard }},$PROVENANCE) are NOT MEASURED — there is no local invocation of them, which is the tool's own verdict, not a skip.pnpm --filter @objectstack/rest test— 180 files / 3082 tests green.pnpm --filter @objectstack/rest typecheck— both legs green;tsc -p tsconfig.test.json --listFilesconfirms all three edited files are in the checked program, so the test layer is measured and not merely advertised.pnpm lint— the whole repo,eslint . --no-inline-config, green. No narrowing claimed.scripts/pm/os-verify-lock.sh, which reportsUNLOCKED (declared)on this host —flockis util-linux and this is macOS, so the shared verify lock was never taken and nothing was serialized. Declared, not silent.Not addressed here
/meta/diagnosticsuntyped sweep stays the recorded environment-wide gap the partial close-out left, pinned as such. Its stated justification leans on the premise thegetMetaItemsgate retired, so it is worth a separate look; rest/meta: two more read doors (/meta/diagnostics,/meta/:type/:name/references) never forward the caller's organization — the "Used by" graph tells an operator an org-referenced item is safe to delete #13753's body is where it is recorded today, so it may want its own card once this closes.Generated by Claude Code