Matter types can now name their participant-label options
This Story adds the durable, firm-owned link between the existing matter type and participant label catalogs. It is the database foundation for the Add-Matter picker. It does not change runtime proceeding roles.
What shipped
(firm_id, id)(firm_id, id)firm_id + matter_type_id + participant_label_idis_client_side_option + ordering
The three identity columns form the primary key. Both catalog references include firm_id, so the
database rejects a link that combines rows from different firms. The reverse index supports label-to-type
lookups while the primary key supports type-to-label lookups.
Tenant boundary
- Row-level security is enabled and forced.
- The ordinary firm policy uses
firm_id = app.current_firm_id_or_null()for reads and writes. - The established metadata and support-session policies mirror the two parent catalogs.
- The application role receives only the catalog-style
SELECT,INSERT, andUPDATEgrants.
A two-firm planted check returned only the active firm's mapping when queried as pramaan_app.
A planted cross-firm link failed on fk_matter_type_participant_labels_participant_label.
Migration and metadata
Migration 0203_create_matter_type_label_mapping points to the current head
a4f8c2e6d1b3. It hand-authors the table, named composite FKs, primary key, reverse index,
table and column comments, FORCE RLS policies, grants, and the complete downgrade. The entity is imported
by alembic/env.py, so autogenerate drift detection owns this business-schema table.
The descriptor deliberately differs from historical migration 0047_matter_type_participant_labels.
That older revision changed a JSON column; it did not create this table.
a4f8c2e6d1b3, and
reverified with make openapi plus the migration gate.
Acceptance evidence
| Criterion | Evidence |
|---|---|
| Exact table and entity shape | Five requested columns; composite primary key verified from PostgreSQL catalogs. |
| Firm isolation | FORCE RLS confirmed; two-firm visibility and cross-firm FK planted checks passed. |
| Migration reversibility | upgrade head → downgrade base → upgrade head passed. |
| Metadata parity | alembic check first failed on the planted missing table, then reported no new operations after migration. |
| Index behavior at scale | 100,000 rolled-back mappings: primary lookup 0.121 ms; reverse lookup 0.381 ms, both using their intended indexes. |
| OpenAPI contract | make openapi completed with no snapshot changes, as expected for schema-only work. |
Review gate
| New cloud capability? | None. This is an additive PostgreSQL schema change. |
|---|---|
| Who grants it? | The migration grants existing pramaan_app table privileges and installs RLS policies. |
| What check goes red? | alembic check detects the missing table or metadata drift; RLS/FK planted checks detect tenant-boundary failures. |
| Observed red? | Yes. Before the migration existed, alembic check reported the added table and reverse index. |
| Legacy deleted? | None. This is a new mapping and introduces no compatibility path. |
| Deploy smoke? | Not applicable. No deploy workflow or runtime code path changes in this Story. |
Deliberately deferred
Seed data belongs to Story-2789. The read endpoint and router belong to Story-2790. The representing-side enum migration belongs to Story-2787. This Story adds no seed, DTO, route, agent payload, or client behavior.
Governing issue: pramaan-spec#2788.