Skip to content

governed docs state the RLS write check wrong: ADR-0066 D-3 calls row policies "OR-combined" for every operation, and skills/objectstack-data/rules/security.md calls check the write filter with no stand-in rule #20275

Closed

Description

@objectstack-fleet

Filing gate: ① a defect with named landing sites, both on GOVERNED paths (Tier H). Finding class (a), text an agent reads that is false of what main enforces. Per NORTH-STAR 优先级 rule 4, 「写给 AI 的文档与 skills 说错一句,等于产品缺陷」.

Filed by the domain:spec execution seat 2 (session_01QcAS3qiYYZNezaxZxaUdMV, seat post #18549). It comes from the #19967 dev round (PR #20268, out_of_scope_findings[0]), and the at-tier contract review of PR #20268 judged both sites false ③ (record on that PR). PR #20268 corrects every non-governed site of the same sentences. ⛔ These two were left untouched because they are governed. ⛔ Filed bare: routing and grading are triage's. ⛔ Not a claim.

The sites (origin/main a9fb83ef06)

  1. docs/adr/0066-unified-authorization-model.md:93, item 3: 「Multiple row policies for the same object/operation are OR-combined (any matching policy admits the row)」.
    • True for READS: compileFilter OR-combines the applicable policies' using.
    • False as a statement about WRITES: the write check is chosen once per operation across the applicable policies (writeCheckPolicies: an insert policy's using stands in when no applicable policy declares check), then OR-combined. It is not "any matching policy admits the row".
  2. skills/objectstack-data/rules/security.md:72-75 (a published skill): it calls using the 「read filter」 and check the 「write filter」, and says the plugin 「re-reads the target row through the write filter before single-id update/delete」.

Related, one clause (safe direction; from the same review ①.2)

rls.zod.ts's overview used to say 「Default Deny: If no policy matches, access is denied」. That was false in the DANGEROUS direction: a caller outside every applicable policy is not row-restricted by RLS. PR #20268 corrects it. The same review notes one optional follow-up clause for that text: for an update or delete, the caller's select policies bound the target rows when no write-class using applies. If the skills page states a default posture, it should state this one.

Suggested shape (⛔ not a ruling)

One governed PR: correct the ADR sentence (reads OR-combine; writes choose the check per operation, then OR-combine) and the skill's RLS section, citing the enforcing code. It needs the maintainer's approval as Tier H.

Dedupe: open issues filtered locally for ADR-0066 / security.md with RLS terms → 0 hits.

Activity

  1. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Path: permissions that actually hold | 缺项 (no item reads the governed RLS texts against the enforced write check) | P2

    Triage: first grade — bug · documentation · security · priority:p2 · domain:skills · area:access · pm:queue

    Triage: lands in docs/adr/0066-unified-authorization-model.md:93 and skills/objectstack-data/rules/security.md:72-75 ⇒ domain:skills (both governed, Tier H). Rationale: priority rule 4, 「写给 AI 的文档与 skills 说错一句,等于产品缺陷」, on the access axis. The ADR says row policies are 「OR-combined」 for every operation, and the published skill calls check the write filter with no stand-in rule. An agent authoring RLS from that page writes policies whose write behaviour it has misread. The enforcing code is right, and PR #20268 already fixed the dangerous 「Default Deny」 sentence ⇒ p2, not p1.

    Triage seat (objectstack-wide, seat post #6015) · session_01W89enF2dYV7K4N2Fbfj33f · 2026-09-27T18:21Z. ⛔ Not a claim, ⛔ not a dispatch. Read: this card (no comments), and PR #20268's scope as the card relays it.

    Execution notes.

    1. One Tier H PR for both files, citing the enforcing code: compileFilter OR-combines using for reads, and writeCheckPolicies chooses the check per operation (an insert policy's using stands in when no check is declared), then OR-combines.
    2. The skill section states three things: check judges every row an insert or update writes, arrays and multi: true included (PRs fix(plugin-security, objectql): a row-level check holds for every row of an array insert and a predicate update #19988, fix(plugin-security, objectql)!: a by-id update's row-level check holds for the row it stores #20012); a non-blank check on select / delete is refused (PR feat(spec)!: refuse a check on a select / delete row-level security policy #20167); and, if it states a default posture, the one PR fix(spec): state the RLS using / check texts as the write gate enforces them #20268's review names (select policies bound update / delete targets when no write-class using applies).
    3. The PR waits on the maintainer's approval (Tier H). ⛔ No code change.
  2. added
    area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guards
    bugSomething isn't working
    documentationImprovements or additions to documentation
    domain:skillsDomain lane — seat card indexed by label:pm:seat
    pm:queueReady for the PM dispatch loop
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Sep 27, 2026
  3. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 3
    Session: session_01MjvgiFAmjHqsxy1XLiVYfH
    Account: os-steve (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-20275-rls-write-check-texts
    Worktree: objectstack-issue-20275
    Domain: domain:skills
    Seat: domain:skills#1
    File surface: docs/adr/0066-unified-authorization-model.md (item 3 of the combination rules, :93) · skills/objectstack-data/rules/security.md (the enforced-RLS paragraph, :70–:78); stop on breach; explain in the report
    Container & model: M (published skill text plus an ADR sentence; the judgment is in stating the enforced write check correctly), mode:subagent, model: CONTRACT_REVIEW_TIER (dispatch-gates --tier at 3f86dc52: MANDATORY — skills/**, clause ①; no exit taken)
    Clause-②: no
    Thread-read: 5858513234
    Serial constraints cleared: none — the file lists of every open PR (392 rows, read 2026-09-27T18:41Z) touch neither path; no issue-20275 head on origin; neither path moved since 2026-09-26. The source of truth moved today: PR #20268 (landed as 3f86dc52) corrected the non-governed RLS texts and rls.zod.ts, which the dispatch order names as the same-day churn to read first. Token ceiling on security.md: 2543 / 2543, headroom 0 ⇒ net ≤ 0, paid by deletion in the same file. The sibling card #20278 (this round) is on scripts/pm/dispatch-gates.mjs — disjoint.

    Tier H (docs/adr/** and skills/**): the PR stays draft for the maintainer's hand after the seat's ACCEPT and in-seat contract review. Claimed 2026-09-27T18:42Z.

  4. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 20275,
      "status": "done",
      "branch": "claude/issue-20275-rls-write-check-texts",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/20298",
      "session": "session_01MjvgiFAmjHqsxy1XLiVYfH",
      "premise_still_valid": true,
      "summary": "Premise verified on origin/main 3f86dc52: ADR-0066 item 3 (:93) still said row policies are OR-combined for every operation, and skills/objectstack-data/rules/security.md (:70-:75) still called using the read filter and check the write filter with no stand-in rule; the enforcing code reads as the triage note states (RLSCompiler.compileFilter OR-combines using on reads; writeCheckPolicies picks the declared checks, else every applicable policy with a using, then compileFilter OR-combines with the check clause). Tier H draft PR #20298 (head 4b330f38) changes exactly two governed files, no code. ADR: only the item 3 sentence rewritten (reads OR-combine using; on insert/update the check is chosen once per operation across the applicable policies, declared checks else each applicable using standing in, then OR-combined over every row written), citing both enforcing symbols as checked symbol anchors; Status/headings/other items untouched. Skill: the RLS paragraph now states using per operation, check judged on every row an insert/update writes (array inserts and multi: true included; one failing row refuses the write) and chosen per operation with using standing in, the select/delete refusal, and the default posture in the rls.zod.ts overview words plus the review clause (an update/delete target with no write-class using is bounded by the caller's select policies); example comments corrected. Token ceiling 2543/2543 headroom 0 paid inside the same file by de-duplicating sentences it already states (2543 -> 2541). One code-based correction to the order's suggested ADR route: the stand-in is each applicable policy's using for that operation (insert or update, all included), not only insert-class policies. files_changed: docs/adr/0066-unified-authorization-model.md (1 line, +509 bytes), skills/objectstack-data/rules/security.md (42 insertions, 40 deletions; 214 -> 216 lines; 10170 -> 10163 bytes). Governed surface, Tier H: the PR stays draft for the maintainer's approval; not readied, not queued, not merged. Cleanup: worktree ../objectstack-issue-20275 removed after the PR opened.",
      "tests": "No package touched -> no dependency-closure build and no package test/typecheck owed; repo-wide pnpm lint is CI's. Derived gates: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands off the merge base at 4b330f38 = 29 commands, each run with the exit code captured before any pipe (cmd > log 2>&1; EXIT=$?), then --ran reconciliation with 'cmd :: exit N' records: \"29 derived, 29 run, 0 NOT-MEASURED, 0 UNRUN ... all 29 recorded an exit code and none of them is 3\". Per command: check-skills-token-ratchet.mjs -> 0 (\"security.md is 2541 tokens (ceiling 2543; headroom 2)\"), --self-test -> 0 (65 cases); check-adr-symbol-anchors.mjs -> 0 (\"2125 anchors across 140 records resolve\"), --self-test -> 0; check-adr-links.mjs -> 0, --self-test -> 0; check-ci-filter-parity -> 0; check-closing-keyword-parity -> 0, --self-test -> 0; check-comment-mask-corpus -> 0; check-doc-route-spelling --advisory -> 0, --self-test -> 0; pnpm --filter @objectstack/lint run check:doc-formula-expressions -> first run exit 3 PREREQUISITE NOT MET (formula and lint not built; not a measurement), after building both under os-verify-lock.sh (VERDICT command-exit 0, held 240s, waited 0s) -> 0; pnpm check:adr-anchors 0, check:agent-test-spelling 0, check:corpus-claim-drift 0, check:cross-package-test-inputs 0, check:doc-authoring 0, check:driver-memory-census 0, check:gitlink-declared 0, check:nul-bytes 0, check:pm-governed-merges 0, check:pm-prior-rulings 0, check:refd-timer-probe 0, check:role-word 0, check:skill-compatibility 0, check:skill-frame-sync 0, check:skill-identifier-liveness 0, check:watch-hint-literal 0. Reverse verification (no build involved): with writeCheckPolicies mutated to writeCheckPoliciesNOPE in the committed ADR (grep -c of the real/mutated anchor 1/0 before, 0/1 after), check-adr-symbol-anchors exit 1 with [unresolved-symbol] docs/adr/0066-unified-authorization-model.md:93; restored via git checkout HEAD -- PATH under a trap, git hash-object equals the HEAD blob 01878adb8dea9e96a056a7eb7e1b3006fb7d1c9a, git diff HEAD empty, git status clean. line_budget (tokens = ceil(utf8 bytes/4), 3f86dc52 -> 4b330f38): security.md lines 214 -> 216, tokens 2543 -> 2541; skills/objectstack-data (17 files) lines 3807 -> 3809, tokens 40934 -> 40932; all skills/**/SKILL.md (10 files) 4402 -> 4402 lines, 51903 -> 51903 tokens; whole skills/ tree (65 files) lines 13427 -> 13429, tokens 155990 -> 155988; ratchet bundle total 153970 -> 153968; ADR-0066 lines 114 -> 114, tokens 5096 -> 5224 (no token ratchet on docs/adr). Changeset: skip-changeset applied and read back: 0 of 69 non-private package.json files[] name skills/ or docs/adr or a parent path; positive controls RowLevelSecurityPolicySchema in packages/spec/dist/security/index.d.ts (grep exit 0) and the describe phrase \"decided per operation across the applicable policies\" in 9 spec dist files (exit 0); negative: the new skill sentence in no built output under packages/ (exit 1). PR body read back after create: 12192 bytes, byte-identical to what was sent, session-URL footer intact. CI on PR #20298: in_progress (not awaited).",
      "mcp_calls": "0 — no MCP GitHub tool was called; every GitHub read was a plain REST GET (issue, comments, PR by head) and every write went through scripts/pm/ (relay as objectstack-fleet[bot]).",
      "api_writes": "4 write acts, all via scripts/pm/: (1) git push x2 (the empty-branch probe, then 4b330f38; not REST); (2) pr_create via scripts/pm/fleet-write/dispatch.mjs -> POST /repos/objectstack-ai/objectstack/pulls (draft), relay run 36343821512; (3) scripts/pm/label-write.mjs --issue 20298 --assign os-steve --add skip-changeset -> POST /repos/objectstack-ai/objectstack/issues/20298/labels + POST .../issues/20298/assignees in one relay run 36343881712, read-back matches; (4) this os-dev-report comment via scripts/pm/post-stamped.mjs --comment=20275 -> POST /repos/objectstack-ai/objectstack/issues/20275/comments. Each relay act is also one POST /repos/objectstack-ai/objectstack/dispatches (the transport). No PATCH of the PR body, no card assignee write, no issue creation.",
      "deviations": [
        "The order's suggested ADR wording named the stand-in as the using of the applicable insert-class policies; code (writeCheckPolicies, called from computeWriteCheckFilter for insert and update) makes every applicable policy's using stand in for that operation, so the landed sentence says each applicable policy's using, matching RowLevelSecurityPolicySchema.using.",
        "check:doc-formula-expressions exited 3 (prerequisite refusal) on the fresh worktree; built @objectstack/formula and @objectstack/lint under os-verify-lock.sh (slot issue-20275; VERDICT command-exit 0, held 240s) and re-ran to 0; the ran-file records the completed run.",
        "The first --ran reconciliation flagged that the record carried no exit codes (a CLAIMED zero); re-recorded every line as 'cmd :: exit N' and reconciled to a DERIVED zero.",
        "The skill file's line count is +2 (natural wrap of the longer paragraph) while the priced unit, tokens, is -2; no re-wrap was used as payment and no other file absorbed content.",
        "PR body footer uses the AGENTS.md session-URL form, not the harness reminder's attribution form (the reminder's own precedence sentence yields to repo instructions); commit trailers are the model-free pair.",
        "PR body cites #19988 / #20012 / #20167 / #20268 only in safe spellings (no closing keyword adjacent); Fixes #20275 alone on line 1; Clause-②: no copied from the claim."
      ],
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: none · noted, not filed — skills/objectstack-data/rules/security.md example policy org_isolation hand-writes organization_id == current_user.organization_id as a select policy while the same file's Multi-tenancy section says never single plus your own RLS and the Layer 0 wall already does it; observation, in the PR Acceptance notes only (dedupe words: org_isolation, security.md example, tenant wall, hand-written RLS)."
      ]
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT — PR #20298 (domain:skills seat 1, session_01MjvgiFAmjHqsxy1XLiVYfH) · 2026-09-27T19:42Z

    Verdict on the dev report 5859010932, checked against GitHub and origin/main 3f86dc52, not against the report's narration.

    Checklist: draft, base main, one commit 4b330f38, first line Fixes #20275, Clause-②: no at line start, #19988 / #20012 / #20167 / #20268 spelled safely, two files inside the claimed surface (docs/adr/0066-unified-authorization-model.md +1 / −1, skills/objectstack-data/rules/security.md +42 / −40; +43 / −41 at head), no packages/** change, skip-changeset on fast-lane paths (read back), PR assignee = card assignee, ## 维护者速读(草稿) present with its five parts, session-URL footer, report JSON parses, mcp_calls 0, api_writes 4 as listed.

    Spot readings (mine, at 2026-09-27T19:24Z): the ADR sentence and the skill paragraph checked sentence by sentence against rls.zod.ts (:94–:99, :420, :483, :546–:568), writeCheckPolicies (security-plugin.ts:890) and the #7665 select-narrowing comment (:2540–:2545); ADR-0058 D4 is the check enforcement decision; the two symbol anchors resolve; security.md at head 10163 bytes = 2541 tokens, 216 lines (ceiling 2543); check-governed-merges --pr 20298: Tier H on both paths, 84 changed lines. Detail in the ## Contract review record on the PR.

    CI reading: at the record's write every completed check on 4b330f38 is green (the two Check PR Size / Check Changeset rows are the expected-skip pairs); check-expected-skips --pr 20298 reported the skip set as CI-STATE-COMPLETE.

    Deviations accepted: the dev corrected the order's suggested ADR wording from the code (each applicable policy's using stands in, not only insert-class) — right, and the reason the order said 实测优先; the prerequisite build for check:doc-formula-expressions; the re-recorded --ran file; +2 lines against −2 tokens. org_isolation stays out (Acceptance notes, carrier none).

    Landing: check-governed-merges → GOVERNED, Tier H (docs/adr/** and skills/**). Four-piece terminal: the in-seat ## Contract review record (PASS) and the 席位 终稿 速读 are on the PR, needs-user-decision is hung on the PR, review is requested from os-zhuang and hotlong. The PR stays draft; on an authorized APPROVED review this seat clears the label and lands through the relay (pr_ready + automerge_enable).


    Generated by Claude Code

  6. added a commit that references this issue on Sep 28, 2026
    2972097
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingdocumentationImprovements or additions to documentationdomain:skillsDomain lane — seat card indexed by label:pm:seatpriority:p2Medium: important, M3security

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions