You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
plugin-security: delegation probes and diagnostics (canReadObject / canWriteObject / canExport, field mask, explain) answer for an on-behalf-of context the operation prologue now refuses (PR #25101) #25107
Filing gate ①, class (b): a served answer that disagrees with the operation it describes, with named producers. The source is the isolated contract review of PR #25101 (#25063), boundary flags 1–2 of 6313542596 (judged parity only: none of these serves rows), and the #25063 dev's round-2 finding. Filed by domain:services seat 2 (seat post #21118, os-support-ai, session session_01UmqzD97e14iws6hmVhgZ5y). ⛔ Not a claim. ⛔ Ungraded and unrouted: the grade and lane are triage's.
Disclosure: the #9387 ruling (5323310017) applies, as on #25063. ⛔ No probe or context shape goes on this card, its PR or any comment.
Several plugin-security paths resolve a delegator on their own, and do not apply that rule. So for such a context they can answer "admitted" while the operation is refused:
the admission probes canReadObject, canWriteObject and canExport;
resolveProjectionFieldMask / getReadableFields;
the explain engine (explainAccessForCaller);
describeDelegationNarrowing;
delegatedWriteContexts.
None of them serves rows: their consumers run the operation through the engine, which refuses.
The raw-SQL read-scope provider is NOT in this card. It serves rows, and is filed separately.
resolveDelegatorContext's comment in explain-engine.ts ("the agent and its delegator are, by construction, in the same org") could point at the prologue check.
Reach: in process only. The one producer of an on-behalf-of context (the /mcp door) never builds the shape.
Governing text
ADR-0090 D10, and the probes' own promise to answer as the operation does.
Dedupe
Searched (semantic): delegated on-behalf-of probe canWriteObject explain parity organization membership. The hits are #25063 (the parent rule) and #24822, plus closed cards; none is this finding.
Claim: card thread · domain:services seat 1 (seat post #6021, project run by the Sales account) · p3 · area:access · the whole card · 2026-10-22T19:03Z
Session: session_01DPxprKQnzCbhdMTXUGRu31
Account: os-sales (the seat's linked user; the card's assignee)
Branch: claude/project-thread-a7kyo3 (the harness-pinned branch for this session; it names no issue, so the Duplicate Fix Guard's branch-name hint will not match — this claim is the identity record)
Worktree: objectstack-issue-25107
Domain: domain:services
Seat: domain:services#1 (seat post #6021)
File surface (read on origin/maincc8d886d4; stop on a breach and explain it in the report):
packages/plugins/plugin-security/src/explain-engine.ts: one exported helper beside resolveDelegatorContext that asks vetOrganizationClaim once with the prologue's inputs (the live organization, the delegator's own memberships, the posture); the explain engine's delegated path in explainOnce asks it and fails closed as it does for a missing delegator; ExplainEngineDeps gains a required tenancyPosture member (the module's own rule for a dep whose omission would misreport, as getPartialMaskRules is required); the resolveDelegatorContext docblock's "same org" sentence points at the prologue check.
packages/plugins/plugin-security/src/security-plugin.ts: the seven sites the card names (canReadObject, canWriteObject, canExport, resolveProjectionFieldMask behind getReadableFields and its siblings, describeDelegationNarrowing, delegatedWriteContexts, and the explain deps wiring) ask the helper at the prologue's point; the prologue (resolveOperationPrincipals) and computeDelegatedReadFilter are rerouted through the same helper with byte-identical behaviour, so the rule has one ask in the plugin.
Tests: new cases per path in delegated-organization-membership.test.ts (the rule's file); the test files that build ExplainEngineDeps by hand gain the tenancyPosture line.
One patch changeset for @objectstack/plugin-security.
⛔ Not packages/core, packages/spec, plugin-sharing or any other plugin.
Container & model: M, card thread (in-session executor), model: default tier (dispatch-gates.mjs --tier on this surface: "no path-derived mandate"), effort: xhigh (security family); a CONTRACT_REVIEW_TIER review of record before the queue (a narrowing)
Clause-②: no (narrowing)
Metadata-protocol: none
Basis for the Clause-② line: probes and diagnostics that answered an admission for a context the operation prologue refuses now answer the refusal (allow to deny), aligned with the operation; nothing widens. As on plugin-security: the raw-SQL read-scope provider (getReadFilter) does not apply the delegated organization-membership rule PR #25101 enforces at the operation prologue; measure reach first (detail withheld) #25106 (PR fix(plugin-security)!: the raw-SQL read scope (getReadFilter) answers the deny sentinel where the delegated prologue refuses #25150): patch with the BREAKING banner and its ADR-0087 disposition.
Responsibility: plugin-security's own probes, field projections and explain path resolve a delegator without the prologue's organization check | the prologue's refusal (ADR-0090 D10 via vetOrganizationClaim) already covers every operation, and #25106 mirrored it in getReadFilter | in-process callers only: the one producer of an on-behalf-of context never builds the shape (the card's reach)
Thread-read: none
Serial constraints cleared:
os-dev-report
{
"issue": 25107,
"status": "done",
"branch": "claude/project-thread-a7kyo3",
"pr": "#25288",
"session": "session_01DPxprKQnzCbhdMTXUGRu31 (card thread)",
"premise_still_valid": true,
"summary": "One helper, delegatedOrganizationRefusal (explain-engine.ts, beside resolveDelegatorContext), asks vetOrganizationClaim once with the prologue inputs (live organization, the delegator own memberships, the posture). The prologue and computeDelegatedReadFilter now ask it (behaviour byte-identical, their 42 cells unchanged), and canReadObject, canWriteObject, canExport, resolveProjectionFieldMask (the four field projections), explainOnce, describeDelegationNarrowing and delegatedWriteContexts ask it at the prologue point, so on a walled posture each answers the refusal the operation already gets. ExplainEngineDeps gains a required tenancyPosture (wired by the plugin; the seven hand-built test deps pass single). Changeset patch with the BREAKING banner, Clause-②: no (narrowing), Metadata-protocol: none, ADR-0087 not-required (runtime-interface-only packages/plugins/plugin-security/src/explain-engine.ts#ExplainEngineDeps).",
"tests": "Rule file delegated-organization-membership.test.ts 42 to 66 cases (refusal 10, preservation 11, parity 2, enumeration 1), 66/66. Red first (both sources at base cc8d886, new tests kept): Tests 13 failed | 53 passed, exactly the 13 new non-preservation cells. Ablation, scripts/ablation-replace.mjs WRAP mode, one leg per site: describeDelegationNarrowing 3 red, delegatedWriteContexts 3, resolveProjectionFieldMask 7, canReadObject 9, canWriteObject 9, canExport 9, explainOnce 11, the helper itself 36 (every refusal cell incl. the prologue and read-scope 24); each restore proven (blob == HEAD security-plugin.ts 4e9f0dc892a5 / explain-engine.ts aee792270aa0, git diff HEAD empty). Source-imported subject, no dist leg. Package suite on 8bb2d5e: Test Files 209 passed (209), Tests 4320 passed | 45 skipped (4365); typecheck exit 0. The first package run on c631ec4 caught 1 moved pin (explain-dependency-fault.test.ts control: its walled-stack delegator had no membership in the agent organization, the refused shape); fixture now seeds the membership, 16/16. Cross-package on-behalf-of suites through the real SecurityPlugin: objectql nested-composition 12/12, service-storage 2 files 152/152, plugin-sharing authored-row-write-deferral 20/20, plugin-audit 2 files 83/83, runtime http-dispatcher 248/248, each exit 0. All under os-verify-lock.",
"gates": "dispatch-gates --commands on 8bb2d5e: 70 derived, all exit 0 (check:dual-build-cjs-loads after building its 8 missing dists; check:type-check-debt re-run under the lock after a queue-timeout exit 3). --ran: 70 derived, 70 run, 0 NOT-MEASURED, 0 UNRUN, exit 0. Roster gates under touched dirs (check-changeset-fixed, check:error-code-casing, check:filter-alias-parity): exit 0. CI on the PR: in_progress at report time; the type-check lanes and path-scheduled jobs are declared to CI.",
"dogfood_battery": "8bb2d5e0 · os-verify-lock: VERDICT command-exit 0 · held the lock 1150s · Test Files 289 passed | 3 skipped (292) · Tests 2481 passed | 18 skipped (2499) · no failures (closure built first: turbo build --filter=@objectstack/dogfood^... exit 0)",
"mcp_calls": "8 — reads only: issue_read x2, pull_request_read x5, search_pull_requests x1 (plus the harness PR-activity subscription, not a GitHub write)",
"api_writes": "5 relay strokes, all via scripts/pm (objectstack-fleet[bot]): label-write #25107 (assignees os-sales, labels +pm:dispatched -pm:queue); post-stamped claim comment 6348946648 on #25107; dispatch pr_create #25288 (POST /pulls draft, POST /issues/25288/assignees os-sales); dispatch pr_request_reviewers (POST /pulls/25288/requested_reviewers os-sales); post-stamped this report on #25107. git push is not a REST write.",
"open_questions": [],
"out_of_scope_findings": [],
"notes": "Serial: #25256 (#25095) landed on main after the branch was cut; it edits security-plugin.ts in hunks disjoint from these (git merge-tree clean), and CI reads the merge ref. A CONTRACT_REVIEW_TIER review of record is owed before the queue (narrowing). PR body was written once at pr_create."
}
Activity
objectstack-fleet commentedon Oct 22, 2026
Claim: card thread ·
domain:servicesseat 1 (seat post #6021, project run by the Sales account) · p3 ·area:access· the whole card · 2026-10-22T19:03ZSession:
session_01DPxprKQnzCbhdMTXUGRu31Account:
os-sales(the seat's linked user; the card's assignee)Branch:
claude/project-thread-a7kyo3(the harness-pinned branch for this session; it names no issue, so the Duplicate Fix Guard's branch-name hint will not match — this claim is the identity record)Worktree:
objectstack-issue-25107Domain:
domain:servicesSeat:
domain:services#1(seat post #6021)File surface (read on
origin/maincc8d886d4; stop on a breach and explain it in the report):packages/plugins/plugin-security/src/explain-engine.ts: one exported helper besideresolveDelegatorContextthat asksvetOrganizationClaimonce with the prologue's inputs (the live organization, the delegator's own memberships, the posture); the explain engine's delegated path inexplainOnceasks it and fails closed as it does for a missing delegator;ExplainEngineDepsgains a requiredtenancyPosturemember (the module's own rule for a dep whose omission would misreport, asgetPartialMaskRulesis required); theresolveDelegatorContextdocblock's "same org" sentence points at the prologue check.packages/plugins/plugin-security/src/security-plugin.ts: the seven sites the card names (canReadObject,canWriteObject,canExport,resolveProjectionFieldMaskbehindgetReadableFieldsand its siblings,describeDelegationNarrowing,delegatedWriteContexts, and the explain deps wiring) ask the helper at the prologue's point; the prologue (resolveOperationPrincipals) andcomputeDelegatedReadFilterare rerouted through the same helper with byte-identical behaviour, so the rule has one ask in the plugin.delegated-organization-membership.test.ts(the rule's file); the test files that buildExplainEngineDepsby hand gain thetenancyPostureline.patchchangeset for@objectstack/plugin-security.packages/core,packages/spec,plugin-sharingor any other plugin.Container & model:
M, card thread (in-session executor),model: default tier(dispatch-gates.mjs --tieron this surface: "no path-derived mandate"),effort: xhigh(securityfamily); aCONTRACT_REVIEW_TIERreview of record before the queue (a narrowing)Clause-②: no (narrowing)
Metadata-protocol: none
Basis for the Clause-② line: probes and diagnostics that answered an admission for a context the operation prologue refuses now answer the refusal (allow to deny), aligned with the operation; nothing widens. As on plugin-security: the raw-SQL read-scope provider (getReadFilter) does not apply the delegated organization-membership rule PR #25101 enforces at the operation prologue; measure reach first (detail withheld) #25106 (PR fix(plugin-security)!: the raw-SQL read scope (getReadFilter) answers the deny sentinel where the delegated prologue refuses #25150):
patchwith the BREAKING banner and its ADR-0087 disposition.Responsibility:
plugin-security's own probes, field projections and explain path resolve a delegator without the prologue's organization check | the prologue's refusal (ADR-0090 D10 via vetOrganizationClaim) already covers every operation, and #25106 mirrored it in getReadFilter | in-process callers only: the one producer of an on-behalf-of context never builds the shape (the card's reach)Thread-read: none
Serial constraints cleared:
security-plugin.tsin hunks near the imports and past line 8900 (the master-detail write gate); every site above is outside them, andexplain-engine.tsis not in it. Whichever lands second mergesmain.plugin-securityfiles (objects, translations, audience bindings), none on this surface.0b8bce40e0.Resume-from: none
⛔ Disclosure (#9387,
5323310017): this claim, the PR and every comment state the rule and an abstract reach only; no probe or context shape.Generated by Claude Code
objectstack-fleet commentedon Oct 22, 2026
os-dev-report
{
"issue": 25107,
"status": "done",
"branch": "claude/project-thread-a7kyo3",
"pr": "#25288",
"session": "session_01DPxprKQnzCbhdMTXUGRu31 (card thread)",
"premise_still_valid": true,
"summary": "One helper, delegatedOrganizationRefusal (explain-engine.ts, beside resolveDelegatorContext), asks vetOrganizationClaim once with the prologue inputs (live organization, the delegator own memberships, the posture). The prologue and computeDelegatedReadFilter now ask it (behaviour byte-identical, their 42 cells unchanged), and canReadObject, canWriteObject, canExport, resolveProjectionFieldMask (the four field projections), explainOnce, describeDelegationNarrowing and delegatedWriteContexts ask it at the prologue point, so on a walled posture each answers the refusal the operation already gets. ExplainEngineDeps gains a required tenancyPosture (wired by the plugin; the seven hand-built test deps pass single). Changeset patch with the BREAKING banner, Clause-②: no (narrowing), Metadata-protocol: none, ADR-0087 not-required (runtime-interface-only packages/plugins/plugin-security/src/explain-engine.ts#ExplainEngineDeps).",
"tests": "Rule file delegated-organization-membership.test.ts 42 to 66 cases (refusal 10, preservation 11, parity 2, enumeration 1), 66/66. Red first (both sources at base cc8d886, new tests kept): Tests 13 failed | 53 passed, exactly the 13 new non-preservation cells. Ablation, scripts/ablation-replace.mjs WRAP mode, one leg per site: describeDelegationNarrowing 3 red, delegatedWriteContexts 3, resolveProjectionFieldMask 7, canReadObject 9, canWriteObject 9, canExport 9, explainOnce 11, the helper itself 36 (every refusal cell incl. the prologue and read-scope 24); each restore proven (blob == HEAD security-plugin.ts 4e9f0dc892a5 / explain-engine.ts aee792270aa0, git diff HEAD empty). Source-imported subject, no dist leg. Package suite on 8bb2d5e: Test Files 209 passed (209), Tests 4320 passed | 45 skipped (4365); typecheck exit 0. The first package run on c631ec4 caught 1 moved pin (explain-dependency-fault.test.ts control: its walled-stack delegator had no membership in the agent organization, the refused shape); fixture now seeds the membership, 16/16. Cross-package on-behalf-of suites through the real SecurityPlugin: objectql nested-composition 12/12, service-storage 2 files 152/152, plugin-sharing authored-row-write-deferral 20/20, plugin-audit 2 files 83/83, runtime http-dispatcher 248/248, each exit 0. All under os-verify-lock.",
"gates": "dispatch-gates --commands on 8bb2d5e: 70 derived, all exit 0 (check:dual-build-cjs-loads after building its 8 missing dists; check:type-check-debt re-run under the lock after a queue-timeout exit 3). --ran: 70 derived, 70 run, 0 NOT-MEASURED, 0 UNRUN, exit 0. Roster gates under touched dirs (check-changeset-fixed, check:error-code-casing, check:filter-alias-parity): exit 0. CI on the PR: in_progress at report time; the type-check lanes and path-scheduled jobs are declared to CI.",
"dogfood_battery": "8bb2d5e0 · os-verify-lock: VERDICT command-exit 0 · held the lock 1150s · Test Files 289 passed | 3 skipped (292) · Tests 2481 passed | 18 skipped (2499) · no failures (closure built first: turbo build --filter=@objectstack/dogfood^... exit 0)",
"mcp_calls": "8 — reads only: issue_read x2, pull_request_read x5, search_pull_requests x1 (plus the harness PR-activity subscription, not a GitHub write)",
"api_writes": "5 relay strokes, all via scripts/pm (objectstack-fleet[bot]): label-write #25107 (assignees os-sales, labels +pm:dispatched -pm:queue); post-stamped claim comment 6348946648 on #25107; dispatch pr_create #25288 (POST /pulls draft, POST /issues/25288/assignees os-sales); dispatch pr_request_reviewers (POST /pulls/25288/requested_reviewers os-sales); post-stamped this report on #25107. git push is not a REST write.",
"open_questions": [],
"out_of_scope_findings": [],
"notes": "Serial: #25256 (#25095) landed on main after the branch was cut; it edits security-plugin.ts in hunks disjoint from these (git merge-tree clean), and CI reads the merge ref. A CONTRACT_REVIEW_TIER review of record is owed before the queue (narrowing). PR body was written once at pr_create."
}
objectstack-fleet commentedon Oct 22, 2026
Landed ·
domain:servicesseat 1 (seat post #6021) ·os-sales·session_016g7nYP9wdNx1GDa5XDr7Uy· 2026-10-22T21:47Z6351583081, contract review6350743954)origin/main5801a4f2c9State: the card closed
completedthrough the PR; this stroke clearspm:dispatchedand the assignee.Generated by Claude Code