This is the historical walkthrough for F4 of RFD-MATTER-001. Story 3387 later replaced its automatic vacancy-repair policy: lead-loss actions now leave a visible, audited no-lead state. The membership reconciliation entry point remains, but it no longer selects a replacement lead.
| Path | What changed |
|---|---|
PATCH /app/firm/members/{user_uuid}/firm-role |
After the membership role change is flushed, the route calls
reconcile_membership_team_assignments. If the target led matters, the seat is
left vacant and the matter is flagged with no lead. The membership mutation still
succeeds. |
POST /app/firm/members/{user_uuid}/deactivate |
After the status change is flushed, the same reconcile entry point removes or demotes the target's team rows and leaves a vacated lead seat unfilled. A no-lead result is returned as a warning, not a refusal. |
The app can now ask what will happen before showing the confirmation dialog:
GET /app/firm/members/{user_uuid}/role-change-preview?roleId=<role_uuid>
GET /app/firm/members/{user_uuid}/deactivate-preview
Both return:
{
"targetUserId": "...",
"hasWarnings": true,
"consequences": [
{
"matterId": "...",
"matterNumber": 12,
"matterRef": "M-012",
"title": "ABC v XYZ",
"outcome": "no_lead",
"warning": "No lead counsel remains assigned to this matter."
}
]
}
outcome is either unchanged or no_lead. The firm-role and
deactivate mutation responses embed the same object
as teamChange, describing the consequence that actually happened after the flush.
Assignment mutations produced by the cascade emit the same matter-team audit vocabulary as the direct team endpoints:
| Change | Audit action |
|---|---|
| Member added, demoted, or transferred between team roles | matter.assigned |
| Member removed from a matter team because their firm role/status no longer allows staffing | matter.removed |
| Firm admin opens a matter they are not staffed on | matter.viewed |
matter.viewed is wired through the matter detail read path. It records a coarse
admin off-team open with authoritative_content_read=false; it is access history,
not a claim that counsel read the matter contents.
| Scenario | Proof |
|---|---|
| MT-14 | Demoting the only lead previews and returns a no-lead warning, and the roster discloses no lead. |
| MT-21 | Deactivating the only lead previews and returns a no-lead warning; the mutation is not refused. |
| MT-30 | An admin off-team matter detail read emits matter.viewed. |
| MT-12 | A membership change that vacates the lead emits membership and matter-team audit rows. |
| MT-04 | The existing admin-add-editor scenario stays green with the new matter-team audit wiring. |
make openapi make gen-permissions make check-features bash scripts/run-with-test-db-env.sh uv run pytest tests/steps/test_matter_team.py -q --tb=short uv run pytest tests/contract/test_app_firm_roles_openapi.py -q make test bash scripts/run-with-test-db-env.sh make contract-test PRAMAAN_APP_SCHEMAS_DIR=/Users/am/projects/pramaan-io/pramaan-app/src/gen
Final full-suite result: 3735 passed, 3 skipped. Contract validation generated and
validated 6 fixtures against the app schemas.