Skip to content

feat(observer): Data Observer Phase 1 — secure RBAC foundation (observer-api + Supabase + observer-web) - #27

Merged
oldhero5 merged 10 commits into
mainfrom
feat/observer-foundation
Jun 30, 2026
Merged

feat(observer): Data Observer Phase 1 — secure RBAC foundation (observer-api + Supabase + observer-web)#27
oldhero5 merged 10 commits into
mainfrom
feat/observer-foundation

Conversation

@oldhero5

Copy link
Copy Markdown
Owner

Summary

Phase 1 (Foundation) of the internal Data Observer — a secure, role-managed app to explore the platform's bitemporal data. This lands the auth/RBAC spine and the app shell end-to-end. The 4V explorer, quality (Veracity) view, geo map, and Neo4j graph views follow in later phases.

What's included

  • observer-api (src/energex/observer/) — FastAPI read service. JWT RBAC (viewer < operator < admin) verified against the Supabase HS256 secret (audience authenticated; exp + aud required). Endpoints: GET /catalog (ArcticDB library/symbol/row inventory, viewer+), GET /me, GET /admin/ping (admin-only), open GET /healthz. Per-symbol read errors are isolated so one unreadable symbol can't fail the whole catalog.
  • Supabase RBAC (supabase/migrations/0001_observer_rbac.sql) — profiles / audit_log / saved_views / issue_acks with RLS. Role is the source of truth in profiles.role and is injected into the JWT as the user_role claim by custom_access_token_hook. RLS blocks role self-escalation (admin-only writes); a dedicated supabase_auth_admin SELECT policy lets the hook resolve roles under RLS. Verified end-to-end — a real minted JWT carries the role.
  • observer-web (observer-web/) — Next.js 16 + Tailwind v4 "Graphite" dark theme. Supabase SSR auth (proxy.ts gate via getUser()), role-aware nav rail, and an Overview page reading /catalog. Vitest unit tests + a deferred Playwright @smoke.
  • Platform wiringdocker-compose.yml adds observer-api (:8090) and observer-web (:8080) under the full + observer profiles; observer-web/Dockerfile (multi-stage, NEXT_PUBLIC_* as build args); .env.example keys; CI runs observer-api tests via --all-extras and adds an observer-web build + Vitest job.

Security model (defense-in-depth)

observer-api is the real authorization boundary: it re-verifies every JWT and enforces min-role per endpoint. The frontend only does UX gating and carries the bearer token — the client is never trusted. No secrets are committed (.env is gitignored; the Supabase service key never reaches the browser bundle).

Testing & review

  • 237 backend tests pass (uv run --all-extras pytest); ruff check + format clean; observer-web npm run build + Vitest green; both docker compose --profile full|observer config validate.
  • Reviewed by a five-lens pass (spec-coverage, security/RBAC end-to-end, backend, frontend-integration, infra): 0 Critical / 0 Important. The worthwhile minors were fixed in-branch; the remainder are tracked for later phases.

Notes

  • Cross-repo change: enabling the access-token hook required setting [auth.hook.custom_access_token] in the shared Supabase project's config.toml (a separate repo) and restarting it; data was preserved.
  • Deploy caveat: NEXT_PUBLIC_SUPABASE_URL must be reachable from both the browser and the web container — documented in docker-compose.yml and .env.example.
  • The design spec and implementation plan live under docs/superpowers/ (gitignored by existing repo convention).

oldhero5 added 10 commits June 30, 2026 07:32
create_app() read ObserverSettings() at import time, requiring
OBSERVER_JWT_SECRET even when only importing the module. Replace the
settings call with a direct os.environ read for OBSERVER_CORS_ORIGINS,
matching the pattern in energex.service.readapi. JWT secret validation
still happens at request time via auth.py (unchanged).

Add regression test: importing energex.observer.app with no
OBSERVER_JWT_SECRET set must succeed, and protected routes still 401.
Wrap each lib.read(s) call in a try/except so a corrupt or unreadable
symbol only increments an `unreadable` counter rather than crashing the
entire /catalog response. Each library entry now includes {"name",
"symbols", "rows", "unreadable"}.

Add test_catalog_resilient_to_bad_symbol to verify HTTP 200 is returned,
good symbols are counted, and unreadable >= 1 when one symbol raises.
profiles/audit_log/saved_views/issue_acks with RLS; observer_current_role()
helper; handle_new_user() trigger seeds a viewer profile; custom_access_token_hook
injects profiles.role into the JWT as the user_role claim. Includes the
supabase_auth_admin read policy so the hook can resolve roles under RLS.
@oldhero5
oldhero5 merged commit 61e56e0 into main Jun 30, 2026
10 checks passed
@oldhero5
oldhero5 deleted the feat/observer-foundation branch June 30, 2026 23:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant