Review: Create read-only Postgres role for westside-streamlit
Verdict: NEEDS_REFINEMENT
Board item #936 on
board-westside-streamlit. Forgejo issue: forgejo_admin/westside-streamlit#5 (state: open). Issue body declares target repo as forgejo_admin/basketball-api — cross-repo pattern: tracked on the consumer board, code lands in the schema owner.Template Completeness
Type: Feature. All required sections present per
template-issue-feature:- [x] Type
- [x] Lineage
- [x] Repo (explicit:
forgejo_admin/basketball-api) - [x] User Story
- [x] Context (cites
arch-dataflow-westside-streamlitandfeedback_never_alter_prod_directly) - [x] File Targets (creations + explicit "do not touch")
- [x] Acceptance Criteria (6 items, positive + negative)
- [x] Test Expectations (unit + manual, run command)
- [x] Constraints (role name, grants, denials, password handling, reversibility)
- [x] Checklist
- [x] Related
Traceability
- [x]
story:reachablelabel — verified:story-westside-streamlit-reachableexists and is listed inproject-westside-streamlituser-stories section (Platform + Head Coach role). - [x]
arch:postgreslabel — present on board item. - [ ] arch note MISSING — no
arch-postgresnote found in pal-e-docs viasearch_notes. The issue also referencesarch-dataflow-westside-streamlitandarch-deployment-westside-streamlit; neither exist yet (project page links them as planned architecture artifacts). [SCOPE] Create architecture notearch-postgres(or scope down toarch-deployment-westside-streamlitwhich would cover the DB connection). - [x] Forgejo issue — westside-streamlit#5, open, correctly filed.
File Targets
- [x]
alembic/versions/<timestamp>_add_westside_streamlit_ro_role.py— to be created. Verified~/basketball-api/alembic/versions/directory exists with 001–029 migrations. Naming convention in the repo uses numeric prefixes (e.g.029_add_schedule_tables.py), not timestamps. [BODY] Recommend clarifying filename convention: next sequential number (likely030_add_westside_streamlit_ro_role.py) rather than timestamp, to match repo convention. - [x] Tests directory
~/basketball-api/tests/exists. New filetest_westside_streamlit_ro_role.pyis a clean add. - [x] "Files NOT to touch" section correctly excludes retroactive migration edits and app code.
Repo Placement
Board lives on
board-westside-streamlit (consumer), code change lands in basketball-api (schema owner). Issue body is explicit about this via the ### Repo header. Acceptable cross-repo pattern — the dashboard is the consumer, but the role and grants belong to the database the schema owner manages. Single PR on basketball-api closes this.Dependencies
Independent. Foundation work — no blocking tickets. The only blast-dependent ticket on the board (#939, arch:streamlit-app) will eventually consume
WESTSIDE_STREAMLIT_RO_PASSWORD, but that consumption is not required for this migration to land. No items in in_progress on board-westside-streamlit.Tables verified against app.py queries
Grepped
~/westside-streamlit/app.py. Real tables referenced: players, parents, teams, player_teams, orders, products. The string jersey_orders appears at line 123 but is a WITH jersey_orders AS (...) CTE, not a real table — it reads from orders + products internally. The 6-table list in Constraints is exact and complete.Table existence confirmed in migrations:
parents/players (001), teams (008), player_teams (019), orders/products (013).Acceptance Criteria
6 criteria, all machine-verifiable:
- Role creation with LOGIN — assertable via
pg_roles. - All app.py queries succeed under new role — testable by capturing queries and replaying.
- INSERT / UPDATE / CREATE TABLE all fail — negative assertions with
pytest.raises. - Password stored in
~/secrets/pal-e-services/westside-streamlit.env— verified absent on host, clean add.
One implicit AC gap: no criterion for DELETE / TRUNCATE denial, even though Constraints list them. [BODY] Recommend adding one AC line: "When I connect as
westside_streamlit_ro and run DELETE FROM players, then the query fails." Symmetry with the INSERT/UPDATE cases.Blast Radius
Sibling pattern exists:
westside-ops project uses the same "read-only Postgres role" architectural invariant (see story-westside-ops-spreadsheet-access, arch-deployment-westside-ops). This migration establishes the pattern; westside-ops should follow with its own westside_ops_ro role migration later. Not blocking — the pattern is additive per-consumer.No downstream runtime consumers yet — the role is provisioned before any deployed service uses it. Password rotation story is out of scope (appropriate).
Decomposition
5-minute rule check:
- File targets: 2 (1 migration + 1 test) — within limit.
- Repos touched: 1 (basketball-api) — within limit.
- AC count: 6 (plus 1 recommended DELETE addition = 7) — at limit but all same domain (Postgres grants).
- Estimated agent work: <5 min — schema-only change, template-driven migration.
No decomposition needed.
Recommendation
[SCOPE]Create architecture note for Postgres or westside-streamlit deployment — eitherarch-postgresorarch-deployment-westside-streamlit. Current issue cites an arch doc that doesn't exist in pal-e-docs. Per prior precedent (review-836-r2) this is flagged but does not block dev dispatch if Ava accepts the gap.[BODY]Clarify migration filename convention in File Targets: next sequential numeric prefix (e.g.030_add_westside_streamlit_ro_role.py), not a timestamp — to match the existing001_…029_numbering in~/basketball-api/alembic/versions/.[BODY]Add a DELETE denial acceptance criterion for symmetry with INSERT/UPDATE/CREATE TABLE.
No [LABEL] or [DECOMPOSE] fixes needed. Scope is solid in substance; the gaps are cosmetic + a missing backing architecture note.