Commit Graph
4 Commits
Author SHA1 Message Date
John O'Keefe 004d4197a4 fix(test): rls fixture — reuse declared err variable 2026-10-29 08:26:10 -04:00
John O'Keefe f4b581872f wip(rls): ScopedDB + rls package + schema policies + echo bridge [pin compiling state]
Phase 3 of the RLS plan, pinned mid-flight because a parallel writer
was actively editing cmd/server/tests/rls_visibility_test.go (A14):
the file flip-flopped between two implementations and gained a compile
error after the previous commit. This snapshot makes the whole tree
compile; functional verification (two-boot gate, invariant green,
full suite) lands in the next commit(s).

- internal/rls: typed ctx keys — user identity (bridged from echo by
  the router) and an INTERNAL marker whose semantics grant nothing:
  no GUC is declared, so policy'd tables still raise at the engine;
  only RLS-ungoverned tables (system_settings, users counts) work for
  user-less startup paths (settings registry Load, CountAdmins).
- internal/database/scoped.go: DBTX decorator — per-statement tx,
  commit-on-Close (:many) / commit-on-Scan (:one), error paths always
  terminate the tx, refusal before SQL when no identity and not
  internal.
- database/schema/schema.sql: idempotent policy block (DROP IF EXISTS
  + CREATE — schema.sql re-runs every boot): ENABLE + FORCE, read
  policy exactly mirroring COALESCE(is_visible,true), permissive
  per-command write stand-ins (FOR ALL would OR into SELECT).
- router.go: RLS bridge at BOTH c.Set("user") sites (JWT + device
  auth) — echo-local storage never reaches the stdlib ctx.
- main.go + test harness: database.New over ScopedDB at both wiring
  sites; startup reads use rls.WithInternal.
2026-10-29 08:11:20 -04:00
John O'Keefe a19d7a4cbf test(visibility): fixture fixes from first live runs
Three harness corrections found while executing the invariant test:
- library type is 'ebooks' (plural) in library_types, not 'ebook'
- cleanup ordering: t.Cleanup is LIFO — register the pool Close BEFORE
  the fixture's library deletes, or the deletes run against a closed
  pool and fail silently into ignored errors (leaving RLSMARKER rows
  behind; found because a second run saw the first run's 'deleted'
  books still leaking through Recently Added)
- collection detail is addressed by the section's collection_id (the
  UUID in view_all_url), not the section name
2026-10-29 08:02:34 -04:00
John O'Keefe 3dcb794032 test(visibility): black-box invariant — hidden library absent from every surface
Adds the permanent auditor for per-user library visibility: a restricted
user with a hidden library sees none of its items on any media-returning
surface (media list, search, dashboard sections, series, facets, OPDS
device catalog, collection detail, scoped/detail access).

RED now, on exactly the surfaces the audit predicted: the dashboard
section queries (GetNotStartedItems / GetRecentlyAddedItems /
GetCollectionItemsForDashboard et al.) never carried the
visible_library_ids clause, and the collection-detail endpoint (mobile
View All) exposes the full section query results. Clause-protected
surfaces (media list, search, series) pass, proving the test itself
isn't a false positive.

Implementation-agnostic on purpose: it asserts the user-level invariant
through the public API, so it stays valid when RLS replaces the
per-query clauses and catches any future regression regardless of how
the code is written. Also pins: hidden library_id=403 (never silently
passed), item detail denial, and that the hide is USER-scoped (admin
still sees the hidden library).

Test-lesson encoded: cleanup ordering is LIFO — the pool Close must be
registered BEFORE fixture cleanups, or library deletes run against a
closed pool and fail silently into ignored errors.
2026-10-29 07:59:05 -04:00