Skip to content

Studio RLS editor rates a non-lowerable USING predicate a WARNING ("can … WIDEN access") and autosaves it, while publish / os build refuse it as an error (rls-predicate-unenforceable, fail-closed) #13061

Closed

Description

@objectstack-fleet

Filing gate: ① a product defect with a named landing point and a reproduction (pm:queue class).
Reader: the objectui lane owning packages/app-shell/src/views/metadata-admin/celAuthoring.ts (Studio CEL authoring) — the fix PR there.
Dedupe (open AND closed, semantic search): objectstack-ai/objectui "RLS policy editor non-pushdownable predicate warning widen access lintCelPredicate severity error rls-predicate-unenforceable" → 4 hits, all closed, none on the severity tier (#2413 the original lint feature, #4302 package-door discard, #4533 a11y, #8069 field-rule fallback); objectstack-ai/objectstack "RLS editor warning tier differs from build lint error unenforceable predicate" → 39 hits, all closed, none on the editor tier (objectstack-ai/objectstack#24452 / objectstack-ai/objectstack#19951 are framework lint coverage). First-round wave cards (#13052 #13054 #13055 #13056) checked — none covers it.

reach: public entry measured — Studio's RLS policy editor (package door) on a stock showcase boot with the console built at objectui 4016370a: the editor warns and autosaves a USING predicate that publish refuses 422.
Path: studio-authoring.expression-editors
QA-source: objectstack-ai/objectstack#24604 · studio-authoring.expression-editors · negative[0]
Checklist item priority: P1
type: Bug

Filed on behalf of the QA runner session session_01NvpZQ6sRwAVRNDi4UsF2c7 (its GitHub scope did not include this repository) by the 18.0 QA wave dispatch session session_01CfRDy7SuTfUqaTu2AEH2Hi.

What is wrong

For an RLS USING predicate that cannot be lowered to a row filter — e.g. size(record.asset_code) > 3 — the Studio editor and the engine give different verdicts for the same expression at the same site:

  • Editor (objectui celAuthoring.ts lintCelPredicate, at 4016370a): severity: 'warning' (engine.celLint.notPushdownable) with the text that the predicate "can be dropped and WIDEN access"; because it is only a warning the Save gate stays open and the policy autosaves (draft PUT 200).
  • Engine (@objectstack/lint rls-predicate-unenforceable, packages/lint/src/validate-rls-predicate-enforceability.ts in objectstack-ai/objectstack, the rule os build runs): severity error; publish answers 422 INVALID_METADATA, "RLS using is not lowerable (unsupported operand "call"), so the policy is dropped and grants no access".

The editor's wording also states the wrong direction: the runtime fails CLOSED (packages/plugins/plugin-security/src/rls-compiler.ts in objectstack-ai/objectstack adds no filter for a dropped policy and returns the deny filter when none compiles), so a dropped policy only removes the access it was written to grant — it never widens. Not an access-control hole; a second grammar in the GUI, which ADR-0032 / celAuthoring exists to prevent.

Reproduction

  1. Boot the showcase with the console built at the .objectui-sha pin; sign in as admin.
  2. Create a writable scratch package (POST /api/v1/packages) with an object carrying a text field asset_code.
  3. Studio → that package → Access → a permission set → RLS policy on the object, USING size(record.asset_code) > 3.
    • Expected (engine verdict): an error that holds Save, worded as fail-closed ("the policy is dropped and grants no access").
    • Actual: an advisory warning "…can be dropped and WIDEN access"; autosave issues PUT /api/v1/meta/permission/NAME?package=PKG&mode=draft → 200.
  4. Publish: POST /api/v1/meta/permission/NAME/publish?package=PKG → 422 INVALID_METADATA, rule rls-predicate-unenforceable, severity error.
    Control: USING record.asset_code == 'A1' → no warning; publish 200.

Expected fix shape

Give the lowerability verdict at error tier for USING and CHECK, using the same decision function the lint rule uses (isSupportedRlsExpression), so Save is gated; drop the "WIDEN access" wording in favour of the fail-closed sentence the engine prints.

Note for the checklist lane (not this card's work): the item's own acceptance[2] / acceptance[4] still encode the warning tier and will need the matching re-point; it is recorded on the wave anchor objectstack-ai/objectstack#24579.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: p2 · bug · domain:ui · area:access · pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-19T17:55Z

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015). ⛔ Not a claim, ⛔ not a dispatch. Fresh read on objectui origin/main c2191654a4.

    • The premise holds as code-read: packages/app-shell/src/views/metadata-admin/celAuthoring.ts carries both tiers (:119/:120). The card measures the non-lowerable USING case rated warning and autosaved, where publish and os build refuse it as rls-predicate-unenforceable.
    • p2: an authoring-tier mismatch. The draft saves and the publish refuses loudly, so nothing unenforceable goes live. The warning text ("can … WIDEN access") also misstates what the runtime does, which fails closed. It is a tax, not a wall.
    • Lane domain:ui: rate it error and block the save, the same verdict as the server, with the server's wording. ⛔ Do not restate the lint: attach the spec/lint verdict where an export exists (the objectui#7715 rule).

    Generated by Claude Code

  2. added
    area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guards
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    and removed on Oct 19, 2026
  3. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 (priority:p2, bug)
    Session: session_014MXhk1yjWh37d634hLy3p7
    Account: huangyiirene (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-13061-rls-lowerable-error
    Worktree: objectui-issue-13061
    Domain: domain:ui
    Seat: domain:ui#1
    File surface: packages/app-shell/src/views/metadata-admin/celAuthoring.ts (lintCelPredicate's non-lowerable USING / CHECK verdict, the engine.celLint.notPushdownable advisory at about :566 of objectui main fb85637, and the CelLintIssue / CelLintMessageKey types), its caller's save gate if it reads only errors, packages/app-shell/src/views/metadata-admin/i18n.ts (the advisory's wording, about :1734 and :5253), tests beside them, and one .changeset/13061-rls-lowerable-error.md. Stop on breach; explain in the report.
    Container & model: M, mode:subagent, model: opus (default tier; dispatch-gates --tier --repo objectstack-ai/objectui over this surface reads no path-derived mandate)
    Clause-②: no
    Responsibility: the risk is produced by objectui's own Studio CEL lint (celAuthoring.ts lintCelPredicate rates a non-lowerable RLS USING predicate warning, so Save stays open and the draft autosaves); the platform path that covers it: the server, at publish (rls-predicate-unenforceable, severity error, 422 INVALID_METADATA), so nothing unenforceable goes live, and the gap is the editor's second verdict; who reaches it: every Studio author writing an RLS predicate that cannot be lowered (QA sweep objectstack-ai/objectstack#24604, reproduced on a stock showcase).
    Thread-read: 6273706610
    Serial constraints cleared: no open objectui PR touches celAuthoring.ts (open PRs #13077, #13073, #13072, #13071, #13070, #13050, file lists read at this claim). PR #13050 (objectui#12982) edits views/metadata-admin/i18n.ts, a shared file and not a single-claim path: whichever PR lands second merges main.

    Why Clause-②: no: celAuthoring.ts is internal to @object-ui/app-shell; its entry (exports['.'] → dist/index) re-exports nothing from it. The server's accept set is unchanged (it already refuses the predicate); only the editor's own gate moves to the server's verdict. A route that would widen a published face (a new export, a new published key or prop) is outside this claim: the dev stops and reports it.

    Claimed at: 2026-10-19T19:21Z

  4. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 13061,
    "status": "done",
    "branch": "claude/issue-13061-rls-lowerable-error",
    "pr": "#13080",
    "session": "session_014MXhk1yjWh37d634hLy3p7 (subagent; parent PM session)",
    "premise_still_valid": true,
    "summary": "lintCelPredicate (celAuthoring.ts) now asks @objectstack/formula's isSupportedRlsExpression for both USING and CHECK. For a clause it refuses, it carries the finding @objectstack/lint's published validateRlsPredicateEnforceability returns for a one-clause stack, with severity (error) and message as written, e.g. 'RLS using is not lowerable (unsupported operand "call"), so the policy is dropped and grants no access'. The existing gate (PermissionAdvancedFacets counts errors; PermissionMatrixEditor holds Save and useDraftAutoSave on celErrorCount) therefore blocks Save with no host change. The module-worded 'WIDEN access' advisory, its engine.celLint.notPushdownable key and its en/zh rows are retired, and the fail-open claims in comments and the app-shell README are corrected. H1-H4 all held as measured; card assignee huangyiirene unchanged (PM's).",
    "tests": "HEAD ae0abf7. Ran from the worktree root under os-verify-lock: pnpm exec vitest run --maxWorkers=2 over 14 files in packages/app-shell/src/views/metadata-admin/ (celAuthoring.test.ts, celAuthoring.fieldRuleVerdict-9318.test.ts, CelPredicateField.{test,i18n-10862-s2.test,labelBinding.test,scopeVerdict-11789.test}.tsx, PermissionAdvancedFacets.{cel,adminScopeAnchor,retiredKeys,rlsLabelDescription,sharedSelect-11865}.test.tsx, ConditionalFormattingEditor.test.tsx, PermissionMatrixEditor.{autosave-11787,packageDoorFacets}.test.tsx). Output: 'Test Files 14 passed (14)' / 'Tests 186 passed (186)' / 'VERDICT command-exit 0'. Full app-shell suite declared to CI (per dispatch). Ablation, one-off: node /home/user/objectstack/scripts/ablation-replace.mjs replaced the severity-mapping line in rlsLowerabilityVerdict with an always-'warning' push (the pre-fix tier). Output: 'anchor x1 -> x0', 'replace x0 -> x1', 'blob 1c4e132a78f3 -> 7a02dbc069a0'. Run over the 4 changed test files: 'Tests 6 failed | 70 passed (76)'. Red: 2 celAuthoring tier pins, the CelPredicateField blocking pin, 3 PermissionAdvancedFacets blocking pins. Green: the 2 i18n word controls and the lowerable control. Restore: 'blob == HEAD (1c4e132a78f3) and git diff HEAD is empty'. No dist rebuild needed: tests import ./celAuthoring from source, and @objectstack/lint/formula were not mutated.",
    "gates": "Local, HEAD ae0abf7, each exit captured before any pipe. pnpm --filter @object-ui/app-shell type-check exit 0. The first run exited 2 because the deps closure was unbuilt; after turbo run build --filter=@object-ui/app-shell^... --concurrency=2 (exit 0), a second run exited 2 with a real TS2345: every inferred-predicate narrowing, fixed in ae0abf7. pnpm --filter @object-ui/app-shell lint exit 0 ('0 errors'; the 1 warning on CelPredicateField.tsx is on untouched code). pnpm check:new-line-citations exit 0 ('0 new citation(s)'). pnpm check:control-bytes exit 0. pnpm check:i18n-keys, check:i18n-dead-keys, check:i18n-designer-parity, check:i18n-drift all exit 0. node scripts/check-changeset-presence.mjs exit 0. pnpm check:changeset-claims and check:pending-changeset-literals exit 0. node scripts/check-governed-queue-guard.mjs --test over the diff exit 0 ('NOT GOVERNED'). No --ran reconciliation: dispatch-gates.mjs refuses objectui, so this list was hand-derived from objectui package.json, as in the dispatch. CI on PR #13080 at report time: label success; skipped: Test (coverage), coverage shards, dependabot. in_progress: Lint, Type Check, Test shards 1-16/16, Test (dist pins), Build & E2E, Build Docs, Bundle Analysis, Repo Gates, Changeset Declaration, Changeset Fixed Group Check, Changeset Claim Re-read, Governed Surface Queue Guard, Live E2E (informational). Not waited on.",
    "deviations": "1) File surface beyond the claim list: CelPredicateField.tsx (comments only) and packages/app-shell/README.md (two paragraphs). Both stated the retired advisory or the fail-open claim and would be false after this change (AGENTS.md commandment 2, docs-driven); both are named in the PR body. Test edits beyond celAuthoring.test.ts are files beside it in the same directory (CelPredicateField.test.tsx, CelPredicateField.i18n-10862-s2.test.tsx, PermissionAdvancedFacets.cel.test.tsx). 2) Gates added beyond the dispatch list: changeset-presence, changeset-claims, pending-changeset-literals, governed-queue-guard --test, i18n-drift, i18n-designer-parity. 3) Ran the read-only git stash list once while filtering lint output; no stash push or pop. 4) Worktree removed after the PR opened, per definition (node_modules then git worktree remove, exit 0).",
    "mcp_calls": "0 — no MCP GitHub tool called (reads via gh api)",
    "api_writes": "2 — two fleet-write relay dispatches, each POST /repos/objectstack-ai/objectstack/dispatches. (a) pr_create, executed as POST /repos/objectstack-ai/objectui/pulls (draft #13080) plus POST /repos//issues/13080/assignees in the same action; read-back: 8083 bytes sent and stored, identical; assignee huangyiirene. (b) comment, executed as POST /repos//issues/13061/comments (this report). Not REST: git push x3 of the branch (empty probe, 39e07c8, ae0abf7).",
    "open_questions": [
    {
    "question": "Should the RLS editor also show the lint finding's hint (rewrite guidance, e.g. 'write has(record.x) as record.x != null') under the error? Today only the message ships, as the card asks.",
    "options": [
    "A Keep message only (as shipped), the publish door's sentence",
    "B Also render the lint's hint as a second line beneath the error"
    ],
    "recommendation": "A, because the card and triage ask for the server's sentence, and adding the hint is a wording/UX call for the lane. It also moves no verdict."
    }
    ],
    "out_of_scope_findings": [
    "carrier: none · noted in PR #13080 Acceptance notes, not filed · the lint rule's reference-pass verdicts on a lowerable clause (e.g. current_user.nope == owner_id -> rls-predicate-unknown-user-variable, error at publish) get no editor finding: silent, not wrong. Read off the rule via a node probe, not reproduced in Studio · dedupe words: RLS editor, unknown user variable, reference pass, publish 422, lintCelPredicate",
    "carrier: none · noted, not filed · legacy SQL-ish owner_id = current_user.id: the editor's CEL parse error blocks Save while the deprecated server bridge lowers it. Pre-existing, the editor is the stricter side; canonical RLS is CEL (ADR-0058 D1)"
    ]
    }


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #13080 comment 6276033278 · no contract-review face hit (Clause-②: no, no schema, no governed path) · domain:ui#1 · session_014MXhk1yjWh37d634hLy3p7 · 2026-10-19T19:57Z. Out-of-scope finding filed as #13082. The flip to ready and auto-merge wait for green check-runs on head ae0abf7.

  6. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    LANDED · domain:ui seat 1 · session_014MXhk1yjWh37d634hLy3p7 · 2026-10-19T20:19Z · pm:dispatched cleared

    reading value
    PR #13080 merged through the merge queue at 2026-10-19T20:18Z; merge commit 2a75c83, which git merge-base --is-ancestor places on origin/main (exit 0)
    queue no gh-readonly-queue/main/pr-13080-* branch remains
    landed content on origin/main, celAuthoring.ts carries async function rlsLowerabilityVerdict (1 hit). notPushdownable is gone from packages/app-shell/src (0 files). Positive control: engine.celLint.notTheRow still reads 2 hits in the same file
    records claim 6275349095, dev report on this card, ACCEPT 6276033278 (no contract-review face hit)
    closing closed completed at 2026-10-19T20:18Z through the PR's Fixes #13061, the only card closed since the merge; pm:dispatched is removed in this act

    Out of scope, carried: the reference-pass parity (a lowerable predicate the rule refuses on what it names) is #13082.

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

Metadata

Metadata

Assignees

Labels

area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions