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
[finding] A check on a select or delete row-level policy is accepted by RowLevelSecurityPolicySchema and never evaluated — and it does not suppress the using default either; no lint reports it #19965
Filed by the domain:spec seat 5 (session_01Sfe5YjBLwB9J3y8fvm2xq1, seat post #19357) from the out-of-scope findings of the #19953 dev (PR #19962, class c). ⛔ Filed unassigned and unlabelled: routing and grading are triage's. ⛔ Not a claim.
The defect
RowLevelSecurityPolicySchema (packages/spec/src/security/rls.zod.ts, authored in permission-set rowLevelSecurity) accepts a check on a policy whose operation is select or delete. That check is authored, stored and silently never evaluated.
Read by this seat on main3b5607019f: packages/plugins/plugin-security/src/security-plugin.ts:2997-3004 evaluates the post-image check only for insert and update operations.
Measured by the #19953 dev (the harness described in PR #19962's report):
A sole check of record.status != 'archived' on a select or delete policy admits every insert and update of archived.
Beside a USING-only all sibling, a check on a select or delete policy does not suppress the sibling's using default. Only a check on an insert / update / all policy does.
The dev found no lint rule in packages/lint/src that reads operation beside check.
This is declared, not enforced (ADR-0049). An author, often an AI, who writes a check on a delete policy believes the rule guards something.
Triage: lands in the RLS policy contract ⇒ domain:spec, security, bug, priority:p2, pm:blocked Blocked-by #19962 (open, same two files); rationale: RowLevelSecurityPolicySchema accepts a check on a policy whose operation is select or delete, the runtime evaluates check only on insert/update (security-plugin.ts:2997-3004), and no lint reads operation beside check — so an author (often an AI) who writes a check on a delete policy believes it guards something it never guards (ADR-0049 declared ≠ enforced; NORTH-STAR rule 4: refuse wrong metadata loudly).
卡面实测:一个只写了 check 的 select / delete 策略,对插入和更新 archived 一律放行;它也不会像 insert / update / all 策略上的 check 那样,取代同级策略的 using 默认。
为什么挂 pm:blocked
PR #19962(#19953)正在改 rls.zod.ts 的 check 描述,以及同一个 lint 文件,它的描述里已经写明"A check on a select or delete policy is never evaluated"。在它合并之前另开 PR,会改同样的两个文件。⇒ Blocked-by #19962,合并后立即可派发。另外,#19951 也排在这个 lint 文件上,注意先后顺序。
Acting on the maintainer's instruction. Provenance: who — the maintainer; verbatim — 「帮我排查一下 blocked 卡片,哪些需要更新状态帮我更新。」; where — the maintainer's chat with session session_013RWUA7bNq5bRhehLPqXwMg, 2026-09-24. ⛔ Not a claim and not a dispatch: the card returns to its lane's take order. Candidate surfaced by check-half-states.mjs H19 on this sweep.
Claim: PM loop round 1
Session: session_01QcAS3qiYYZNezaxZxaUdMV
Account: os-project-manager (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-19965-rls-check-select-delete-refused
Worktree: objectstack-issue-19965
Domain: domain:spec
Seat: domain:spec#2 (seat post #18549)
File surface: packages/spec/src/security/rls.zod.ts (the RowLevelSecurityPolicySchemacheck docblock/describe and its superRefine only) and packages/spec/src/security/rls.test.ts; packages/spec/src/migrations/ (one ADR-0087 entry plus the regenerated registry); any fixture, example or shipped policy that authors a check on a select / delete policy, if the census finds one; generated artefacts regenerated, never hand-edited; .changeset/. ⛔ Not packages/lint/src/validate-rls-predicate-enforceability.ts (open PR #19947, then #19951) (stop on breach; explain in the report)
Container & model: M, mode:subagent, model: default judgment tier (dispatch-gates.mjs --tier at 8d1f7ab: 「no path-derived mandate … floor · default · ceiling」, the default slot taken; built at the default tier, and reviewed at CONTRACT_REVIEW_TIER by an isolated subagent before enqueue, since this seat is off-tier)
Clause-②: no
Thread-read: 5824297747
Serial constraints cleared: read at 2026-09-27T02:23Z — Open-PR census (7 open PRs, 992 file rows): none touches rls.zod.ts or rls.test.ts. PR #19947 (#19886, seat 5) holds the sibling lint file validate-rls-predicate-enforceability.ts plus migrations/registry.ts, and this card stays off the lint file. registry.ts is also held by PR #19957 and PR #19637; that is ordinary concurrency, resolved by regeneration. In-flight claims: seat 1 (#19867 schemaless-node-config, #19543 api/automation + runtime + client, #19856 joined report) and seat 5 (#19938, #19886, #19731, #18459, #17707) name no rls.zod.ts region. Queue siblings on rls.zod.ts: #19967 (p3 docs) is NOT folded, because fold gate ① fails (stale prose vs declared-not-enforced refusal) ⇒ serial after this card. #19959 is pm:blocked.
Scope, restated from the dev contract: 「范围 = 这张 issue,别无其它。」 The work item is the one triage re-derived at 5824297747: refuse a check on a select / delete row-level policy at authoring time, with a prescription. ⛔ No lint transition.
{
"issue": 19965,
"status": "done",
"branch": "claude/issue-19965-rls-check-select-delete-refused",
"pr": "https://github.com/objectstack-ai/objectstack/pull/20167",
"session": "session_01QcAS3qiYYZNezaxZxaUdMV (mode:subagent, the PM's session; identity = the branch)",
"premise_still_valid": true,
"summary": "RowLevelSecurityPolicySchema's superRefine now refuses a non-blank `check` on a policy whose `operation` is `select` or `delete`: one `custom` issue at path ['check'] whose message begins \"`check` is never evaluated on a `select` policy:\" (or `delete`) and prescribes both rewrites (the predicate as `using`, or the `check` on an insert/update/all policy). A blank `check` is deliberately not refused (the runtime's policyDeclaresClause reads it as absent; the Studio editor leaves check: '' behind). The check docblock and .describe() sentence became the refusal; reference docs regenerated via gen:docs. Registered as ADR-0087 D3 semantic entry rls-check-on-select-or-delete-policy-refused (protocol 18, no D2 conversion); changeset @objectstack/spec minor with **BREAKING** banner, FROM -> TO table and the `registered` marker. Premise re-measured on origin/main 8d1f7ab: H1 holds (writeCheckPolicies at security-plugin.ts:889, step 3.6 gated insert||update at :3111-3112, getApplicablePolicies matches operation or all); H2 holds for shipped/authored metadata (census 9332 files / 264 select|delete literals / 2 with check, both test fixtures, both re-spelled: rls.test.ts:48 -> 'all', rls-reserved-membership-keys.test.ts:189 -> 'insert'; objectui pin f8a9d0fb05 and main 5c61e52: 0 hits); H3 proven through defineStack (STACK_SCHEMA_INVALID/422, sole issue at permissions.0.rowLevelSecurity.0.check), os validate and metadata save by code reading only; H4 measured (check-only policy gets this refusal, not 'At least one', and the prescribed rewrite parses); H5 registered. Clause-② line copied verbatim from the claim as `Clause-②: no`; the more precise spelling would carry the `(narrowing)` arm, not self-authored per contract; breaking-ness is carried by the **BREAKING** banner and check-adr-0087-registration reads it.",
"tests": "All at HEAD f25e34d. spec test: 540 files, 15809 passed, 2 todo (exit 0). spec typecheck exit 0. plugin-security test: 135 files, 2697 passed; typecheck exit 0. lint test: 108 files, 4144 passed. runtime test: 279 files, 3906 passed, 1 skipped. verify test: 15 files, 116 passed. cli unit tier (vitest --project unit): 222 files passed, 3128 tests passed, 29 skipped; 2 files NOT MEASURED (published-subpath-console.pin / published-subpath-hook-body.pin threw 'packages/cli is not built (./dist/index.js is absent)', a prerequisite, not a verdict); cli integration tier (migrate-meta.e2e, whose select policy carries no check) declared to CI. dogfood: census 0 hits; CI Dogfood Regression Gate 1/3, 2/3, 3/3 completed/success on f25e34d. Direction: these are 5 downstream consumers of @objectstack/spec (PM lead list), not the full '...@objectstack/spec' set. Ablation via scripts/ablation-replace.mjs on packages/spec/src/security/rls.zod.ts (subject imported from ./rls.zod src, no dist on the path, so no rebuild leg): anchor \"(data.operation === 'select' || data.operation === 'delete') &&\" x1 -> x0, 'false &&' x0 -> x1, blob 6b2ec57ec014 -> e59c5d3f1e8c; rls.test.ts 6 failed / 52 passed (exactly the two per-op refusal pins, the two check-only pins, the permission-set reach pin and the defineStack reach pin); restore blob == HEAD 6b2ec57ec014, git diff HEAD empty; post-restore 58/58. Observed direction: turned red (as predicted). eslint narrowed: 5 of 5 changed .ts files linted (population: eslint.config.mjs block files '**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}'; count from --format json: 5 results), 0 errors 0 warnings; invariance: eslint.config.mjs enables no type-aware linting (no parserOptions.project), so the diff cannot move a verdict on an untouched file.",
"gates": "dispatch-gates --repo objectstack-ai/objectstack --commands at f25e34d: 115 derived. Ran 38, every one exit 0, recorded with ':: exit N'; --ran reconciliation: '115 derived, 38 run, 0 NOT-MEASURED, 77 UNRUN' (exit 1 by design: the 77 root check:* families are the declared narrowing left to CI per the dispatch's 'do not enumerate the whole farm'). Ran: all 22 derived @objectstack/spec gates (check:generated '✓ All 15 generated artifacts are up to date'; check:api-surface 'public API surface + factory signatures unchanged ✓'; check:docs '✅ 226 generated files in sync'; check:liveness '✓ ... state-counts.md is current'; check:strictness-ledger '✓ ... is current'; check:authorable-surface, spec-changes, upgrade-guide, migration-registry '✓ ... is current (247 semantic ...)', skill-examples '✅ 259 prose examples type-check' (first run exit 3 PREREQUISITE NOT MET: client-react unbuilt; rebuilt and re-run exit 0), plus the rest); check-adr-0087-registration --base origin/main '✓ ... 1 declared-breaking changeset(s), each carrying an ADR-0087 disposition.' and --self-test; check-changeset-no-major --base origin/main; check-empty-changeset --base origin/main; check-spec-docblock-symbol-anchors; docs-audit/check-affected-docs; lint check:doc-formula-expressions, check:doc-security-posture; pnpm check:nul-bytes 'OK (scanned 9559 ...)', check:issue-citations, check:doc-authoring, check:cross-package-test-inputs, check:test-source-alias, check:spec-parsed-alias, check:docs-spec-enumerations, check:changeset-gate-self-tests. CI on f25e34d at report time: Build Core, Check Changeset, Dogfood Regression Gate success; Lint & Repo Gates and Test Core shards in_progress (not awaited).",
"line_budget": "n/a (no skills/** in the diff)",
"deviations": "None from the claim's file surface. Two test-only fixtures outside packages/spec were re-spelled (rls-reserved-membership-keys.test.ts), inside the claim's 'any fixture that authors a check on a select/delete policy' clause. Local verification narrowed as declared (77 derived root gate families left to CI). os validate and live metadata-save doors not run (reach by code reading only).",
"files_changed": [
".changeset/19965-rls-check-on-select-or-delete-refused.md",
"content/docs/references/security/permission.mdx (gen:docs)",
"content/docs/references/security/rls.mdx (gen:docs)",
"packages/plugins/plugin-security/src/rls-reserved-membership-keys.test.ts",
"packages/spec/src/migrations/entries/semantic/18.rls-check-on-select-or-delete-policy-refused.ts",
"packages/spec/src/migrations/registry.ts (gen:migration-registry)",
"packages/spec/src/security/rls.test.ts",
"packages/spec/src/security/rls.zod.ts"
],
"mcp_calls": "0",
"api_writes": "3 relay strokes (each one POST /repos/objectstack-ai/objectstack/dispatches executed by fleet-write.yml as objectstack-fleet[bot]): pr_create -> POST /repos/objectstack-ai/objectstack/pulls (draft, PR 20167); assign via label-write.mjs -> POST /repos/objectstack-ai/objectstack/issues/20167/assignees (os-project-manager, read back MATCHES); comment -> POST /repos/objectstack-ai/objectstack/issues/19965/comments (this report). Plus 3 git pushes (not REST). No label added (skip-changeset criterion does not fire: the published spec changes).",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none (承接者:无) · noted, not filed — objectui Studio RLS editor (packages/app-shell/src/views/metadata-admin/PermissionAdvancedFacets.tsx at pin f8a9d0fb05 and main 5c61e52) renders a `check` input for every operation; its client validation parses @objectstack/spec/security PermissionSetSchema, so the refusal reaches the editor on the next spec bump; hiding the input on select/delete is polish.",
"carrier: none (承接者:无) · noted, not filed — the published JSON Schema cannot express this cross-field superRefine refusal; the `check` description now states it.",
"carrier: the release compile (release-owned) · noted, not filed — pending .changeset/19953-rls-check-default-composition-text.md still reads 'A `check` on a `select` or `delete` policy is never evaluated' and ships in the same train as this refusal; left untouched (another PR's changeset).",
"carrier: none (承接者:无) · noted, not filed — the `using` docblock says USING is required for SELECT/UPDATE/DELETE; after this change that holds for select/delete (check refused + at-least-one rule) but not update, where a check-only policy is legal and enforced; prose drift."
]
}
ACCEPT — PR #20167 at head f25e34d08bef76fd97d88c4d4ff90dcee4f05863 · domain:spec seat 2 (session_01QcAS3qiYYZNezaxZxaUdMV) · 2026-09-27T04:42Z
Seat review against GitHub, not the report:
Shape: draft, base main, first body line Fixes #19965. That is the only closing keyword in the body, and no other card number appears. Clause-②: no is line-leading. The PR assignee is os-project-manager.
Scope: 8 files, all inside claim 5851942911's surface: rls.zod.ts and rls.test.ts, one ADR-0087 semantic entry plus the regenerated registry.ts, two gen:docs reference pages, the changeset, and one plugin-security test fixture re-spelled select → insert, which the claim's fixture clause covers. No content/docs/releases/. The changeset is @objectstack/spec minor with a BREAKING banner and the registered marker.
Spot reading of the diff: the superRefine refuses a non-blank check when operation is select / delete, at path check, with both rewrites in the message. The blank-check exemption follows the runtime's own blank-clause-is-absent reading, and the diff comment and the changeset both say so. Ruling: none; triage direction 5813132452 executed, with no lint transition.
CI on this head at review time: 26 success, 2 skipped, 5 in progress (Lint & Repo Gates, Type Check · workspace, Test Core 1/3/5). ⛔ Not ready until both required jobs and every check read success on the head.
Clause-② gate: the path limb fires (packages/spec/src/** non-test), so an at-tier contract review record is owed before enqueue. It runs next, in an isolated subagent (this seat is off-tier). needs:contract-review goes on the PR in this act.
Out-of-scope findings (the dev's four):
Studio RLS editor shows check for every operation → Acceptance notes (polish; the refusal reaches the editor through PermissionSetSchema on the next spec bump).
JSON Schema cannot carry the cross-field refusal → Acceptance notes.
Pending .changeset/19953-rls-check-default-composition-text.md says a select/delete check 「is never evaluated」 → dropped. It stays true of the released behaviour, and it is another PR's changeset.
Landing record — PR #20167 merged through the merge queue at 2026-09-27T05:28Z as b276d4463f · domain:spec seat 2 (session_01QcAS3qiYYZNezaxZxaUdMV) · 2026-09-27T05:29Z
Verified by content on origin/mainb276d4463f, not by the merged field:
git grep -c "is never evaluated on a" -- packages/spec/src/security/rls.zod.ts → 1 (the refusal message);
Activity
objectstack-fleet commentedon Sep 24, 2026
Blocked-by: #19962
分诊首次定级:
priority:p2·security·bug·domain:spec·pm:blocked—— 写在select/delete策略上的check被接受却从不生效,也没有 lint 报出Path:
packages/spec/src/security/rls.zod.ts(RowLevelSecurityPolicySchema)+packages/lint/src/validate-rls-predicate-enforceability.ts—— 两处都正被 PR #19962(#19953)修改Triage: lands in the RLS policy contract ⇒
domain:spec,security,bug,priority:p2,pm:blockedBlocked-by #19962 (open, same two files); rationale:RowLevelSecurityPolicySchemaaccepts acheckon a policy whoseoperationisselectordelete, the runtime evaluatescheckonly on insert/update (security-plugin.ts:2997-3004), and no lint readsoperationbesidecheck— so an author (often an AI) who writes acheckon adeletepolicy believes it guards something it never guards (ADR-0049 declared ≠ enforced; NORTH-STAR rule 4: refuse wrong metadata loudly).分诊席(
session_01Tw7jnJinGHvoGSi8aFkhPJ,座位贴 #6015),2026-09-24T11:25Z。⛔ 不认领、不派发。本席读完了卡面(本卡尚无评论),并核对了 PR #19962 的改动文件。定级说明
check本来就只对写入的新行有意义。但作者会以为"删除受到这条规则保护",实际上删除不受它约束。行级安全规则上的"写了等于没写",属于安全相关的误配置,应当在编写时就明确拒绝。check的select/delete策略,对插入和更新archived一律放行;它也不会像insert/update/all策略上的check那样,取代同级策略的using默认。为什么挂
pm:blockedPR #19962(#19953)正在改
rls.zod.ts的check描述,以及同一个 lint 文件,它的描述里已经写明"Acheckon aselectordeletepolicy is never evaluated"。在它合并之前另开 PR,会改同样的两个文件。⇒ Blocked-by #19962,合并后立即可派发。另外,#19951 也排在这个 lint 文件上,注意先后顺序。方向(默认)
在编写期拒绝:
operation为select/delete时出现check,schema 按名拒绝并给出处方(删除时用using限定可删的行)。这是收窄已接受的写法,按仓库规则判断 Clause-② 并登记 ADR-0087。在此之前可以先由 lint 报出警告,作为过渡。Generated by Claude Code
objectstack-fleet commentedon Sep 24, 2026
Unblock re-derivation:
pm:blocked→pm:queue· 2026-09-24T23:59ZActing on the maintainer's instruction. Provenance: who — the maintainer; verbatim — 「帮我排查一下 blocked 卡片,哪些需要更新状态帮我更新。」; where — the maintainer's chat with session
session_013RWUA7bNq5bRhehLPqXwMg, 2026-09-24. ⛔ Not a claim and not a dispatch: the card returns to its lane's take order. Candidate surfaced bycheck-half-states.mjsH19 on this sweep.completedon 2026-09-24 (landed asb5853da).5813132452names exactly that condition (samerls.zod.tsand lint file).checkon aselectordeleterow-level policy is accepted byRowLevelSecurityPolicySchemaand never evaluated — and it does not suppress theusingdefault either; no lint reports it #19965 among the cards filed from the measurement, not as fixed.checkon aselect/deleterow-level policy at authoring time.State:
pm:blocked→pm:queue;domain:spec,priority:p2,securityunchanged; no assignee.Generated by Claude Code
objectstack-fleet commentedon Sep 27, 2026
Claim: PM loop round 1
Session:
session_01QcAS3qiYYZNezaxZxaUdMVAccount:
os-project-manager(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-19965-rls-check-select-delete-refusedWorktree:
objectstack-issue-19965Domain:
domain:specSeat:
domain:spec#2(seat post #18549)File surface:
packages/spec/src/security/rls.zod.ts(theRowLevelSecurityPolicySchemacheckdocblock/describe and itssuperRefineonly) andpackages/spec/src/security/rls.test.ts;packages/spec/src/migrations/(one ADR-0087 entry plus the regenerated registry); any fixture, example or shipped policy that authors acheckon aselect/deletepolicy, if the census finds one; generated artefacts regenerated, never hand-edited;.changeset/. ⛔ Notpackages/lint/src/validate-rls-predicate-enforceability.ts(open PR #19947, then #19951) (stop on breach; explain in the report)Container & model:
M,mode:subagent,model: default judgment tier(dispatch-gates.mjs --tierat8d1f7ab: 「no path-derived mandate … floor · default · ceiling」, the default slot taken; built at the default tier, and reviewed atCONTRACT_REVIEW_TIERby an isolated subagent before enqueue, since this seat is off-tier)Clause-②: no
Thread-read: 5824297747
Serial constraints cleared: read at 2026-09-27T02:23Z —
Open-PR census (7 open PRs, 992 file rows): none touches rls.zod.ts or rls.test.ts. PR #19947 (#19886, seat 5) holds the sibling lint file validate-rls-predicate-enforceability.ts plus migrations/registry.ts, and this card stays off the lint file. registry.ts is also held by PR #19957 and PR #19637; that is ordinary concurrency, resolved by regeneration. In-flight claims: seat 1 (#19867 schemaless-node-config, #19543 api/automation + runtime + client, #19856 joined report) and seat 5 (#19938, #19886, #19731, #18459, #17707) name no rls.zod.ts region. Queue siblings on rls.zod.ts: #19967 (p3 docs) is NOT folded, because fold gate ① fails (stale prose vs declared-not-enforced refusal) ⇒ serial after this card. #19959 is pm:blocked.Scope, restated from the dev contract: 「范围 = 这张 issue,别无其它。」 The work item is the one triage re-derived at
5824297747: refuse acheckon aselect/deleterow-level policy at authoring time, with a prescription. ⛔ No lint transition.objectstack-fleet commentedon Sep 27, 2026
os-dev-report
{ "issue": 19965, "status": "done", "branch": "claude/issue-19965-rls-check-select-delete-refused", "pr": "https://github.com/objectstack-ai/objectstack/pull/20167", "session": "session_01QcAS3qiYYZNezaxZxaUdMV (mode:subagent, the PM's session; identity = the branch)", "premise_still_valid": true, "summary": "RowLevelSecurityPolicySchema's superRefine now refuses a non-blank `check` on a policy whose `operation` is `select` or `delete`: one `custom` issue at path ['check'] whose message begins \"`check` is never evaluated on a `select` policy:\" (or `delete`) and prescribes both rewrites (the predicate as `using`, or the `check` on an insert/update/all policy). A blank `check` is deliberately not refused (the runtime's policyDeclaresClause reads it as absent; the Studio editor leaves check: '' behind). The check docblock and .describe() sentence became the refusal; reference docs regenerated via gen:docs. Registered as ADR-0087 D3 semantic entry rls-check-on-select-or-delete-policy-refused (protocol 18, no D2 conversion); changeset @objectstack/spec minor with **BREAKING** banner, FROM -> TO table and the `registered` marker. Premise re-measured on origin/main 8d1f7ab: H1 holds (writeCheckPolicies at security-plugin.ts:889, step 3.6 gated insert||update at :3111-3112, getApplicablePolicies matches operation or all); H2 holds for shipped/authored metadata (census 9332 files / 264 select|delete literals / 2 with check, both test fixtures, both re-spelled: rls.test.ts:48 -> 'all', rls-reserved-membership-keys.test.ts:189 -> 'insert'; objectui pin f8a9d0fb05 and main 5c61e52: 0 hits); H3 proven through defineStack (STACK_SCHEMA_INVALID/422, sole issue at permissions.0.rowLevelSecurity.0.check), os validate and metadata save by code reading only; H4 measured (check-only policy gets this refusal, not 'At least one', and the prescribed rewrite parses); H5 registered. Clause-② line copied verbatim from the claim as `Clause-②: no`; the more precise spelling would carry the `(narrowing)` arm, not self-authored per contract; breaking-ness is carried by the **BREAKING** banner and check-adr-0087-registration reads it.", "tests": "All at HEAD f25e34d. spec test: 540 files, 15809 passed, 2 todo (exit 0). spec typecheck exit 0. plugin-security test: 135 files, 2697 passed; typecheck exit 0. lint test: 108 files, 4144 passed. runtime test: 279 files, 3906 passed, 1 skipped. verify test: 15 files, 116 passed. cli unit tier (vitest --project unit): 222 files passed, 3128 tests passed, 29 skipped; 2 files NOT MEASURED (published-subpath-console.pin / published-subpath-hook-body.pin threw 'packages/cli is not built (./dist/index.js is absent)', a prerequisite, not a verdict); cli integration tier (migrate-meta.e2e, whose select policy carries no check) declared to CI. dogfood: census 0 hits; CI Dogfood Regression Gate 1/3, 2/3, 3/3 completed/success on f25e34d. Direction: these are 5 downstream consumers of @objectstack/spec (PM lead list), not the full '...@objectstack/spec' set. Ablation via scripts/ablation-replace.mjs on packages/spec/src/security/rls.zod.ts (subject imported from ./rls.zod src, no dist on the path, so no rebuild leg): anchor \"(data.operation === 'select' || data.operation === 'delete') &&\" x1 -> x0, 'false &&' x0 -> x1, blob 6b2ec57ec014 -> e59c5d3f1e8c; rls.test.ts 6 failed / 52 passed (exactly the two per-op refusal pins, the two check-only pins, the permission-set reach pin and the defineStack reach pin); restore blob == HEAD 6b2ec57ec014, git diff HEAD empty; post-restore 58/58. Observed direction: turned red (as predicted). eslint narrowed: 5 of 5 changed .ts files linted (population: eslint.config.mjs block files '**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}'; count from --format json: 5 results), 0 errors 0 warnings; invariance: eslint.config.mjs enables no type-aware linting (no parserOptions.project), so the diff cannot move a verdict on an untouched file.", "gates": "dispatch-gates --repo objectstack-ai/objectstack --commands at f25e34d: 115 derived. Ran 38, every one exit 0, recorded with ':: exit N'; --ran reconciliation: '115 derived, 38 run, 0 NOT-MEASURED, 77 UNRUN' (exit 1 by design: the 77 root check:* families are the declared narrowing left to CI per the dispatch's 'do not enumerate the whole farm'). Ran: all 22 derived @objectstack/spec gates (check:generated '✓ All 15 generated artifacts are up to date'; check:api-surface 'public API surface + factory signatures unchanged ✓'; check:docs '✅ 226 generated files in sync'; check:liveness '✓ ... state-counts.md is current'; check:strictness-ledger '✓ ... is current'; check:authorable-surface, spec-changes, upgrade-guide, migration-registry '✓ ... is current (247 semantic ...)', skill-examples '✅ 259 prose examples type-check' (first run exit 3 PREREQUISITE NOT MET: client-react unbuilt; rebuilt and re-run exit 0), plus the rest); check-adr-0087-registration --base origin/main '✓ ... 1 declared-breaking changeset(s), each carrying an ADR-0087 disposition.' and --self-test; check-changeset-no-major --base origin/main; check-empty-changeset --base origin/main; check-spec-docblock-symbol-anchors; docs-audit/check-affected-docs; lint check:doc-formula-expressions, check:doc-security-posture; pnpm check:nul-bytes 'OK (scanned 9559 ...)', check:issue-citations, check:doc-authoring, check:cross-package-test-inputs, check:test-source-alias, check:spec-parsed-alias, check:docs-spec-enumerations, check:changeset-gate-self-tests. CI on f25e34d at report time: Build Core, Check Changeset, Dogfood Regression Gate success; Lint & Repo Gates and Test Core shards in_progress (not awaited).", "line_budget": "n/a (no skills/** in the diff)", "deviations": "None from the claim's file surface. Two test-only fixtures outside packages/spec were re-spelled (rls-reserved-membership-keys.test.ts), inside the claim's 'any fixture that authors a check on a select/delete policy' clause. Local verification narrowed as declared (77 derived root gate families left to CI). os validate and live metadata-save doors not run (reach by code reading only).", "files_changed": [ ".changeset/19965-rls-check-on-select-or-delete-refused.md", "content/docs/references/security/permission.mdx (gen:docs)", "content/docs/references/security/rls.mdx (gen:docs)", "packages/plugins/plugin-security/src/rls-reserved-membership-keys.test.ts", "packages/spec/src/migrations/entries/semantic/18.rls-check-on-select-or-delete-policy-refused.ts", "packages/spec/src/migrations/registry.ts (gen:migration-registry)", "packages/spec/src/security/rls.test.ts", "packages/spec/src/security/rls.zod.ts" ], "mcp_calls": "0", "api_writes": "3 relay strokes (each one POST /repos/objectstack-ai/objectstack/dispatches executed by fleet-write.yml as objectstack-fleet[bot]): pr_create -> POST /repos/objectstack-ai/objectstack/pulls (draft, PR 20167); assign via label-write.mjs -> POST /repos/objectstack-ai/objectstack/issues/20167/assignees (os-project-manager, read back MATCHES); comment -> POST /repos/objectstack-ai/objectstack/issues/19965/comments (this report). Plus 3 git pushes (not REST). No label added (skip-changeset criterion does not fire: the published spec changes).", "open_questions": [], "out_of_scope_findings": [ "carrier: none (承接者:无) · noted, not filed — objectui Studio RLS editor (packages/app-shell/src/views/metadata-admin/PermissionAdvancedFacets.tsx at pin f8a9d0fb05 and main 5c61e52) renders a `check` input for every operation; its client validation parses @objectstack/spec/security PermissionSetSchema, so the refusal reaches the editor on the next spec bump; hiding the input on select/delete is polish.", "carrier: none (承接者:无) · noted, not filed — the published JSON Schema cannot express this cross-field superRefine refusal; the `check` description now states it.", "carrier: the release compile (release-owned) · noted, not filed — pending .changeset/19953-rls-check-default-composition-text.md still reads 'A `check` on a `select` or `delete` policy is never evaluated' and ships in the same train as this refusal; left untouched (another PR's changeset).", "carrier: none (承接者:无) · noted, not filed — the `using` docblock says USING is required for SELECT/UPDATE/DELETE; after this change that holds for select/delete (check refused + at-least-one rule) but not update, where a check-only policy is legal and enforced; prose drift." ] }Generated by Claude Code
objectstack-fleet commentedon Sep 27, 2026
ACCEPT — PR #20167 at head
f25e34d08bef76fd97d88c4d4ff90dcee4f05863·domain:specseat 2 (session_01QcAS3qiYYZNezaxZxaUdMV) · 2026-09-27T04:42ZSeat review against GitHub, not the report:
main, first body lineFixes #19965. That is the only closing keyword in the body, and no other card number appears.Clause-②: nois line-leading. The PR assignee isos-project-manager.5851942911's surface:rls.zod.tsandrls.test.ts, one ADR-0087 semantic entry plus the regeneratedregistry.ts, twogen:docsreference pages, the changeset, and one plugin-security test fixture re-spelledselect→insert, which the claim's fixture clause covers. Nocontent/docs/releases/. The changeset is@objectstack/specminor with a BREAKING banner and theregisteredmarker.superRefinerefuses a non-blankcheckwhenoperationisselect/delete, at pathcheck, with both rewrites in the message. The blank-checkexemption follows the runtime's own blank-clause-is-absent reading, and the diff comment and the changeset both say so. Ruling: none; triage direction5813132452executed, with no lint transition.Lint & Repo Gates,Type Check · workspace,Test Core1/3/5). ⛔ Not ready until both required jobs and every check readsuccesson the head.packages/spec/src/**non-test), so an at-tier contract review record is owed before enqueue. It runs next, in an isolated subagent (this seat is off-tier).needs:contract-reviewgoes on the PR in this act.Out-of-scope findings (the dev's four):
checkfor every operation → Acceptance notes (polish; the refusal reaches the editor throughPermissionSetSchemaon the next spec bump)..changeset/19953-rls-check-default-composition-text.mdsays a select/deletecheck「is never evaluated」 → dropped. It stays true of the released behaviour, and it is another PR's changeset.usingdocblock's 「For SELECT/UPDATE/DELETE operations, USING is required」 is false forupdate→ carrier [finding] After PR #19962 rewrites the RLScheckdescribe, three texts still quote the old "defaults to USING clause if not specified" contract, and theusingtext never says it is the insert check when nocheckis declared #19967 (the same family: that card already holds theusingtext), pointer left there.objectstack-fleet commentedon Sep 27, 2026
Landing record — PR #20167 merged through the merge queue at 2026-09-27T05:28Z as
b276d4463f·domain:specseat 2 (session_01QcAS3qiYYZNezaxZxaUdMV) · 2026-09-27T05:29Zorigin/mainb276d4463f, not by themergedfield:git grep -c "is never evaluated on a" -- packages/spec/src/security/rls.zod.ts→ 1 (the refusal message);git grep -c "rls-check-on-select-or-delete-policy-refused" -- packages/spec/src/migrations/registry.ts→ 1 (the ADR-0087 entry);writeCheckPolicies|policyDeclaresClauseinsecurity-plugin.ts→ 9.completedbyFixes #19965.pm:dispatchedwas stripped in this act, anddomain:spec,priority:p2,securityandbugstay.checkdescribe, three texts still quote the old "defaults to USING clause if not specified" contract, and theusingtext never says it is the insert check when nocheckis declared #19967 (named in comments only) is still open.usingdocblock drift is on [finding] After PR #19962 rewrites the RLScheckdescribe, three texts still quote the old "defaults to USING clause if not specified" contract, and theusingtext never says it is the insert check when nocheckis declared #19967, and the Studio editor polish is in the PR's Acceptance notes.fix(spec, lint): state the RLS check default per operation across the…
feat(spec)!: refuse a `check` on a `select` / `delete` row-level secu…