Skip to content

fix(plugin-security)!: the raw-SQL read scope (getReadFilter) answers the deny sentinel where the delegated prologue refuses - #25150

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-25106-read-scope-org-vet
Oct 22, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-25106-read-scope-org-vet

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #25106

Clause-②: no (narrowing) — on a walled posture, the security service's raw-SQL read scope for an on-behalf-of context whose live active organization the delegator's membership does not back is the deny sentinel, where it used to be a composed scope.
Metadata-protocol: none

Seat call of record: 6328262579 on the card (option A, the provider mirror). The claim 6328928374 carries Clause-②: no. This body adds the (narrowing) arm and the BREAKING banner, following #25063's precedent under the amended 「甲」 (the dispatch says to follow it and say so); the level is patch, per ruling A's level axis (Metadata-protocol: none). ⛔ Disclosure (#9387 ruling 5323310017): this body states the rule and an abstract reach only.

The rule

ADR-0021 D-C makes the raw-SQL read scope the find path's scope, and getReadFilter's own doc promises agreement with the engine middleware. Under isolated or group, the delegated-principal prologue (resolveOperationPrincipals) refuses every operation of an on-behalf-of context whose live active organization the delegator's own membership does not back (ADR-0090 D10, landed with #25063). computeDelegatedReadFilter mirrored the prologue's dangling-delegator refusal but not that one, so for such a context the provider answered a composed scope where the find path refuses.

computeDelegatedReadFilter (packages/plugins/plugin-security/src/security-plugin.ts) now asks vetOrganizationClaim once, on the resolved-delegator arm, right after the dangling-delegator mirror and before the delegator's sets resolve: the prologue's position. It passes the prologue's inputs (the live organization, the delegator's memberships, the posture) and answers RLS_DENY_FILTER itself, by reference, where the prologue refuses. The refusal logs at warn (a refusal is not a fault), as the prologue's does. No error code, no new rule text: vetOrganizationClaim stays the one spelling of "a membership backs this organization". Nothing else in the file moves: not the prologue, not getReadFilter, not canReadObject.

Reach, abstractly

In process only. No served door builds the shape (readings 6328225139, seat-verified in 6328262579): the one producer of an on-behalf-of context, the /mcp OAuth door, vets the organization against the same membership, and the engine's own composition runs behind the prologue. The population is an in-process caller that hands such a context to the published provider, or to the analytics service.

Boundary, before and after

  • Before: under isolated or group, the provider answered a composed read scope for that context, while the engine's find refused it 403 PERMISSION_DENIED.
  • After: the provider answers the deny sentinel, so a door that bypasses the engine reads zero rows, on every object (public, private and organization-less alike: the rule is about the context).
  • Unchanged: the single posture; an on-behalf-of context with no active organization; an on-behalf-of context in an organization the delegator belongs to; a context without onBehalfOf; a dangling delegator (still the sentinel); system contexts.

Pins

All in packages/plugins/plugin-security/src/delegated-organization-membership.test.ts, the rule's existing file (real ObjectQL from source, real SqlDriver, real SecurityPlugin; the delegator rebuilt from stored rows). 17 new cases; the file goes from 25 to 42.

  • Refusal (6): group and isolated × a public, a private and an organization-less object: getReadFilter answers toBe(RLS_DENY_FILTER).
  • Preservation (9): per walled posture, an in-set delegated context, the undelegated caller, a delegated context with no active organization and a system context; plus the single posture. Each scope is pinned by the rows it admits.
  • Parity (2): per walled posture, a table over the contexts the prologue decides × the three objects: find refused 403 PERMISSION_DENIED ⇔ the provider's deny sentinel, and where find admits, the scope admits exactly the rows find returns. The table asserts its refused set, so neither side is vacuous.

One-time proofs (not left as tests):

  • Before/after answers. The provider's answer, the rows it admits and the find outcome were recorded for 45 cells (three postures × five context kinds × three objects) on the unmodified provider and again with the fix. 39 answers are byte-identical (scope JSON and admitted rows). The 6 that changed are exactly the refusal cells, each of which find refuses.
  • Ablation. With the fix committed, scripts/ablation-replace.mjs replaced the vetOrganizationClaim ask with false (anchor x1 → x0, blob 130693ab6579 → 447156f4c27f, marker on disk x1), and the file ran: Tests 8 failed | 34 passed (42). The 8 are the 6 refusal cells and the 2 parity cells; every preservation cell and all 25 prologue cells stayed green. Restored by the tool: blob after restore = HEAD blob 130693ab6579, git diff HEAD empty (re-read independently: git status --porcelain empty, git hash-object = HEAD: blob). The subject is imported from source (./security-plugin.js), so there is no dist/ leg.
  • Red first. Before the fix, the 6 refusal cells were red and every other case green (Tests 6 failed | 28 passed, with the temporary measuring cell).

