project-page active westside-streamlit
project-westside-streamlit updated 2026-04-23

westside-streamlit

Vision

An operational dashboard for Westside Basketball built in Streamlit — a read-only view over the basketball-api Postgres database that surfaces contract status, jersey payments, and parent-outreach cohorts in one place. It is deliberately not a CRUD app: its job is to turn raw database state into "who does Marcus need to chase today?" in a single screen. The dashboard is Marcus's window into his business — the read-only operational view where he identifies what needs to get done and messages Lucas in GroupMe to execute.

Eventual finish line: an HTTP remote MCP server exposes the same data surface so Claude on Marcus's phone can answer "what are my data gaps and actionables today?" natively. Read-only stays the axiom at every layer.

User Stories

Each story is its own note (tag: user-story). Keys become ticket labels (e.g. story:triage) per the traceability triangle.

Key Story Note Role Success Metric
triage story-westside-streamlit-triage Ops (Lucas/Ava) Identify unsigned players in <10s without drilling into cards
blast story-westside-streamlit-blast Ops (Lucas/Ava) Grab a scoped email cohort for a blast in one copy-paste
clusters story-westside-streamlit-clusters Ops (Lucas/Ava) Parents with multiple unsigned kids surface automatically
jerseys story-westside-streamlit-jerseys Head Coach (Marcus) See who has/hasn't paid for jerseys, including cash payments
audit story-westside-streamlit-audit Ops (Lucas/Ava) Audit signed contracts by date, version, and amount
reachable story-westside-streamlit-reachable Platform + Head Coach Marcus can open the dashboard on his phone with Keycloak SSO (shared with westside-app) and no Tailscale client required

Architecture

  1. Domain Model — entities touched
  2. Data Flow — how a page render moves data from Postgres to the browser
  3. Deployment — where the service runs and how it connects to the database
  4. CI Pipeline (shared platform pattern) — the Woodpecker → Harbor → pal-e-deployments → ArgoCD loop this service uses

