[Story-1088] Rebuild matter templates catalog composition

Story 1088Epic None79 files · +9339/−5352

PR #277 · pramaan-io/pramaan-functions · merged 2026-06-08

Why this matters

Matter templates freeze every case at birth

snapshot tables, legacy shapes, locked composition

Lock the case to a frozen template version — forever.

The fix — Template is a recipe. Matter is the plate. Archive the recipe, never delete it while a plate still uses it.

How it flows

sequenceDiagram
  autonumber
  participant cat as Catalog (recipe)
  participant kit as Service (kitchen)
  participant mat as Matter (plate)
  cat->>kit: create template → MT number
  kit->>kit: matter creation
  kit->>mat: copy template defaults
  mat->>mat: add filing / proceeding / label on the fly
  cat->>cat: archive (in-use safe)
  cat->>cat: delete (blocked if in use)

app-api.json · +4315 — new methods

Methodin → outwhat it does
_create_updated_at_trigger(table_name) → None
_drop_updated_at_trigger(table_name) → None
_replace_reference_kind_constraint(include_matter_template) → None

What changed, file by file

Alembic migration creating the new thin matter_templates catalog, three template default-link tables, three matter-owned copy tables, runtime tables, and per-matter RLS guards.

new methodsignature
_create_updated_at_trigger(table_name) → None
_drop_updated_at_trigger(table_name) → None
_replace_reference_kind_constraint(include_matter_template) → None
_drop_policies(table) → None
_enable_catalog_rls(table) → None
_enable_matter_rls(table, matter_expression) → None
_add_staff_with_grant_select_policy(table) → None
_disable_rls_for_drop(table) → None
_create_matter_templates() → None
_create_template_link_tables() → None
_create_matter_copy_tables() → None
_create_runtime_tables() → None
_create_participant_summoner_trigger(table_name) → None
_upgrade_can_read_entity() → None
_downgrade_can_read_entity() → None
upgrade() → None
downgrade() → None
New fields: revision : strdown_revision : str | Sequence[str] | Nonebranch_labels : str | Sequence[str] | Nonedepends_on : str | Sequence[str] | None

Template service: creates, updates, archives, deletes matter templates with duplicate-name checks, catalog-id validation, atomic link replacement, and in-use usage counting.

new methodsignature
_current_user_id(session) → UUID
_dedupe(ids) → list[UUID]
_find_duplicate_name(session, firm_id, name, excluding_template_id) → MatterTemplate | None
_raise_if_name_conflict(session, firm_id, name, excluding_template_id) → None
_get_editable_template(session, firm_id, matter_template_id) → MatterTemplate
_validate_catalog_ids(session, firm_id, entity, ids, field_name) → None
_replace_links(session, firm_id, template_id, filing_type_ids, proceeding_type_ids, participant_label_ids) → None
_count_rows(session, entity, firm_id, template_id) → int
_usage_count(session, firm_id, template_id) → int
_list_item(session, template) → MatterTemplateListItem
_detail_view(session, template) → MatterTemplateDetailView
create_matter_template(session, payload) → MatterTemplateDetailView
update_matter_template(session, matter_template_id, payload) → MatterTemplateDetailView
archive_matter_template(session, matter_template_id) → MatterTemplateDetailView
unarchive_matter_template(session, matter_template_id) → MatterTemplateDetailView
delete_matter_template(session, matter_template_id) → None

Redesign service for add-on-the-fly matter-owned filing, proceeding, and participant-label rows, with live-source validation and display-order sequencing.

new methodsignature
_snapshot_proceeding_kind_view(row) → MatterSnapshotProceedingKindView
_snapshot_filing_type_view(row) → MatterSnapshotFilingTypeView
get_template_snapshot(session, matter_id) → MatterTemplateSnapshotView
_next_display_order(session, entity, firm_id, matter_id) → int
_ensure_live_source(session, entity, firm_id, source_id, field_name) → None
_ensure_matter_owned_name_available(session, entity, firm_id, matter_id, name, excluding_id) → None
create_matter_filing_type(session, matter_id, req) → MatterSnapshotFilingTypeView
update_matter_filing_type(matter_id, matter_filing_type_id, req) → MatterSnapshotFilingTypeView
create_matter_proceeding_type(session, matter_id, req) → MatterSnapshotProceedingKindView
update_matter_proceeding_type(session, matter_id, matter_proceeding_type_id, req) → MatterSnapshotProceedingKindView
create_matter_participant_label(req) → MatterParticipantLabelView
update_matter_participant_label(session, matter_id, matter_participant_label_id, req) → MatterParticipantLabelView

Runtime logic for proceedings, participant-role assignment, and filings, with active-matter checks for type and label references.

new methodsignature
_active_type_for_matter(matter_proceeding_type_id) → MatterProceedingType
_participant_role_view(row, participant, label) → ProceedingParticipantRoleView
_filing_view(row, filing_type) → FilingView
_active_participant(session, firm_id, matter_id, participant_id) → MatterParticipant
_active_participant_label(session, firm_id, matter_id, participant_label_id) → MatterParticipantLabel
_active_filing_type(session, firm_id, matter_id, filing_type_id) → MatterFilingType
list_participant_roles(session, matter_id, proceeding_id) → ProceedingParticipantRoleListResponse
assign_participant_role(session, matter_id, proceeding_id, participant_id, req) → ProceedingParticipantRoleView
list_filings(session, matter_id, proceeding_id) → FilingListResponse
create_filing(session, matter_id, proceeding_id, req) → FilingView

HTTP routes for the new matter-owned filing types, proceeding types, participant labels, per-proceeding participant roles, and per-proceeding filings.

new methodsignature
create_matter_filing_type(matter_id, req, db, _) → MatterSnapshotFilingTypeView
update_matter_filing_type(matter_id, matter_filing_type_id, req, db, _) → MatterSnapshotFilingTypeView
create_matter_proceeding_type(matter_id, req, db, _) → MatterSnapshotProceedingKindView
update_matter_proceeding_type(matter_id, matter_proceeding_type_id, req, db, _) → MatterSnapshotProceedingK
create_matter_participant_label(matter_id, req, db, _) → MatterParticipantLabelView
update_matter_participant_label(matter_id, matter_participant_label_id, req, db, _) → MatterParticipantLa
list_matter_proceeding_participant_roles(matter_id, proceeding_id, db) → ProceedingParticipantRoleListResponse
assign_matter_proceeding_participant_roleasync def assign_matter_proceeding_participant_role( matter_id: int, proceeding_id: UUID, participant_id: UUID, req: ProceedingParticipantRoleAssignRequest, db: AppDbDep, _: Annotated[None, Depends(require_permission("matters:write"))], ) -
list_matter_proceeding_filings(matter_id, proceeding_id, db) → FilingListResponse
create_matter_proceeding_filing(matter_id, proceeding_id, req, db, _) → FilingView

SQLAlchemy models for the matter-owned filing types, proceeding types, participant labels, and the new runtime proceeding-participant-role and filing rows.

new classsignature
MatterFilingType(class)
MatterProceedingType(class)
MatterParticipantLabel(class)
ProceedingParticipantRole(class)
Filing(class)
New fields: matter_template_id : UUID | Nonematter_template_name : str | Noneentity_id : UUID | Nonename : strsource_filing_type_id : UUID | Nonedescription : str | Nonestatus : strdisplay_order : intid : UUIDfirm_id : UUIDmatter_id : UUIDsource_proceeding_type_id : UUID | Nonesource_participant_label_id : UUID | Noneproceeding_id : UUIDparticipant_id : UUIDparticipant_label_id : UUIDfiling_type_id : UUIDtitle : strfiled_at : datetime | Nonecreated_by_user_id : UUID | None

Matter creation logic that resolves a live template and copies its default filing, proceeding, and participant-label rows into the matter's own tables.

new methodsignature
_get_live_template(template_id) → MatterTemplate
_copy_template_defaults(session, firm_id, matter, template) → None

SQLAlchemy models for the thin MatterTemplate catalog row and the three default-link tables linking it to filing, proceeding, and participant-label catalog rows.

