Story-2629 — Matter representing_side orientation input

🧭 Why this exists

Matter Significance / the comparator reads a document as helpful-vs-hurtful only if it knows which side our counsel acts for. Spike-2628 found that input missing: declared_side already exists per-document (migration 0151, whose document this is), but representing_side — the matter's own position — was absent everywhere in functions. Without it the comparator has nothing to orient against. This story lands the field so the matter captures it permanently at creation; it does not build the comparator's consumption of it (that layer is not built yet).

🎚️ The closed enum

representing_side is a three-value closed enum stored on the matter:

ValueMeaning
principalWe act for the principal adversarial side — Claimant / Plaintiff / Petitioner / Complainant, derived by forum.
opposingWe act for the opposing side — Respondent / Defendant / Accused.
advisoryNo single adverse side — mediation, advisory, multi-party, in-house. The comparator runs neutral in this mode.

🔒 Single source of truth & the advisory-honesty rule

Only representing_side is stored. The adversary label is derived at render from this value plus the forum (via the app's forumAwareRoleLabel()) — never duplicated onto participant rows, because two stored copies drift. And advisory is a required, first-class value: not every matter is a clean two-sided fight, so the field must let counsel say "no single adverse side" rather than force a false for/against. That is the same honesty rule as pramaan-docs ADR-175 — the comparator must never manufacture an orientation that does not exist.

🗄️ Migration 0190 and the backfill

add matters.representing_side  (nullable)
        │
        ▼
UPDATE business.matters SET representing_side = 'principal'   -- every existing row
        │
        ▼
ALTER … SET NOT NULL, server_default 'principal', + CHECK IN (principal, opposing, advisory)
        │
        ▼
COMMENT ON COLUMN …            -- byte-identical to the model, so `alembic check` stays clean

The backfill sweeps all existing matters to principal in the same migration. Rationale: staging is dummy data — no clients, no production — so a clean sweep is safe, and principal is the modal real case, giving the comparator a testable orientation. Real matters set the value explicitly at creation going forward. The NOT NULL plus 'principal' server_default is a staging safety net for direct inserts; the create DTO still requires an explicit value, so the API boundary never silently defaults a real matter. Downgrade drops the CHECK and the column.

📜 The contract change

SurfaceChange
RepresentingSide StrEnumNew, in src/shared/constants/firm_status.py.
MatterCreateRequestrepresenting_side required — validated against the closed enum, so an omitted or unknown value is a 422.
Read DTOPresent on MatterListItem (inherited by MatterDetailView) so the app can display it.
OpenAPIopenapi/app-api.json regenerated so pramaan-app can codegen the new field. admin-api.json unaffected — the matter DTO does not surface there.

Producer only: this ticket makes the field exist and be captured. The pramaan-app matter-creation UI and the comparator both consume the contract in later, separate tickets. The per-document declared_side split is preserved untouched (sibling of Epic-2624 / Indigo).

🎯 Recall

The matter stores one orientation fact — principal, opposing, or advisory — the adversary is derived not duplicated, advisory is always available so no fight is faked, and every existing dummy matter starts at principal.