Repository navigation
security(mcp): a nested-relation filter condition through the MCP stdio data bridge is evaluated without asking the target's exposure (census row 8 of #22661) #22777
Copy link
Copy link
Closed
Labels
area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingSomething isn't workingdomain:enginepriority:p2Medium: important, M3Medium: important, M3security
Description
This card carries census row 8 of #22661 (part of #22661). The door was found by #22737's dev, and #22661's census did not walk it. #22661 keeps its other rows. ⛔ Classes, positions and functions only.
Blocked-by: #22737
Filing class: ① a product defect, class (a).
Reach: measured at the MCP tool surface by #22737's dev (os-dev-report 6106039434, out-of-scope finding):
- The real
createStdioDataBridgeandregisterObjectTools'query_recordswere driven over an in-memory MCP client/server pair. - The caller was an administrator API key resolved through the MCP plugin's own chain, a non-system context.
- The stdio byte transport and the process boundary were not exercised.
Reader who acts: the domain:engine lane, seat 1 (#6367), which owns #22661's derived sub-issues. The fix lands in packages/mcp (domain:cli), so the claim declares the cross-domain path. It waits for #22737, so that the refusal takes the shape the data door gives.
The gap
createStdioDataBridge'squery()(packages/mcp/src/stdio-data-bridge.ts) judges the ADDRESSED object's exposure throughenforceApiExposure, then hands the caller's filter toengine.find.- A nested-relation condition in that filter is lowered by
ObjectQL.lowerRelationConditions, which reads the lookup's TARGET as the caller and does not ask the target's exposure. - So a source row comes back only when the related value matches. The MCP door itself refuses that target when it is addressed directly.
- This is the same defect as security(data): a nested-relation filter condition on a lookup target is evaluated without asking the target's exposure (census row 4 of #22661) #22737, at a door that does not pass through the data door.
Direction (not a ruling)
- The bridge asks the spec's one exposure decision of each nested-relation condition's target, for
list, as security(data): a nested-relation filter condition on a lookup target is evaluated without asking the target's exposure (census row 4 of #22661) #22737 does at the data door. ⛔ No second rule. - A refused target answers with the declared exposure refusal the MCP tools already give for an unexposed addressed object.
- Pins: an unexposed target is refused, and an exposed target is unchanged. Ablation-verified. Join security(data, analytics): a lookup target's exposure declaration is not judged when the data door's
$expand, or the dataset door's dimension-label pass, reads it — detail withheld pending maintainer #22661's enumeration pin if its discriminator can see the bridge; otherwise say why.
Duplicate check
- Population: issues updated since 2026-09-01, paged to the end through REST, plus those updated since 2026-10-10T22:00Z. That is 4,497 issues, PRs excluded and closed included, ADR-0048: Semantic-reference metadata embeds in package docs (flow:// / view:// / dashboard:// / object://) #1795 to trigger-api: implement the declared inbound-hook member through the same verifier, read
http.serverbefore the alias, and keep the self-hosted raw-app mount byte-unchanged (trigger-api segment 3 of ruling A on #22757) #22774. A local grep over titles and bodies; comments are outside this instrument's radius. - Queries:
stdio-data-bridge: 3, all closed (error-code provenance:@objectstack/mcp's stdio data bridge stampsOBJECT_API_DISABLEDthrough a constant outsidecheck:error-code-provenance's declared patterns, and the ledger has no@objectstack/mcprow for it #22664, an error-code provenance card; [security] Stored-metadata-body family: two exits #21120 did not reach. An engine-only door serves an administrator credential material in cleartext, and the data door serves a content hash computed over the withheld credentials #21207; The #8497 MCP write-response tripwire enumerates the stdio bridge only — the runtime's HTTP McpDataBridge is a second implementation no guard walks #8589).createStdioDataBridge: 1, closed.query_records: 5, all closed.- MCP together with a relation condition or filter: 2, both closed.
enforceApiExposure: 1, closed (error-code provenance:@objectstack/mcp's stdio data bridge stampsOBJECT_API_DISABLEDthrough a constant outsidecheck:error-code-provenance's declared patterns, and the ledger has no@objectstack/mcprow for it #22664).
- Controls:
mcpalone 649;lowerRelationConditions1 (security(data): a nested-relation filter condition on a lookup target is evaluated without asking the target's exposure (census row 4 of #22661) #22737, open).
Generated by Claude Code
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingSomething isn't workingdomain:enginepriority:p2Medium: important, M3Medium: important, M3security
Activity
objectstack-fleet commentedon Oct 11, 2026
Claim: PM loop round 3 · 2026-10-11T09:10Z
Session:
session_01JfJfBUC3cQ6hhgm9MQK76TAccount:
os-project-manager(the seat's linked user, asGET /useranswers it; the card's assignee)Branch:
claude/issue-22777-mcp-relation-filter-exposureWorktree:
objectstack-issue-22777Domain:
domain:engineSeat:
domain:engine#1(seat post #6367)File surface (read on
origin/main14f4912390; stop on a breach and explain it in the report):packages/mcp/src/stdio-data-bridge.ts(domain:cli, declared on [PM seat] domain:cli — ⏳ vacant #6024): the bridge's read path asks each nested-relation condition's TARGET the exposure decision forlist, through the gate it already applies to the addressed object (enforceApiExposure). ⛔ No second rule.relationConditionSites(packages/metadata-protocol/src/protocol.ts) moves topackages/core/src/utils/and is exported from@objectstack/core, which both doors already depend on.protocol.tsimports it, with no behaviour change there. ⛔ No second walker. If the measurement shows a better home, say so in the report before moving it.packages/core/src/security/second-object-read-exposure.pin.test.ts: census row 8 joins asdecidedif the pin's discriminator can see the bridge. Otherwise the report says why.Container & model:
M,mode:subagent,model: default(dispatch-gates --tier: no path-derived mandate)Clause-②: yes (narrowing)
@objectstack/coregains one export, an additive public member. It owes one contract-review-tier review before the queue, and a changeset stating FROM → TO.Responsibility:
createStdioDataBridge's read path, which judges the ADDRESSED object's exposure and then hands the caller's filter toengine.find, whose relation lowering reads the target as the caller | the spec's one exposure decision, which the data door now asks of each condition's target forlist(security(data): a nested-relation filter condition on a lookup target is evaluated without asking the target's exposure (census row 4 of #22661) #22737,refuseUnservedRelationTarget) | any MCP stdio caller whose filter names a condition on a lookup's related object that refuses the API; measured at the MCP tool surface by security(data): a nested-relation filter condition on a lookup target is evaluated without asking the target's exposure (census row 4 of #22661) #22737's dev (os-dev-report 6106039434)Thread-read: none
Inputs read: the card body; security(data): a nested-relation filter condition on a lookup target is evaluated without asking the target's exposure (census row 4 of #22661) #22737's os-dev-report 6106039434 (the finding); security(data): a nested-relation filter condition on a lookup target is evaluated without asking the target's exposure (census row 4 of #22661) #22737's ACCEPT 6107195645
Serial constraints cleared:
ec7c7e0637. It was this card'sBlocked-by:, and its refusal is the shape this door gives.packages/mcp/src/stdio-data-bridge.tsor the relation-condition walk.protocol.tsonly around line 16616, in a different region from the walk and its import.userfield written withoutreferencefailsreference_not_foundon import, while the spec's arbiter gives itsys_user#22785 (core'simport-field-meta.ts) and objectql: anafterInserthook withonError: 'abort'(the default) that throws rejects the write, but the row stays stored —HookSchema.onErrorsays abort rolls the transaction back, and a plain write opens none #22782 (objectql's hook dispatch). Both are disjoint files.Generated by Claude Code
objectstack-fleet commentedon Oct 11, 2026
os-dev-report
{
"issue": 22777,
"status": "done",
"branch": "claude/issue-22777-mcp-relation-filter-exposure",
"pr": "#22820",
"session": "session_01JfJfBUC3cQ6hhgm9MQK76T — the dispatching seat's id, inherited by this subagent",
"premise_still_valid": true,
"summary": "createStdioDataBridge's query() and aggregate() now call enforceRelationTargetExposure right after the addressed object's gate: it finds the nested-relation conditions in where and asks each TARGET enforceApiExposure(..., GATED_ACTIONS.query, ..., position), the gate an addressed object already gets, so list and no second rule; an unserved target refuses before the engine is asked in the bridge's existing envelope (404 OBJECT_API_DISABLED / 405 OBJECT_API_METHOD_NOT_ALLOWED with allowedOperations), the message naming target, position where.FIELD and the remedy (enforceApiExposure gained an optional condition argument). relationConditionSites moved unchanged from metadata-protocol's protocol.ts (module-private) to packages/core/src/utils/relation-condition-sites.ts, exported from @objectstack/core; protocol.ts imports it, refuseUnservedRelationTarget untouched; objectql and types untouched. Census pin: the moved walk is a new referenceTargetOf caller, classified not-a-served-read; the discriminator does not see the bridge (it never calls the arbiter; its targets resolve inside the shared walk), so row 8 is NOT a decided row and the discriminator is unchanged; the header's not-seen paragraph names the bridge and where it is held. HYPOTHESES: H1 confirmed on a real stack (scratch probe, not committed: bootStack over the #22661 dogfood fixture, admin and member API keys minted through the key route, principal via resolveAuthzContext + assembleExecutionContext, real createStdioDataBridge + registerObjectTools over an in-memory MCP pair; StdioServerTransport and process boundary not exercised). Before (base 23419ba), both personas, query_records and aggregate_records: a condition on an API-disabled, get-only or create-only target was evaluated (match -> rows, miss -> none), also under $and/$or/$not, while the same targets addressed directly were refused. After: all refused, match and miss alike; controls (list-only, undeclared target, injected owner lookup to a list-served platform object) unchanged; second-level still refused by the engine before any read. Census of bridge paths handing a caller filter to the engine: query (where -> engine.find) and aggregate (where -> engine.aggregate); get/update/remove and the record resource build where {id} themselves; aggregations[i].filter is not reachable from the aggregate_records tool (no filter member in its schema) and the engine refuses a nested-relation condition there, so not walked. H2 confirmed: core is the right home (both doors depend on it, spec carries no logic). H3 confirmed: reuse of enforceApiExposure with GATED_ACTIONS.query (-> list). H4 measured: every stdio call carried isSystem false / principalKind human; assembleExecutionContext emits isSystem: false as a literal and resolvePrincipal throws on no principal, so the reused isSystem bypass is unreachable from this door and every reachable caller gets #22737's verdict; chose reuse (no second rule). H5 measured: query_records/aggregate_records flatten a thrown error to its message (errorResult(messageOf(err))), as for an addressed refusal: the caller sees isError + text naming target and position; code/status/allowedOperations live on the bridge error and are pinned there. files_changed (9): .changeset/22777-core-relation-condition-sites.md (+10), .changeset/22777-mcp-stdio-relation-condition-target-exposure.md (+31), packages/core/src/index.ts (+6), packages/core/src/security/second-object-read-exposure.pin.test.ts (+15/-1), packages/core/src/utils/relation-condition-sites.test.ts (+32, new), packages/core/src/utils/relation-condition-sites.ts (+69, new), packages/mcp/src/stdio-data-bridge.relation-condition-target-exposure.test.ts (+136, new), packages/mcp/src/stdio-data-bridge.ts (+48/-3), packages/metadata-protocol/src/protocol.ts (+1/-42). line_budget: 394 changed lines (+348/-46) vs suggested 300; source +124/-45 net +79 (mcp +48/-3; core walk +69 is a move offset by protocol.ts -42/+1; core index +6) vs suggested 60. Excess = the moved function's docblock and exported type, and an mcp pin parameterised over 24 refusal cells. deviations: (a) base origin/main 23419ba (moved past the dispatch's 14f4912), origin/main c11b758 merged as 2261c25; main has since gained 3 commits (67b669e) touching none of the 9 paths; not re-merged. (b) line budget above. (c) positions: where on query and aggregate only; aggregations[i].filter not walked (reason above). (d) one hop: the engine serves one level and refuses a relation beneath before reading; the data door recurses only for its internal-field refusal. (e) isSystem bypass kept by reuse, unreachable here (H4). (f) changesets: @objectstack/mcp minor BREAKING with FROM -> TO, migration, the Clause-② yes (narrowing) line and ADR-0087 not-required (no-migration-prescription); @objectstack/core minor + @objectstack/metadata-protocol patch in one non-breaking changeset. (g) zero label writes: dispatch named none; changesets exist. (h) check:dual-build-cjs-loads NOT MEASURED (exit 3, PREREQUISITE NOT MET: 19 packages outside this closure have no dist); narrowed probe in its place. (i) PR body line 2 kept as dispatched: Clause-② yes (narrowing) — yes for core's new export, narrowing for the mcp accept set.",
"tests": "All at final head 2261c25 (origin/main c11b758 merged; dogfood/verify/plugin-security/mcp closure rebuilt after the merge, 47 turbo tasks exit 0). mcp vitest 36 files / 417 passed (new pin 27/27; stdio-data-bridge.exposure.test.ts 27/27). core local 94 files / 2364 passed; core repo 5 files / 55 passed (census pin 4/4). metadata-protocol vitest 225 files passed + 3 skipped / 28129 passed, 19 skipped (protocol.relation-condition-target-exposure.test.ts 13/13). dogfood second-object-exposure.dogfood.test.ts 34/34. Each VERDICT command-exit 0. Typecheck: mcp (tsc + check:test-typecheck OK), metadata-protocol (tsc), core (tsc + examples + check:test-typecheck OK): VERDICT command-exit 0. Reverse type check: scratch file in mcp assigning an extra key to RelationConditionSite -> tsc TS2353 on that key (reads rebuilt core .d.ts); removed, tsc exit 0. ABLATION A (committed d1e2291, via ablation-replace.mjs, anchor 1 -> 0, blob cf59e766 -> 822adfc1): the walk's result in enforceRelationTargetExposure replaced by an empty list -> new mcp pin 25 red (24 refusals + tool leg) / 2 green (controls); exposure.test.ts 27/27 green; restore blob == HEAD cf59e766, git diff HEAD empty. ABLATION B (on core dist): walker's $-key descent replaced by a string marker (anchor 1 -> 0, blob 7ab785d5 -> f43161f2, --hold); core rebuilt; ablation-dist-preflight present exit 0 (2 built files) -> core walker test 1 red / 1 green; mcp pin 16 red (exactly the $or/$not cells) / 11 green; metadata-protocol relation pin 8 red ($or filter cases, both contexts) / 5 green; restore blob == HEAD 7ab785d5, git diff HEAD empty; core rebuilt; preflight --absent exit 0; git status --porcelain clean. GATES (union of the dispatch list 59 and dispatch-gates --commands on this diff 67 = 67 commands, exit captured before any pipe): node scripts/check-adr-0087-registration.mjs --base origin/main :: 0; node scripts/check-adr-0087-registration.mjs --self-test :: 0; node scripts/check-changeset-no-major.mjs --base origin/main :: 0; node scripts/check-changeset-no-major.mjs --self-test :: 0; node scripts/check-ci-filter-parity.mjs :: 0; node scripts/check-closing-keyword-parity.mjs :: 0; node scripts/check-closing-keyword-parity.mjs --self-test :: 0; node scripts/check-comment-mask-adoption.mjs :: 0; node scripts/check-comment-mask-adoption.mjs --self-test :: 0; node scripts/check-comment-mask-corpus.mjs :: 0; node scripts/check-dev-prereqs.mjs --self-test :: 0; node scripts/check-dts-emitted.mjs --self-test :: 0; node scripts/check-empty-changeset.mjs --base origin/main :: 0; node scripts/check-empty-changeset.mjs --self-test :: 0; node scripts/check-issue-citations.mjs :: 0; node scripts/check-keyed-text-bounds.mjs :: 0; node scripts/check-keyed-text-bounds.mjs --self-test :: 0; node scripts/check-platform-object-tenancy-census.mjs :: 0; node scripts/check-platform-object-tenancy-census.mjs --self-test :: 0; node scripts/check-plugin-teardown-shape.mjs :: 0; node scripts/check-plugin-teardown-shape.mjs --self-test :: 0; node scripts/check-registry-log-declared.mjs :: 0; node scripts/check-registry-log-declared.mjs --self-test :: 0; node scripts/check-rest-log-spy-declared.mjs :: 0; node scripts/check-rest-log-spy-declared.mjs --self-test :: 0; node scripts/check-system-context-census.mjs :: 0; node scripts/check-system-context-census.mjs --self-test :: 0; node scripts/check-undeclared-dep-imports.mjs :: 0; node scripts/check-undeclared-dep-imports.mjs --self-test :: 0; node scripts/docs-audit/check-affected-docs.mjs :: 0; node scripts/docs-audit/check-drift-comment.mjs :: 0; node scripts/pm/release-rehearsal-clone.mjs --self-test :: 0; node scripts/release-pending-publish.mjs --self-test :: 0; pnpm --filter @objectstack/spec run check:duration-unit-keys :: 0; pnpm check:changeset-gate-self-tests :: 0; pnpm check:cross-package-test-inputs :: 0; pnpm check:dispatcher-error-vocabulary :: 0; pnpm check:doc-authoring :: 0; pnpm check:driver-memory-census :: 0; pnpm check:durability-log-level :: 0; pnpm check:engine-double-contract :: 0; pnpm check:error-status-conformance :: 0; pnpm check:filter-alias-parity :: 0; pnpm check:gitlink-declared :: 0; pnpm check:issue-citations :: 0; pnpm check:kernel-hook-pairs :: 0; pnpm check:logger-receiver-detach :: 0; pnpm check:nul-bytes :: 0; pnpm check:objectql-double-limit :: 0; pnpm check:objectui-changeset :: 0; pnpm check:org-identifier :: 0; pnpm check:page-declaration-shape :: 0; pnpm check:pm-changeset-deadline-census :: 0; pnpm check:query-options-erasure :: 0; pnpm check:refd-timer-probe :: 0; pnpm check:slot-lookup :: 0; pnpm check:test-source-alias :: 0; pnpm check:tier-file-adoption :: 0; pnpm check:watch-hint-literal :: 0; pnpm check:where-matcher :: 0; pnpm check:dts-closure :: 0; pnpm check:dual-build-cjs-loads :: 3; pnpm check:lean-entry-closure :: 0; pnpm check:published-files :: 0; pnpm check:sourcemap-no-sources-content :: 0; pnpm check:type-check-coverage :: 0; pnpm check:type-check-debt :: 0. check:dual-build-cjs-loads narrowed in its place: require() of the CJS entries of core, mcp and metadata-protocol each exit 0, core's exports relationConditionSites as a function. dispatch-gates --ran: 67 derived, 66 run, 1 NOT-MEASURED (exit 3), 0 UNRUN. LINT narrowed: eslint --no-inline-config --format json over the 7 changed .ts files: 7 files, 0 errors, 0 warnings; --print-config shows 5-6 active rules per file and no parserOptions.project/projectService, so no untouched file's verdict can move. CI not awaited.",
"mcp_calls": "0 — no MCP GitHub tool called; reads via gh api REST GETs (card, comments, PR read-back)",
"api_writes": "2 — fleet-write relay (objectstack-fleet[bot]): (1) POST /repos/objectstack-ai/objectstack/dispatches carrying pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos//issues/22820/assignees [os-project-manager]; (2) POST /repos/objectstack-ai/objectstack/dispatches carrying this os-dev-report = POST /repos//issues/22777/comments. Zero label writes; git push not counted.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · noted, not filed — the MCP HTTP door's bridge (callData) prefers the protocol service (the data door #22737 decided) and falls back to the engine when that service is absent; whether the fallback is reachable in a real composition was not measured here (in PR Acceptance notes).",
"carrier: none · noted, not filed — query_records/aggregate_records answer every bridge refusal as text only (errorResult), while errorResultFromThrown in the same file preserves code/status for other tools; pre-existing and identical for an addressed object; no defect class claimed (in PR Acceptance notes)."
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 11, 2026
ACCEPT (seat review): PR #22820 at head
2261c253ecdomain:engineseat 1 (#6367) ·session_01JfJfBUC3cQ6hhgm9MQK76T· 2026-10-11T11:09Z. Claim 6107453288. Dev report 6108171450. Contract review PASS 6108313740 (CONTRACT_REVIEW_TIER, same head). Read against GitHub andorigin/main, not against the report. ⛔ Classes, positions and functions only.Shape.
main, assignedos-project-manager.Fixes #22777. A closing-keyword scan of the whole body finds that line only.Clause-②: yes (narrowing):yesfor@objectstack/core's new export, and(narrowing)for the bridge's accept set.What it does.
queryandaggregateask each nested-relation condition's TARGET the exposure decision forlist, right after the addressed object's gate and before any engine read.enforceRelationTargetExposurecalls the bridge's ownenforceApiExposurewithGATED_ACTIONS.query. That is one decision, and no second rule.404 OBJECT_API_DISABLED, or405 OBJECT_API_METHOD_NOT_ALLOWEDwith the allowed operations). The message names the target and its position.relationConditionSitesmoved frommetadata-protocolto@objectstack/core(relation-condition-sites.ts, withRelationConditionSite). Its body is identical underdiff -w. The data door imports it, and its 136-line refusal region,refuseUnservedRelationTargetincluded, is byte-identical toec7c7e0637.whereis the bridge's only filter position.aggregations[i].filteris unreachable from theaggregate_recordstool, and the engine refuses a nested-relation condition there anyway.isSystembypass is unreachable from this door:assembleExecutionContextemitsisSystem: false. So every reachable caller gets security(data): a nested-relation filter condition on a lookup target is evaluated without asking the target's exposure (census row 4 of #22661) #22737's verdict.not-a-served-read. Row 8 is held in its own package by the 24-cell bridge pin, because the discriminator does not see the bridge.Evidence read.
get-only orcreate-only target was evaluated before (also under$and/$or/$not), and is refused after, match and miss alike;list-only, undeclared and platform-lookup controls are unchanged.$-key descent mutated on core'sdist: the bridge pin, the core walker pin and the data door's own relation pin each red, exactly on the$or/$notcells.2261c253ec:mcp36 files / 417;core2,364, and its repo project 55;metadata-protocol28,129;check:dual-build-cjs-loads, a missing dist). CI'sBuild Corecarries that step and concludedsuccess.check-expected-skipsOK).Changesets checked.
@objectstack/mcp:minor, BREAKING, with FROM → TO and a migration (filter on the lookup's stored id, or declarelist). Its ADR-0087 marker isnot-required (no-migration-prescription), judged honest by the review, in the same shape as security(data): a nested-relation filter condition on a lookup target is evaluated without asking the target's exposure (census row 4 of #22661) #22737's.@objectstack/coreminor(the export) and@objectstack/metadata-protocolpatch(the import): one non-breaking changeset.Files outside the claim's first surface: none. The
packages/mcppath was declared ondomain:cli(#6024, 6107462295).The review's escalation, ruled: filed. The MCP HTTP door's
callDataengine fallback (packages/runtime) handswhereto the engine after judging only the addressed object, on an assembly without the protocol service. The code's own comment names that composition. It is the same class at a third position, so it is filed as census row 10, #22823 (p3,Blocked-by: #22777, a sub-issue of #22661). Finding 2, the text-only tool refusal, is pre-existing and identical for both positions. Not this class, and not filed.Landing.
mainis five commits past the merge base, none touching these nine paths, andmerge-treeis clean.$expand, or the dataset door's dimension-label pass, reads it — detail withheld pending maintainer #22661's checklist ticks row 8, and security(runtime):callData's engine fallback hands a caller'swheretoql.findafter judging only the addressed object, so a nested-relation condition skips the target's exposure ask (census row 10 of #22661) #22823 unblocks.Generated by Claude Code
objectstack-fleet commentedon Oct 11, 2026
Landed: PR #22820 →
c4e7fa5a31·domain:engineseat 1 (#6367) ·session_01JfJfBUC3cQ6hhgm9MQK76T· 2026-10-11T11:42Z⛔ Classes, positions and functions only.
c4e7fa5a31. The two readings:origin/main.enforceRelationTargetExposureis instdio-data-bridge.ts, andpackages/core/src/utils/relation-condition-sites.tsexists.gh-readonly-queue/main/pr-22820-*is gone.completedthroughFixes #22777.pm:dispatchedwas removed in this act, andbug,security,domain:engine,area:accessand the grade stay. The lane's closed set since 10:45Z is this card alone.@objectstack/mcpminor, BREAKING narrowing;@objectstack/coreminor;@objectstack/metadata-protocolpatch):listonqueryandaggregate, before any engine read.relationConditionSitesnow lives in@objectstack/core, and the data door imports it, unchanged.$expand, or the dataset door's dimension-label pass, reads it — detail withheld pending maintainer #22661's checklist (6102873261) ticks row 8. The parent stays open for row 9 (security(auth): the identity import handsrunImporta protocol withoutgetMetaItem, so its reference cells skip the target's exposure ask (census row 9 of #22661) #22800) and row 10 (security(runtime):callData's engine fallback hands a caller'swheretoql.findafter judging only the addressed object, so a nested-relation condition skips the target's exposure ask (census row 10 of #22661) #22823).callData's engine fallback hands a caller'swheretoql.findafter judging only the addressed object, so a nested-relation condition skips the target's exposure ask (census row 10 of #22661) #22823 (row 10,callData's engine fallback), which imports the shared walk. It moves topm:queuein this act.Generated by Claude Code