The six existing plugin-security files the readings flagged as building delegated contexts through getReadFilter under a walled posture all stay green: their delegated cells are dangling-delegator or single-posture cases, or reach the prologue directly. No pin needed flipping.

Tests and gates

Read on head 03a9717ca (the fix 58623eceb plus a clean merge of origin/main 459afea1f, which moved packages/lint and three driver-sql test files only; @objectstack/lint was rebuilt after it). Every run went through scripts/pm/os-verify-lock.sh; each exit code was captured before any pipe.

  • Dependency closure (①): pnpm --workspace-concurrency=2 --filter '@objectstack/plugin-security^...' build (19 packages): VERDICT command-exit 0.
  • The package (②): pnpm --filter @objectstack/plugin-security exec vitest run --maxWorkers=2: Test Files 207 passed (207), Tests 4253 passed | 45 skipped (4298). pnpm --filter @objectstack/plugin-security typecheck: exit 0 (check:test-typecheck: OK; the new cases' file is in tsconfig.test.json's program, counted with --listFiles). Read on 58623eceb; on 03a9717ca the rule's file and the six flagged files re-ran: Test Files 7 passed (7), Tests 340 passed (340).
  • The derived gate families (③): node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on 03a9717ca derived 69 commands; 67 exit 0. Two exit 3, PREREQUISITE NOT MET, NOT MEASURED locally: pnpm check:dual-build-cjs-loads (needs every workspace package's dist/) and pnpm check:i18n (needs the built CLI and the build closures of nine packages). This diff changes no translation key, label or build output shape; CI's required Lint & Repo Gates runs both. --ran reconciliation: 69 derived, 67 run, 2 NOT-MEASURED, 0 UNRUN, exit 0.
  • Not run locally, declared to CI: test:repo (the diff changes none of its tests), the type-check lanes and the path-scheduled CI jobs dispatch-gates names.
  • Full dogfood battery: owed on this head (the title carries !); its reading is reported in the os-dev-report comment on the card.

Acceptance notes


Generated by Claude Code

…el where the delegated prologue refuses

computeDelegatedReadFilter now asks vetOrganizationClaim once on the
resolved-delegator arm, with the inputs the operation prologue passes,
right after the dangling-delegator mirror and before the delegator's
sets resolve. On a walled posture, a delegated context whose live active
organization the delegator's own membership does not back answers
RLS_DENY_FILTER itself, as the find path refuses it (ADR-0090 D10,
ADR-0021 D-C). The single posture, a context with no active
organization, an in-set delegated context, an undelegated caller and a
system context keep their answers.

Pins in delegated-organization-membership.test.ts: refusal cells under
both walled postures on a public, a private and an organization-less
object; preservation cells; and a parity cell against the engine find.

Claude-Session: https://claude.ai/code/session_01UnHcTADjeg3VwxF21QHj7W
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Oct 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/plugin-security, touching 2 documentable anchor(s).

4 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/permissions/field-level-security.mdx (via SecurityPlugin (symbol, a top-level class))
  • content/docs/permissions/index.mdx (via SecurityPlugin (symbol, a top-level class))
  • content/docs/plugins/packages.mdx (via SecurityPlugin (symbol, a top-level class))
  • content/docs/ui/forms.mdx (via SecurityPlugin (symbol, a top-level class))

⛔ 2 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/implementation-status.mdx (via SecurityPlugin (symbol, a top-level class))
  • content/docs/releases/v18/18-0.mdx (via SecurityPlugin (symbol, a top-level class))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 16 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 459afea1f69ff69422de2a7a2c7674b6f7ea1aca → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 09dffc6d0108cd9935d5445efe8215e4ee5da84c — the merge of head 03a9717ca74695f7f9b8ea238fdae69611d7dfc3 into base 459afea1f69ff69422de2a7a2c7674b6f7ea1aca, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 09dffc6d0108cd9935d5445efe8215e4ee5da84c && git checkout 09dffc6d0108cd9935d5445efe8215e4ee5da84c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 459afea1f69ff69422de2a7a2c7674b6f7ea1aca 03a9717ca74695f7f9b8ea238fdae69611d7dfc3 && git checkout -B drift-repro 459afea1f69ff69422de2a7a2c7674b6f7ea1aca && git merge --no-ff 03a9717ca74695f7f9b8ea238fdae69611d7dfc3

node scripts/docs-audit/affected-docs.mjs --json 459afea1f69ff69422de2a7a2c7674b6f7ea1aca

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 459afea1f69ff69422de2a7a2c7674b6f7ea1aca → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 03a9717ca74695f7f9b8ea238fdae69611d7dfc3
Local-runs: none
Scope: packages/plugins/plugin-security/src/security-plugin.ts, packages/plugins/plugin-security/src/delegated-organization-membership.test.ts + packages/core/src/security/resolve-authz-context.ts (vetOrganizationClaim), packages/spec/src/security/tenancy-posture.ts (postureEnforcesWall), packages/plugins/plugin-security/src/explain-engine.ts (resolveDelegatorContext, buildContextForUser), packages/objectql/src/engine.ts (composeReadableIdsAtBoundary, the engine's getReadFilter call site), packages/plugins/plugin-audit/src/parent-record-read-gate.ts (the header flag), docs/adr/0021-analytics-dataset-semantic-layer.md (D-C), .changeset/25106-read-scope-delegated-organization-membership.md (② only: its level and its declaration lines)

Rendered at 2026-10-22T01:18Z on card #25106's body and all eight comments, PR #25150's body, file list and net diff against main at this head (the PR base 459afea1f is the merge base and the current origin/main, so the two-dot and three-dot diffs are one: 3 files, +192/−2), and the head's check-runs. Ruling of record: seat call 6328262579 (option A, the provider mirror) under claim 6328928374. Disclosure (#9387, 5323310017): this record states the rule, the narrowing and the verdict abstractly.

① Derived judgments

(a) The one accept-set change, confined to computeDelegatedReadFilter: right. The net diff to security-plugin.ts is two hunks, both inside that private method (its docblock, and one branch in its body); nothing else in the file moves — not getReadFilter, not resolveOperationPrincipals, not canReadObject. The method has one caller, getReadFilter's delegated branch, so the narrowing reaches exactly what that branch serves: the published security service's getReadFilter answer (the analytics read-scope bridge, and the engine's ReadableIds composition) for a context that carries the delegation link. No export is added or changed, no rule text is duplicated and no error code is added: the branch answers the existing RLS_DENY_FILTER sentinel by reference, as every other deny in the method does, and logs at warn, as the prologue's refusal does.

(b) The refused cells are exactly the prologue's, under both walled postures: right. Read side by side, the new branch and the prologue's branch (resolveOperationPrincipals, the resolved arm after the dangling-delegator refusal) compute the live organization by the same expression (a non-empty tenantId, stringified), hand vetOrganizationClaim the same three inputs — that organization, the delegator context's own accessible_org_ids (resolved from the delegator's current memberships by buildContextForUser, never inherited from the live principal, and fresh on every call) and this.tenancyPosture — and refuse on the same reading (undefined). Read in @objectstack/core, the function answers undefined only when a wall is enforced (postureEnforcesWall: every posture but single) and the set does not hold the claim. So the provider answers the deny sentinel precisely where the find path refuses — a walled posture, a live active organization, a delegator whose membership does not back it — and, as the pins show, on a public, a private and an organization-less object alike, because the rule reads the context and not the object. The ask sits at the prologue's position: after the dangling-delegator mirror, before the delegator's sets resolve.

(c) Every other cell is unchanged, by construction: right. single (or no posture): the function returns the claim, so the branch does not fire. No active organization: the guard liveOrganization !== undefined skips the ask entirely — load-bearing, since the function answers undefined for an absent claim — and it is identical to the prologue's guard (ADR-0123 D2 keeps that context). In-set delegated context: the claim is in the delegator's set, the branch does not fire. Undelegated caller: getReadFilter never enters the delegated method. Dangling delegator: the sentinel is answered one statement earlier, as before. System context: getReadFilter's isSystem exit precedes the delegated branch and is untouched. A principal that resolves no set: the sentinel is answered before the delegator is resolved, as before. The engine's own composition asks the provider only inside the executor behind the middleware chain (find, findOne, count, aggregate), so for an operation the prologue admitted the ask repeats with the same fresh inputs and the same answer; it cannot move an admitted operation.

(d) The pins are the right cells and are not vacuous: right. Six refusal cells (two walled postures × three objects) assert the sentinel by identity. Nine preservation cells (per walled posture: in-set delegated, undelegated, no active organization, system; plus single) assert "not the sentinel" and then pin the scope by the rows it admits, read as a bypassing door would read it. Two parity cells walk the contexts the prologue decides × the three objects and assert per cell that find refused 403 PERMISSION_DENIED ⇔ the provider's sentinel, and where find admits, that the scope admits exactly find's rows — and they assert the refused set itself, so neither side is empty. The ablation and the before/after recording are the dev's runs, not re-run here; the method is sound (the ask is the branch's only conjunct besides the guard, so replacing it with false is the rule removed, and the predicted reds are the refusal and parity cells only). No existing pin needed flipping, which the check-runs (③) confirm.

(e) One bound on the prose, not the code. "No served door builds the shape" holds at the shipped default; as the precedent record on PR #25101 noted, with the grants cache on (a non-default configuration) the /mcp door may hand over a context the prologue then refuses fresh. This diff gives the provider the same fresh answer for that context, which is the fail-closed direction and keeps the lock-step; no overstatement beyond that window.

② Semver level

.changeset/25106-read-scope-delegated-organization-membership.md: '@objectstack/plugin-security': patch, a fix(plugin-security)!: title, Clause-②: no (narrowing) and Metadata-protocol: none, exactly one ADR-0087 marker (not-required (no-migration-prescription)), and the BREAKING banner with FROM, TO, Unchanged and the in-process producer's fix. The PR body carries the same two declaration lines, the arm identical. Level: right. The diff moves one published package and changes nothing an author writes against (no spec key, accepted value, metadata type, meaning or default), so Metadata-protocol: none is honest, and under ruling A (#25105) none grades patch — a minor beside none is what the level gate now refuses. The arm is right: a context that was answered a composed scope is now answered the deny sentinel and nothing widens, so this is a runtime narrowing, BREAKING under 「甲」 rule 2 as amended (6303865025); ruling A keeps the BREAKING banner and the ADR-0087 disposition on a non-protocol narrowing while re-levelling it to patch. That is the precedent's shape (#25063 / PR #25101, which shipped minor before ruling A) with the level moved by the later ruling, as the dev's deviation note says. The ADR-0087 arm is consistent with the diff: no declaration is removed or narrowed (one branch in a private method), the package publishes, no ADR-0087 id is named. Check Changeset is green on the head. The changeset's sentences are the lane seat's at TIER_DEFAULT; only the level and the two lines are judged here.

③ Boundary flags

  1. canReadObject does not vet the organization (Acceptance note 1): 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 is open and carries it. Judged: the row scope's deny is the fail-closed half; object admission is the other half (ADR-0139 D2's split), and the analytics door asks it through canReadObject before getReadFilter, so after this change the refused shape is admitted at the object level on that door and served zero rows rather than a 403. Answered: carried by 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; no change to this verdict.
  2. The stale plugin-audit header (Acceptance note 2; parent-record-read-gate.ts, its item 1): verified at this head — it still says the security service's read scope does not carry the delegator intersection, which getReadFilter's own docblock and the engine's composition contradict. The routing it describes (a delegated caller goes to the bounded probe; no leaf is written) stays safe. Documentation drift outside this claim's surface, with no carrier: escalated to the seat to carry (a one-line doc fix, or a row on the 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 family). No verdict impact.
  3. The parity table's coverage (Acceptance note 3): right as stated — the table covers the contexts the prologue decides; a grant-intersection refusal is object admission's half for any caller, and the no-active-organization context is pinned in the preservation block, where this diff leaves it byte-identical by the guard in ①(c). Answered.
  4. The system-context observation (Acceptance note 4): the provider's isSystem exit is untouched by this diff and matches its documented mirror of the middleware's system skip; what the engine's find applies for a system context that carries an active organization is not the security middleware's doing and not this rule's. Pre-existing and outside this narrowing: escalated to the seat as an observation to triage (file or dismiss). No verdict impact.
  5. The review owed at CONTRACT_REVIEW_TIER (Acceptance note 5): this record.
  6. open_questions: the final dev report (6330174654) carries none. The readings report (6328225139) carried one (build the mirror, or close as no-door reach); the seat call 6328262579 answered it (A), and the PR implements A as called — the provider mirror only; no prologue edit, no canReadObject, no service-analytics, no plugin-audit. Answered.
  7. Deviations in the dev report: the (narrowing) arm and the banner added over the claim's bare Clause-②: no is the precedent's move and is judged right in ②; the two derived gates NOT MEASURED locally and test:repo not run locally are covered by the head's Lint & Repo Gates and Test Core; the pins living in the rule's existing file is inside the claim's surface. Answered.
  8. Disclosure: the card, the PR body, the changeset, the two dev reports and this record carry the rule and an abstract reach only; the pins land with the fix and pin the refusal, not the unfixed state.
  9. Check-runs on the head, read before rendering: 41 runs — 34 success, 7 skipped (conditional or opt-in: Temporal Conformance, Build Docs, Console Pin Gate, the Dogfood Regression Gate shards, Dogfood Verify CLI, Packed-tarball smoke, 18.x upgrade harness), 0 failure, 0 pending. Green includes the required Lint & Repo Gates, TypeScript Type Check, Test Core and its six shards, Build Core and Governed Surface Queue Guard, plus Check Changeset, Dogfood Regression Gate, the four type-check lanes, the lint lanes and the three claim/card guards. Those conclusions are the gate verdicts; nothing was built, run or re-run here.

Implemented-by: claude/issue-25106-read-scope-org-vet
Reviewed-by: session_01UnHcTADjeg3VwxF21QHj7W

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

domain:services seat 2 (seat post #21118) · os-bill · session_01UnHcTADjeg3VwxF21QHj7W · 2026-10-22T01:21Z · ACCEPT at head 03a9717ca74695f7f9b8ea238fdae69611d7dfc3 · dev report on #25106 · ⛔ Disclosure (#9387, 5323310017): abstract only.

  1. Shape: a draft PR to main whose first line is Fixes #25106, assigned os-bill.
  2. Premises measured: the readings 6328225139 (no served door builds the shape) and the seat call A (6328262579).
    • The diff adds one vetOrganizationClaim ask inside computeDelegatedReadFilter, after the dangling-delegator mirror. Nothing else in the file moves.
    • The red-first run, the before/after table (39 of 45 cells identical, the 6 changed being exactly the cells find refuses) and the ablation (8 red) are in the PR.
  3. Gates (check-runs pinned to this head):
    • 34 success, including Lint & Repo Gates, TypeScript Type Check, Governed Surface Queue Guard, Check Changeset, Build Core and Test Core 1–6.
    • 7 skipped, each on the check-expected-skips.mjs roster.
    • mergeable_state: clean. check-governed-merges.mjs --pr 25150: NOT governed, +192 / −2.
    • Contract review PASS at CONTRACT_REVIEW_TIER on this head, from an isolated reviewer: 6330333152. The seat read its record on GitHub: the head sha, Served-tier, Scope (the narrowing face's two paths plus the cited code), Local-runs: none and the verdict.
    • dogfood_battery, owed for the ! title: 287 test files passed / 3 skipped, 2395 tests passed, on this head, under the lock's VERDICT command-exit 0.
  4. Accepted deviations:
    • the claim's bare Clause-②: no becomes no (narrowing) in the body and changeset;
    • two derived gates not measured locally (they need a full-repo build), green in CI.
  5. Out of scope (each finding, one line):
  6. Landing: ordinary queue landing (not governed). The seat sets ready and auto-merge through the relay in this act. After the merge, 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 closes by Fixes; the seat removes pm:dispatched and records the landing. plugin-security: a controlled_by_parent child's read loads every readable master id with no limit (twice per request), so its CPU grows with the master table and the read answers 500 "too many SQL variables" past ~32.8k masters #24932 (blocked on objectql: a controlled_by_parent detail whose master is bound to another datasource, or carries an object-scoped read middleware, is admitted at registration and refused only at read time — refuse the boot instead (ADR-0139 D5.2 / D5.3) #25144) merges main over this change when it is re-dispatched.

Changeset sentences checked against the diff:

  • the level and declaration lines;
  • the BREAKING banner;
  • FROM: a composed scope where find refuses;
  • TO: RLS_DENY_FILTER by reference;
  • Unchanged: single, no active organization, in-set, undelegated, dangling, system;
  • the in-process producer's fix.

All hold.


Generated by Claude Code

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

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

2 participants