new classsignature
MatterTemplate(type)
MatterTemplateFilingType(type)
MatterTemplateProceedingType(type)
MatterTemplateParticipantLabel(type)
New fields: firm_id : UUIDmatter_template_number : intname : strdescription : str | Noneavailability : strcreated_by_user_id : UUID | Nonearchived_at : datetime | Nonearchived_by_user_id : UUID | Nonedeleted_at : datetime | Nonedeleted_by_user_id : UUID | Noneid : UUIDtemplate_id : UUIDfiling_type_id : UUIDdisplay_order : intproceeding_type_id : UUIDparticipant_label_id : UUID

Pydantic request and view DTOs for matter template create, update, list, and detail, including availability enum, MT-number formatting, and usage flags.

new classsignature
MatterTemplateAvailability(class)
MatterTemplateCreateRequest(class)
_trim_name(value) → str | None
_trim_description(value) → str | None
MatterTemplateUpdateRequest(class)
MatterTemplateListItem(class)
MatterTemplateDetailView(class)
New fields: name : strdescription : str | Noneavailability : MatterTemplateAvailabilitydefault_filing_type_ids : list[UUID] | Nonedefault_proceeding_type_ids : list[UUID] | Nonedefault_participant_label_ids : list[UUID] | Noneid : UUIDfirm_id : UUIDmatter_template_number : intmatter_template_number_formatted : strfiling_type_count : intproceeding_type_count : intparticipant_label_count : intusage_count : intis_in_use : boolcan_delete : boolarchived_at : datetime | Nonearchived_by_user_id : UUID | Nonecreated_by_user_id : UUID | None

HTTP routes for matter template CRUD, archive, unarchive, and delete operations guarded by permission checks.

new methodsignature
create_matter_template(req, db, _) → MatterTemplateDetailView
update_matter_template(matter_template_id, req, db, _) → MatterTemplateDetailView
archive_matter_template(matter_template_id, db, _) → MatterTemplateDetailView
unarchive_matter_template(matter_template_id, db, _) → MatterTemplateDetailView
delete_matter_template(matter_template_id, db, _) → Response

Pydantic DTOs for per-proceeding participant-role assignment and matter-owned filing view, list, and create, including the filing status enum.

new classsignature
ProceedingParticipantRoleAssignRequest(class)
ProceedingParticipantRoleView(class)
ProceedingParticipantRoleListResponse(class)
FilingStatus(class)
FilingCreateRequest(class)
FilingView(class)
FilingListResponse(class)
New fields: matter_proceeding_type_id : UUIDproceeding_type_name : strparticipant_label_id : UUIDid : UUIDmatter_id : UUIDproceeding_id : UUIDparticipant_id : UUIDparticipant_name : strparticipant_label_name : strfiling_type_id : UUIDtitle : strfiled_at : datetime | Nonestatus : FilingStatusfiling_type_name : strcreated_by_user_id : UUID | None

Pydantic DTOs for add-on-the-fly matter-owned filing, proceeding, and participant-label create and update requests.

new classsignature
MatterProceedingTypeCreateRequest(class)
MatterProceedingTypeUpdateRequest(class)
MatterFilingTypeCreateRequest(class)
MatterFilingTypeUpdateRequest(class)
MatterParticipantLabelCreateRequest(class)
MatterParticipantLabelUpdateRequest(class)
New fields: matter_template_id : UUID | Noneid : UUIDmatter_id : UUIDsource_proceeding_type_id : UUID | Nonename : str | Nonedescription : str | Nonestatus : MatterSnapshotVocabularyStatus | Nonedisplay_order : int | Nonesource_filing_type_id : UUID | Nonesource_participant_label_id : UUID | Nonetemplate_name : str | Nonesnapshot_reason : str

Matter view DTO updated to surface source matter template id, copied template name, and a compatibility snapshot reason string.

New fields: matter_template_id : UUID | Nonematter_type_id : strmatter_template_name : str | None

Proceeding row updated to carry a per-proceeding matter_proceeding_type_id reference into the matter-owned proceeding type set.

New fields: matter_proceeding_type_id : UUID

Small formatter that renders a firm-local template number into a display reference such as MT-001.

new methodsignature
format_matter_template_ref(number) → str

Files changed (79)

