participant.side + filing.filed_by_participant_idThe Record shows all matter material — our filings, the opponent's, and the court's. Until now the register surfaced what (filing type) and state, but never whose filing it is. A counsel reading the full record could not answer "show me everything the other side filed" or "everything from Vega Capital". The 2026-06-21 Filing-By decision fixes that by adding two small fields to the existing model — no new participant entity (pramaan-docs ADR-1179 is reused, not rebuilt).
ours or opposing depending on whom the firm acts for — so side is a structural axis, not a firm-editable vocabulary. Fixed four values; default others; counsel classifies.filed_by_participant_id adds that link, NULL until classified.| File | Change |
|---|---|
src/shared/constants/firm_status.py | New ParticipantSide StrEnum: ours / opposing / court / others. Distinct from the wizard bucket MatterPartySide (which maps to role_type). |
src/domains/matter/entity.py — MatterParticipant | side column (String(32), server_default 'others', NOT NULL) + CHECK matter_participants_side_valid. |
src/domains/matter/entity.py — Filing | filed_by_participant_id (nullable Uuid) + composite FK (firm_id, matter_id, filed_by_participant_id) → matter_participants (RESTRICT, MATCH SIMPLE so NULL filers are unconstrained) + partial index ix_filings_firm_filed_by. |
alembic/.../0115_participant_side_filing_filed_by.py | Additive migration off head 0114 (d4a7c1e9b305). Single head; column comments byte-identical to the models so alembic check is clean. downgrade() drops both. |
src/domains/matter/dtos/identity.py | side on MatterParticipantCreateRequest (default others), MatterParticipantUpdateRequest (optional), and MatterParticipantView. |
src/domains/matter/identity_service.py | Create sets side; update reclassifies it; view + audit metadata + diff/summary snapshots carry it. |
src/domains/proceeding/dtos/proceeding.py | filed_by_participant_id on FilingCreateRequest + FilingView. |
src/domains/proceeding/service.py | create_filing validates the filer is in this matter (reuses _active_participant) and records it; _filing_view echoes it. |
openapi/app-api.json | Regenerated: ParticipantSide schema, side, and filedByParticipantId on the participant + filing contracts. admin-api.json unchanged. |
tests/integration/test_app_filing_by_party.py | System-boundary coverage (real requests, real DB). |
Pre-launch (per the decision). side ships with server_default 'others', so every existing participant row is classified the moment the column lands — no zero/NULL state. filed_by_participant_id is nullable by design (most filings start unattributed).
single alembic headround-trip greenalembic check cleanRLS firm-scopedopenapi regenerated1028 passed
The integration test drives real POST/GET/PATCH through the router against a freshly provisioned firm — including the zero-input first run (side omitted ⇒ others; no filer ⇒ null):
POST /app/matters/1/participants {entityId, roleType:"opposing_adversarial_side", side:"opposing"} → side == "opposing"
POST /app/matters/1/participants {entityId, roleType:"witness"} (side omitted) → side == "others"
PATCH /app/matters/1/participants/{id} {side:"court"} → side == "court"
POST /app/matters/1/participants {side:"plaintiff"} → 422 (out-of-enum)
POST /app/matters/1/proceedings/{pid}/filings {filingTypeId, title, filedByParticipantId} → filedByParticipantId echoed
POST /app/matters/1/proceedings/{pid}/filings {filingTypeId, title} (no filer) → filedByParticipantId == null
POST /app/matters/1/proceedings/{pid}/filings {filedByParticipantId: } → rejected (4xx)
FN-1 is the data spine only. The FILING-LINK derive extension (carrying filed_by → name + label + side into the register), the Record's "Filing By" column + Side badges + group/slice, page-sum summaries, and the record-wide Party/Source for loose documents are separate follow-ups on the same register.
Closes pramaan-io/pramaan-spec#1531.