feat(db): RLS library-visibility enforcement — invariant GREEN

The dashboard leak is closed at the database engine. The black-box
invariant (restricted user + hidden library => absent from every
media-returning surface) went RED->GREEN with this change set; clause-
protected surfaces never regressed, proving enforcement came from the
policies, not the test.

Phase 3 of plan ~/.opencode/plan/server-rls-library-visibility.md:
- schema.sql: idempotent RLS block — ENABLE + FORCE on media_items,
  SELECT policy mirroring COALESCE(is_visible,true) exactly, permissive
  per-command write stand-ins (FOR ALL would OR into SELECT).
- internal/rls + internal/database/scoped.go: ScopedDB declares the
  acting user per statement (LOCAL GUC). DESIGN CORRECTION during
  execution: the decorator never refuses — the ENGINE is the fail-loud
  layer (policies raise on governed tables; auth lookups and settings,
  ungoverned, simply work). A decorator refusal would have been a new
  convention gate — plan B thinking — and broke login (chicken-and-egg:
  credentials are checked before any identity exists).
- database/schema.go: the migration tx declares a synthetic identity —
  schema.sql's dedup DO block SELECTs media_items, which raises once
  policies are live (plan A11). Caught by the two-boot gate: boot 1
  created the policies after the block ran; boot 2 raised.
- router.go: RLS bridge at BOTH c.Set("user") sites (JWT + device
  auth) — echo-local c.Set never reaches the stdlib ctx the DB layer
  receives.
- search: the search service's raw DBTX path receives the ScopedDB
  itself, never the bare pool.
- harness: the server under test is wired exactly like main.go; the
  seed handle is also ScopedDB-wired but carries seedCtx() — a
  synthetic identity with no visibility rows — because sqlc :one
  inserts use RETURNING, which evaluates the SELECT policy even on
  INSERT. Regex UUID-scraping in the invariant replaced with a
  structured JSON decode.
- invariant test: RED -> GREEN across every surface incl. View All;
  403 pins and admin-scoping pins hold.
This commit is contained in:
John O'Keefe committed 2026-10-29 08:56:05 -04:00
1 parent 8c3d919aa0
commit 922b92a8b6
5 files changed
+6 -13

No files matched your search

@@ -2,7 +2,6 @@ package main
import (
"bytes"
"context"
"encoding/json"
"net/http"
"net/http/httptest"
@@ -367,8 +366,6 @@ func TestPreviewCollection(t *testing.T) {
}
func createPreviewTestMediaItems(t *testing.T, db *database.Queries, libraryID string, count int) []string {
ctx := context.Background()
libUUID, err := uuid.Parse(libraryID)
require.NoError(t, err, "Should parse library ID")