# Pattern 03 — Permissive policy broadening

## The failure

AI adds a second SELECT policy to an existing policy, not understanding that PostgreSQL ORs permissive policies:

```sql
-- Existing policy (correct):
CREATE POLICY "own_posts" ON posts
  FOR SELECT USING (auth.uid() = user_id);

-- AI adds this to "allow public reading of published posts":
CREATE POLICY "published_posts" ON posts
  FOR SELECT USING (is_published = true);
```

With both policies active, the effective USING clause is:

```
(auth.uid() = user_id) OR (is_published = true)
```

Any authenticated user now sees ALL posts where `is_published = true` — regardless of who wrote them. If a user publishes a post that they later want to hide from specific people, they can't — all authenticated users will see it.

This matters most when "published" doesn't mean "publicly readable by everyone" — for example, a content management app where "published" means "sent to editors" not "visible to all users."

## How PostgreSQL RLS policy combination works

- **PERMISSIVE** (default): multiple policies of the same type are OR'd. Access is granted if ANY policy permits it.
- **RESTRICTIVE**: policies are AND'd with all permissive results. Access is granted only if all restrictive policies pass.

The Supabase policy UI defaults to PERMISSIVE. Adding a second permissive policy always broadens access.

## The fix

Express the full intent in a single policy. Combine conditions with OR explicitly so the logic is visible:

```sql
CREATE POLICY "select_posts"
  ON posts
  FOR SELECT
  TO authenticated
  USING (
    auth.uid() = user_id      -- own posts
    OR is_published = true    -- or published by anyone
  );
```

If you need a RESTRICTIVE policy (AND semantics), use `AS RESTRICTIVE`:

```sql
-- Example: must be authenticated AND in the right org
CREATE POLICY "must_be_org_member" ON posts
  AS RESTRICTIVE
  FOR SELECT
  TO authenticated
  USING (
    EXISTS (
      SELECT 1 FROM org_members
      WHERE org_members.org_id = posts.org_id
      AND org_members.user_id = auth.uid()
    )
  );
```

## Testing it

In Supabase SQL editor, view all policies on a table:

```sql
SELECT policyname, cmd, qual, with_check, roles
FROM pg_policies
WHERE tablename = 'posts'
ORDER BY cmd, policyname;
```

For each `cmd` (SELECT, INSERT, UPDATE, DELETE), check whether multiple PERMISSIVE policies exist. If they do, evaluate whether their USING clauses combined with OR produce the intended behaviour.
