Commit Graph
5 Commits
Author SHA1 Message Date
John O'Keefe 8c3d919aa0 wip(rls): engine-side fail-loud + harness seed identity [pin: work interrupted]
Design correction discovered by the auth chicken-and-egg: the
decorator-level refusal was re-created a convention gate (plan B
thinking). ScopedDB now NEVER refuses — it declares the GUC when the
context carries an identity and declares nothing otherwise; the ENGINE
is the fail-loud layer (policies raise on governed tables, ungoverned
tables like auth lookups simply work). rls.WithInternal removed as a
dead concept.

- search service's raw DBTX path now receives the ScopedDB itself
  (main.go + harness) — a bare pool bypassed the GUC.
- database/schema.go: migration tx declares a synthetic identity
  (schema.sql's dedup DO block SELECTs media_items; with policies
  live, boot 2 raised 'unrecognized configuration parameter' — A11).
- harness: TestServerSetup.DB is a raw-pool SEED handle (write
  policies permissive, other seeded tables ungoverned); the server
  under test keeps the scoped wiring. 33 CreateMediaItem sites use
  seedCtx() — a seed identity with no visibility rows — because
  RETURNING requires SELECT visibility of the new row.
- invariant test: regex UUID-scraping replaced with a structured JSON
  decode of the sections payload.

INTERRUPTION NOTE: an edit to this same effect (seedCtx) was applied,
confirmed by the tool, and subsequently vanished from disk while an
'opencode serve' process (PID 84551) is alive on the machine. This
commit pins the tree against further loss; implementation is PAUSED
pending the user killing that process.
2026-10-29 08:44:12 -04:00
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