Skip to content

Commit 51a71c3

Browse files
committed
docs(lint): the RLS enforceability header and consequence text state the write-check composition
The header said computeWriteCheckFilter collects only the policies that declare a check. It takes its set from writeCheckPolicies: the declared checks when any applicable policy declares one, otherwise every applicable policy's using compiled as its check. Every using consequence now states what a dropped using on an insert or all policy does to the insert check, and every check consequence is qualified by the rest of the declared set. Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude <noreply@anthropic.com>
1 parent e039b6a commit 51a71c3

1 file changed

Lines changed: 62 additions & 15 deletions

File tree

‎packages/lint/src/validate-rls-predicate-enforceability.ts‎

Lines changed: 62 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,17 @@
2929
* on that object then matches zero rows. If other policies also applied,
3030
* this one is simply dropped out of the OR and the access it was written to
3131
* grant does not exist.
32-
* 4. **Write path (`check`, ADR-0058 D4).** `computeWriteCheckFilter` collects
33-
* only the policies that declare a `check`; the same drop makes the
34-
* post-image predicate the deny sentinel, `matchesFilterCondition` fails,
35-
* and the write raises `PermissionDeniedError`.
32+
* 4. **Write path (`check`, ADR-0058 D4).** `computeWriteCheckFilter` takes
33+
* its set from `writeCheckPolicies`, per insert / update and per caller:
34+
* the applicable policies that declare a `check` when any does (a
35+
* USING-only sibling then adds nothing), otherwise every applicable policy
36+
* with a `using`, that `using` compiled as its check. So an unlowerable
37+
* `check` always reaches this path, and an unlowerable `using` on an
38+
* `insert` / `update` / `all` policy reaches it whenever no applicable
39+
* policy for that operation declares a `check`. When nothing else in the
40+
* set compiles, the same drop makes the post-image predicate the deny
41+
* sentinel, `matchesFilterCondition` fails, and the write raises
42+
* `PermissionDeniedError`; when something else compiles, it alone decides.
3643
*
3744
* So this is not a hole — the runtime fails CLOSED, which is why it has been
3845
* survivable. It is a policy that reads as an authorization and behaves as a
@@ -246,6 +253,36 @@ function quote(source: string): string {
246253
return source.length > 200 ? `${source.slice(0, 197)}...` : source;
247254
}
248255