File±
alembic/env.py+11 −5
alembic/versions/9c1d2e3f4a85_0085_matter_template_rebuild.py ★+1179
docs/walkthroughs/story-1088-walkthrough.html+64
openapi/app-api.json ★+4315 −2353
src/app_api/app.py+0 −2
src/app_api/router_matter_templates.py+95 −2
src/app_api/router_matter_types.py+0 −76
src/app_api/router_matters.py+207
src/domains/filing_type/AGENTS.md+4 −3
src/domains/filing_type/service.py+13 −5
src/domains/forum/AGENTS.md+13 −11
src/domains/forum/service.py+0 −8
src/domains/matter/dtos/__init__.py+14 −2
src/domains/matter/dtos/matter.py+32 −21
src/domains/matter/dtos/template_snapshot.py+63 −35
src/domains/matter/entity.py+168 −223
src/domains/matter/identity_service.py+1
src/domains/matter/redesign_service.py+416 −202
src/domains/matter/service.py+162 −62
src/domains/matter_type/__init__.py+3 −3
src/domains/matter_type/dtos/__init__.py+7 −21
src/domains/matter_type/dtos/matter_template.py+100 −32
src/domains/matter_type/dtos/matter_type.py+0 −64
src/domains/matter_type/entity.py+153 −184
src/domains/matter_type/repository.py+0 −86
src/domains/matter_type/seeds/__init__.py+0 −15
src/domains/matter_type/seeds/system_matter_types.py+0 −185
src/domains/matter_type/service.py+0 −85
src/domains/matter_type/template_service.py ★+483 −48
src/domains/participant_label/AGENTS.md+10 −7
src/domains/participant_label/service.py+18 −2
src/domains/proceeding/__init__.py+2 −2
src/domains/proceeding/dtos/__init__.py+14 −8
src/domains/proceeding/dtos/proceeding.py+66 −39
src/domains/proceeding/entity.py+17 −115
src/domains/proceeding/seeds/__init__.py+0 −13
src/domains/proceeding/seeds/system_proceeding_kinds.py+0 −304
src/domains/proceeding/service.py+399 −111
src/domains/proceeding_type/AGENTS.md+10 −5
src/domains/proceeding_type/service.py+18 −2
src/domains/rbr/entity.py+3 −3
src/domains/reference_counter/entity.py+4 −1
src/shared/constants/firm_status.py+2 −1
src/shared/db/entities.py+18 −10
src/shared/formatters/matter_template_ref.py+9
tests/contract/generate_fixtures.py+2 −6
tests/contract/seed.py+1 −1
tests/integration/test_agent_approval_request_rls.py+2 −2
tests/integration/test_agent_run_rls.py+2 −2
tests/integration/test_app_adversary_reads.py+13 −8
tests/integration/test_app_annotations.py+2 −2
tests/integration/test_app_claim_block_refs.py+2 −2
tests/integration/test_app_claim_rbr_links.py+2 −2
tests/integration/test_app_claims.py+2 −2
tests/integration/test_app_clients.py+2 −3
tests/integration/test_app_document_blocks.py+2 −2
tests/integration/test_app_documents.py+2 −2
tests/integration/test_app_filing_types.py+43 −72
tests/integration/test_app_firm_routes.py+1 −1
tests/integration/test_app_forums.py+43 −87
tests/integration/test_app_matter_redesign.py+343 −58
tests/integration/test_app_matter_types.py+0 −495
tests/integration/test_app_matters.py+41 −79
tests/integration/test_app_participant_labels.py+89 −18
tests/integration/test_app_proceeding_types.py+71 −21
tests/integration/test_app_rbrs.py+7 −6
tests/integration/test_app_validation_checks.py+2 −2
tests/integration/test_binder_cache_rls.py+2 −2
tests/integration/test_extraction_claim_promotion.py+2 −2
tests/integration/test_extraction_proposals_rls.py+2 −2
tests/integration/test_rls_isolation.py+130 −19
tests/integration/test_updated_at_triggers.py+2 −3
tests/routes/test_admin_firms.py+6 −6
tests/services/test_firm_service.py+3 −3
tests/shared/matter_model.py+361
tests/unit/test_matter_dtos.py+8 −5
tests/unit/test_matter_redesign_dtos.py+16 −11
tests/unit/test_matter_redesign_schema_migration.py+33 −37
tests/unit/test_proceedings_migration.py+7 −33