feat(scanner): scanner entrypoints + generic jobs carry RLS identity; SeedDB harness fallback

The full suite enumerated the remaining triage class: tests drive the
scanner directly (ScanFolders(context.Background())) and read seeded
media via Queries with bare contexts — every governed read raised.

- MediaScanner.ScanFolders self-declares its configured admin
  (SetAdminID — set by main AND the test helpers): a scan is server
  maintenance, product-correct regardless of caller.
- ScopedDB gains a Fallback identity, exposed as NewSeedDB for the
  TEST HARNESS ONLY: setup.DB falls back to rls.SeedIdentity when the
  context carries none, so direct seeding/verification helpers work
  uniformly instead of per-site context threading. rls.SeedIdentity is
  the shared constant (no library_visibility rows => full visibility).
- generic job creation (jobs.go) stamps the requesting admin into the
  job Context, same as the scan endpoint.

The sync queue processor stays identity-free: sync_queue is not an
RLS-governed table.
This commit is contained in:
John O'Keefe committed 2026-10-29 09:27:04 -04:00
1 parent 385a5d9dba
commit bb2cc26e9d
9 files changed
+72 -35

No files matched your search

+9 -9
View File
@@ -318,15 +318,15 @@ func createRegularUserOnce(t *testing.T, db *database.Queries) UserTestData {
}
}
// seedCtx is the context harness-level media seeding runs under. The
// seed identity is a valid UUID with NO library_visibility rows, so
// RLS grants it full visibility — needed because sqlc's :one inserts
// use RETURNING, which requires SELECT visibility of the new row.
// Product code never uses this; requests carry real personas.
var seedIdentity = pgtype.UUID{Bytes: [16]byte(uuid.MustParse("00000000-0000-0000-0000-000000000002")), Valid: true}
// seedCtx is the context harness-level media seeding runs under. It
// carries rls.SeedIdentity — a valid UUID with NO library_visibility
// rows, so RLS grants it full visibility (needed because sqlc :one
// inserts use RETURNING, which requires SELECT visibility of the new
// row). setup.DB additionally falls back to this identity for calls
// that pass a bare context. Product code never uses this; requests
// carry real personas.
func seedCtx() context.Context {
return rls.WithUser(context.Background(), seedIdentity)
return rls.WithUser(context.Background(), rls.SeedIdentity)
}
// uuidToPGType converts uuid.UUID to pgtype.UUID
@@ -516,7 +516,7 @@ func setupTestServer(t *testing.T) *TestServerSetup {
// writes everything. Real personas only exist at the HTTP layer.
scopedDB := database.NewScopedDB(dbPool)
queries := database.New(scopedDB)
seedDB := database.New(database.NewScopedDB(dbPool))
seedDB := database.New(database.NewSeedDB(dbPool))
// Wire the tunable settings registry exactly like main.go: Load
// populates the cache (missing rows fall back to compiled defaults)