Story-2787 — Representing-side posture migration

Why this change exists

The old matter field mixed two facts: whether a matter is adversarial and which adversarial side the firm represents. That made principal and opposing carry role labels that belong on participants. Spike-2786 pinned the durable contract: the matter stores only its posture, while a participant label carries the represented side.

The new contract

ValueMeaning
adversarialThe matter has opposing sides. This field does not say which side the firm represents.
advisoryThere is no single adverse side, such as advisory, mediation, or in-house work.

The Python enum, database CHECK constraint, server default, DTO descriptions, and OpenAPI schema all expose exactly these two values.

How existing staging rows move

principal ─┐
           ├──> adversarial
opposing  ─┘

advisory  ────> advisory

There is no participant-label backfill. Spike-2786 confirmed that staging has no real clients, so the old directional distinction is disposable test data and both old values map straight through.

Migration safety

Migration 0202 drops the old CHECK, transforms rows, changes the default and column comment, and installs the new CHECK. Existing RLS policies, grants, indexes, and partial-index predicates stay unchanged; RLS is explicitly reasserted. The downgrade maps adversarial to principal, the valid old default, because the collapsed source value cannot be reconstructed.

Verification

Contract tests pin the two enum members, ORM CHECK/default, legacy-row SQL, RLS statement, migration parent, and byte-identical column comment. The repository gate additionally runs the complete test suite, Ruff, changed- surface mypy, OpenAPI regeneration, Alembic upgrade/downgrade/upgrade, and the schema-drift check.

One sentence to remember: a matter now records only adversarial versus advisory posture, leaving the represented side to participant labels.