[Story-1130] Add cacheable matter config bootstrap aggregate

Story 1130Epic None17 files · +1133/−2

PR #288 · pramaan-io/pramaan-functions · merged 2026-06-09

Why this matters

Create-matter screen fans out to five services — every time.

Slow opens, stale dropdowns, one firm-wide read model missing.

Stale cache. The dropdown shows filing types that no longer exist, hides ones that were just added.

The fix — Catalog once per firm, stamp permissions per visit, pull the card on every mutation.

How it flows

sequenceDiagram
  autonumber
  participant visitor as Visitor (request)
  participant librarian as Librarian (service + cache)
  participant catalog as Catalog card (DB row)
  visitor->>visitor: opens 'create matter' form
  visitor->>librarian: request with firm_id + user
  librarian->>catalog: read firm card
  catalog->>catalog: forums, filingTypes, proceedingTypes, participantLabels
  librarian->>visitor: catalog + fresh permission stamp
  visitor->>visitor: form ready, all four dropdowns populated
  catalog-->>librarian: admin mutates a card → librarian pulls it

matter_config_bootstrap_service.py · +254 — new methods

Methodin → outwhat it does
MatterConfigBootstrapService(class)
_permission_context(session, current_user, firm_id) → AppMeView
_load_catalogs(session, firm_id) → MatterConfigBootstrapCatalogs

What changed, file by file

Builds the aggregate matter-config read model (permission context plus catalogs) for the new create-bootstrap endpoint.

new classsignature
MatterConfigBootstrapService(class)
_permission_context(session, current_user, firm_id) → AppMeView
_load_catalogs(session, firm_id) → MatterConfigBootstrapCatalogs

Firm-scoped persisted cache with TTL metadata and write-path invalidation for the matter config bootstrap payload.

new classsignature
MatterConfigBootstrapCache(class)
get_fresh_payload(session, firm_id) → dict[str, Any] | None
put_payload(session, firm_id, payload, now) → None
invalidate_firm(session, firm_id, stale_reason) → None
invalidate_current_firm(session, stale_reason) → None

Alembic migration that creates the new firm-scoped bootstrap cache table used by the aggregate service.

new methodsignature
upgrade() → None
downgrade() → None
New fields: revision : strdown_revision : str | Sequence[str] | Nonebranch_labels : str | Sequence[str] | Nonedepends_on : str | Sequence[str] | None

Response DTOs MatterConfigBootstrapCatalogs and MatterConfigBootstrapResponse for the aggregate bootstrap payload.

new classsignature
MatterConfigBootstrapCatalogs(class)
MatterConfigBootstrapResponse(class)
New fields: permission_context : AppMeView

Wires GET /app/matter-templates/create-bootstrap to the bootstrap service via get_config_screen_bootstrap.

new methodsignature
get_config_screen_bootstrap(response, user, db) → MatterConfigBootstrapResponse

Files changed (17)

File±
alembic/env.py+1
alembic/versions/2e8f4a6b9c01_0086_matter_config_bootstrap_cache.py+107
docs/walkthroughs/story-1130-walkthrough.html+84
openapi/app-api.json ★+131
src/app_api/bootstrap_dtos.py+42
src/app_api/matter_config_bootstrap_service.py ★+254
src/app_api/router_matter_templates.py+20
src/domains/filing_type/service.py+4
src/domains/forum/service.py+4
src/domains/participant_label/service.py+4
src/domains/proceeding_type/service.py+4
src/shared/config_bootstrap_cache.py ★+202
src/shared/db/entities.py+2
tests/contract/test_app_matter_template_create_bootstrap_openapi.py+54
tests/integration/test_app_matter_template_create_bootstrap.py+195
tests/integration/test_rls_isolation.py+3 −2
tests/unit/test_matter_config_bootstrap_cache_migration.py+22