256+
/**
257+
* The INSERT half of a dropped `using`, appended to every `using` consequence
258+
* below. The ADR-0058 D4 write check takes its set from `writeCheckPolicies`:
259+
* when no applicable policy for the insert declares a `check`, every applicable
260+
* policy's `using` is compiled as its check, so the same drop reaches the insert
261+
* too. Measured through the real `SecurityPlugin` on an `insert` and an `all`
262+
* policy, for all three kinds of drop (an unlowerable shape, an unresolved
263+
* `current_user.*`, an undeclared column): with nothing else compiling in that
264+
* set, every single-record insert is refused (403); with another applicable
265+
* policy's `using` compiling, that one alone decides; with a declared `check`
266+
* beside it, the declared check alone decides and this `using` takes no part.
267+
*/
268+
const USING_INSERT_CONSEQUENCE =
269+
' On an `insert` or `all` policy the same `using` is also the INSERT check whenever no applicable ' +
270+
'policy for the insert declares a `check` (ADR-0058 D4): when nothing else in that set compiles, ' +
271+
'every single-record insert it governs fails with `PermissionDeniedError`; when another ' +
272+
"policy's `using` compiles, that one alone decides the insert.";
273+
274+
/**
275+
* Qualifies every `check` consequence below. The write check OR-combines the
276+
* declared checks of all the applicable policies for the operation, so a
277+
* dropped `check` is a blanket refusal only when no other declared `check` in
278+
* that set compiles (measured: beside a compiling declared `check`, that one
279+
* alone decided; beside a USING-only sibling, every write was refused, because
280+
* a USING-only sibling takes no part once any policy declares a `check`).
281+
*/
282+
const CHECK_SET_QUALIFIER =
283+
' That holds when no other applicable policy for the operation declares a `check` that compiles; ' +
284+
'when one does, that `check` alone decides and this one contributes nothing.';
285+
249286
/** What the runtime does with a predicate it cannot compile, per clause. */
250287
function consequence(clause: 'using' | 'check'): string {
251288
const dropped =
@@ -256,11 +293,14 @@ function consequence(clause: 'using' | 'check'): string {
256293
'When it is the only applicable policy for that object and operation, `compileFilter` returns the ' +
257294
'`RLS_DENY_FILTER` sentinel instead, which is AND-ed onto the where clause: every select / update / ' +
258295
'delete on the object matches ZERO rows. When other policies also apply, this one just vanishes ' +
259-
'from the OR and grants none of the access it appears to.'
296+
'from the OR and grants none of the access it appears to.' +
297+
USING_INSERT_CONSEQUENCE
260298
: dropped +
261299
'On the ADR-0058 D4 write path that leaves the post-image `check` as the `RLS_DENY_FILTER` ' +
262-
'sentinel, which no record can satisfy: every insert / update the policy governs fails with ' +
263-
'`PermissionDeniedError`. The policy reads as a write rule and behaves as a blanket refusal.';
300+
'sentinel, which no record can satisfy: every single-record insert and by-id update the policy ' +
301+
'governs fails with `PermissionDeniedError`. The policy reads as a write rule and behaves as a ' +
302+
'blanket refusal.' +
303+
CHECK_SET_QUALIFIER;
264304
}
265305

266306
/* ────────────────────────────────────────────────────────────────────────────
@@ -532,11 +572,14 @@ function referenceConsequence(clause: 'using' | 'check', kind: 'field' | 'variab
532572
'delete matches ZERO rows, so the object DISAPPEARS for every holder of this permission set — ' +
533573
'not because they were denied, but because the narrowing they were granted resolves to nothing. ' +
534574
'When other policies also apply, this one vanishes from the OR and grants none of the access it ' +
535-
'appears to.'
575+
'appears to.' +
576+
USING_INSERT_CONSEQUENCE
536577
: dropped +
537-
'On the ADR-0058 D4 write path that leaves the post-image `check` unsatisfiable: every insert / ' +
538-
'update the policy governs fails with `PermissionDeniedError`. The policy reads as a write rule ' +
539-
'and behaves as a blanket refusal for every holder of this permission set.';
578+
'On the ADR-0058 D4 write path that leaves the post-image `check` unsatisfiable: every ' +
579+
'single-record insert and by-id update the policy governs fails with `PermissionDeniedError`. ' +
580+
'The policy reads as a write rule and behaves as a blanket refusal for every holder of this ' +
581+
'permission set.' +
582+
CHECK_SET_QUALIFIER;
540583
}
541584

542585
// ── The FIELD half. ONE direction, in every position and every polarity,
@@ -555,11 +598,15 @@ function referenceConsequence(clause: 'using' | 'check', kind: 'field' | 'variab
555598
'`RLS_DENY_FILTER` sentinel: every select / update / delete matches ZERO rows, so the object ' +
556599
'DISAPPEARS for every holder of this permission set — not because they were denied, but because ' +
557600
'the narrowing they were granted names a column that is not there. When other policies also ' +
558-
'apply, this one vanishes from the OR and grants none of the access it appears to.'
601+
'apply, this one vanishes from the OR and grants none of the access it appears to.' +
602+
USING_INSERT_CONSEQUENCE
559603
: dropped +
560-
'On the ADR-0058 D4 write path that leaves the post-image `check` unsatisfiable: every insert / ' +
561-
'update the policy governs fails with `PermissionDeniedError`. The policy reads as a write rule ' +
562-
'and behaves as a blanket refusal for every holder of this permission set. ⚠️ On a runtime older ' +
604+
'On the ADR-0058 D4 write path that leaves the post-image `check` unsatisfiable: every ' +
605+
'single-record insert and by-id update the policy governs fails with `PermissionDeniedError`. ' +
606+
'The policy reads as a write rule and behaves as a blanket refusal for every holder of this ' +
607+
'permission set.' +
608+
CHECK_SET_QUALIFIER +
609+
' ⚠️ On a runtime older ' +
563610
'than that guard this clause failed OPEN rather than closed — the write path had no ' +
564611
'field-existence check at all, so a negated miss was satisfied VACUOUSLY by the post-image and ' +
565612
'PERMITTED exactly the writes the policy was written to refuse, on every driver. Fix the name ' +

0 commit comments

Comments
 (0)