Key decisions:

  • Read-only forever. No writes. Defense in depth — enforced at app layer (no write code), auth layer (admin role only), AND database layer (SELECT-only Postgres role).
  • Same-realm SSO. Keycloak client lives in the existing westside-basketball realm. Marcus logs into basketball-landing once, the dashboard is SSO-reachable in the same browser session.
  • Admin-role gating (Lucas's "B" decision). Only admin realm role sees the dashboard. Parents/coaches hit 403 via a friendly denied page.
  • In-app Keycloak integration (streamlit-keycloak), not oauth2-proxy sidecar — the app needs JWT role claims in st.session_state for the role check and any future role-based data filtering.
  • Canonical hostname: westside-streamlit.tail5b443a.ts.net (NOT westside-ops — that identity is retired).
  • PGURL env var is required — no fallback, no default. Fails loud.

🚨 Incident 2026-04-10

A parallel quick-and-dirty deploy (pal-e-streamlit, mis-named) bypassed this project's entire ticket scaffold and shipped a public Tailscale funnel with zero authentication. PII for minors' families was exposed on the public internet for ~4 hours. Fully documented in incident-2026-04-10-pal-e-streamlit-public-funnel. Mitigated via kubectl annotate removing the funnel annotation. Source-of-truth aligned via pal-e-deployments#109. Postmortem action items are tracked through ticket #4 (kustomize overlay rename migration) and the behavioral rule in feedback_funnel_requires_auth.

The incident validated the project's existing ticket scaffold — ticket #7 Keycloak was already marked "Hard blocker on the Tailscale funnel ticket: a public funnel without auth would leak PII" BEFORE the incident happened. The failure was that a parallel work stream didn't flow through this project's gate.

Board

board-westside-streamlit — kanban for all work on this project. Columns: Backlog → Todo → Next Up → In Progress → Needs Approval → Done. Review gate at Backlog → Todo (per feedback_ticket_review_gate and sop-ticket-scope-review).

Status — 2026-04-23 (live)

2026-04-23 state. App code on main: 211-line app.py with Keycloak-OIDC admin gate + Contracts + Jerseys sections (filters, parent-cluster detection, email cohort export). Postgres role westside_streamlit_ro live in basketball-api prod (migration 044, 6-table allowlist — not 14 as the orphan arch-domain-westside-ops note claims; that note is superseded). Kustomize overlay committed in pal-e-deployments/overlays/westside-streamlit/prod/. Zero pods running. Substrate audit 2026-04-23 found 4 critical-path gaps: no ArgoCD Application (nothing syncs the overlay), no Harbor project, no Keycloak client in terraform (#10 was closed on the board without the code landing), no Woodpecker registration. 6 new tickets filed 2026-04-23 to close the gaps — see table below. Marcus's content questions (contracts ✓, jerseys ✓, monthly, tournament) are answered by existing app.py plus two new content sections (C1, C3) + one GRANT migration (C2). Critical path to Marcus-sees-data: S1 + S2 + #3 + #6 applied → pod up → C1 + C2 + C3 merged → O1 walkthrough.

Phase # Title Column Status (2026-04-23)
Substrate pal-e-services#60 S1 — add westside-streamlit to var.services (Harbor + ArgoCD Application) backlog NEW — awaiting /review-ticket
Substrate pal-e-services#61 S2 — Keycloak client in terraform (supersedes the closed-without-code #10) backlog NEW — awaiting /review-ticket
Substrate #3 Woodpecker CI pipeline (.woodpecker.yaml + repo activation) todo Prior review APPROVED (review-934); unblocked once #60 lands
Substrate #6 Tailscale ingress (needs body re-scope: private, NOT funnel) backlog Re-scope body → then /review-ticket
Substrate #12 Deployment Secret + env wiring for Keycloak (child of #7) next_up Verify still needed after #61 — SOPS secret already committed in overlay
Content #14 C1 — app: Monthly subscription section backlog NEW — awaiting /review-ticket
Content basketball-api#510 C2 — migration 048: GRANT tournament + registrations to ro role backlog NEW — awaiting /review-ticket
Content #15 C3 — app: Tournament registration section (blocked by #510) backlog NEW — awaiting /review-ticket
Onboard #16 O1 — Marcus onboarding: browser-SSO + walkthrough (final ticket) backlog NEW — awaiting /review-ticket
Deferred #1 Track cash jersey payments backlog Enhancement — post-reachable
Deferred #8 Extend cohort export to Declined + Jerseys-unpaid backlog Enhancement — post-reachable
Done #2, #11, #13 Dockerfile (#9), streamlit-keycloak integration (merged via #13) closed Merged
⚠ Drift #10 Keycloak client (board said qa but code never landed) closed (incorrectly) Superseded by pal-e-services#61

Deferred scope from the incident postmortem

  • Rotate + remove hardcoded PGURL fallback in ~/pal-e-streamlit/app.py:8 (the orphan incident directory)
  • Retire orphan: delete pal-e-streamlit namespace + hostname + on-disk ~/pal-e-streamlit directory after the proper westside-streamlit deploy is live (bundled into #4)
  • Retire orphan: delete empty westside-ops namespace (bundled into #4)
  • Tailscale funnel access log audit for the 4-hour exposure window
  • Hook proposal: pre-commit / pre-apply check that flags tailscale.com/funnel: "true" without a documented auth layer (captured in feedback_funnel_requires_auth)
  • Hook proposal: block kubectl apply of new prod-adjacent ingresses without a matching ArgoCD Application

Milestones

  • Milestone 1 — "Marcus can see his data": tickets #2, #5, #7-decomposed, #4, #6 all merged and deployed. Marcus signs into basketball-landing on his phone, opens westside-streamlit, sees the dashboard. No Tailscale client. Target: as soon as reachable bundle clears.
  • Milestone 2 — MCP remote: HTTP remote MCP server exposing the same data surface for Claude-on-phone queries. Deferred — scoped but not ticketed.

Repos

Repo Platform Role Status
forgejo_admin/westside-streamlit Forgejo Streamlit app source 3 commits on main, PR #9 open (Dockerfile)
forgejo_admin/basketball-api Forgejo Database owner — PRs for migration 040/041 and future ACL changes land here PR #435 open (Postgres RO role, fix round in progress)
forgejo_admin/pal-e-deployments Forgejo Kustomize overlay + ArgoCD Application (to be created in #4) Partial overlay committed via incident PR #109 (ingress.yaml only); full overlay pending #4