test(visibility): full suite green under RLS — sync paths, device-auth bridge, seed pool

Phase 5 (plan §7): the entire integration suite passes with the RLS
policies live. The suite was the triage enumerator the plan predicted:

- device_auth middleware: the THIRD identity bridge — KOReader/Kobo
  device-authenticated requests read media as the DEVICE OWNER
  (plan A9 called this path out; the flip was 404s on sync metadata).
- conflicts/analytics/progress handlers: detached
  context.Background() reads threaded to the request context (the
  requesting user's identity) — 12+ sites; resolve helpers take ctx.
- scanner: ScanFolders self-declares its configured admin identity
  (server maintenance, product-correct for any caller); generic job
  creation stamps the requesting admin.
- harness: setup.DBPool and the sync-test pool declare
  rls.SeedIdentity at SESSION scope via BeforeAcquire — the pool never
  serves a second identity, so no A3 bleed; setup.DB falls back to the
  seed identity (NewSeedDB) for ungoverned-context calls; filter tests
  use seedExec for raw governed-table statements.
- contract pin WIDENED (docs/contract_pins.md 2026-10-29): under RLS,
  item-scoped gates may answer 404 (row invisible — no existence leak)
  as well as 403 (Go-level checks on listings keep strict 403).
- TestWorker_JobStatusTracking — the pre-existing red from the Phase 0
  baseline — passes with the harness seed work; no product change.
This commit is contained in:
John O'Keefe committed 2026-10-29 10:14:08 -04:00
1 parent bb2cc26e9d
commit 4d697f5fcf
7 files changed
+84 -38

No files matched your search

+29
View File
@@ -329,6 +329,26 @@ func seedCtx() context.Context {
return rls.WithUser(context.Background(), rls.SeedIdentity)
}
// seedExec runs a raw statement against the pool under the seed
// identity. The bare pool bypasses ScopedDB (nothing reads the ctx for
// the GUC), so governed tables need the identity declared manually
// inside a transaction.
func seedExec(t *testing.T, pool *pgxpool.Pool, sql string, args ...any) {
t.Helper()
ctx := rls.WithUser(context.Background(), rls.SeedIdentity)
tx, err := pool.Begin(ctx)
require.NoError(t, err)
if _, err := tx.Exec(ctx, `SELECT set_config('app.user_id', $1, true)`, rls.SeedIdentity.String()); err != nil {
_ = tx.Rollback(ctx)
require.NoError(t, err, "declare seed identity")
}
if _, err := tx.Exec(ctx, sql, args...); err != nil {
_ = tx.Rollback(ctx)
require.NoError(t, err, "seed exec")
}
require.NoError(t, tx.Commit(ctx))
}
// uuidToPGType converts uuid.UUID to pgtype.UUID
func uuidToPGType(u uuid.UUID) pgtype.UUID {
return pgtype.UUID{Bytes: [16]byte(u), Valid: true}
@@ -503,6 +523,15 @@ func setupTestServer(t *testing.T) *TestServerSetup {
dbConfig, err := pgxpool.ParseConfig(cfg.DatabaseURL())
require.NoError(t, err, "Failed to parse database URL")
dbConfig.MaxConns = 1
// Harness-only session identity (plan §5): every connection in this
// pool runs as rls.SeedIdentity at SESSION scope, so raw-pool
// statements in tests (DBPool.Exec/QueryRow against governed tables)
// pass the read policy. No bleed risk: this pool never serves a
// second identity — A3 applies to the server's request pool.
dbConfig.BeforeAcquire = func(ctx context.Context, conn *pgx.Conn) bool {
_, err := conn.Exec(ctx, `SELECT set_config('app.user_id', $1, false)`, rls.SeedIdentity.String())
return err == nil
}
dbPool, err := pgxpool.NewWithConfig(context.Background(), dbConfig)
require.NoError(t, err, "Failed to connect to test database")