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. */
250287function 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