Story-3386 · functions lane · RFD-MATTER-006 F2

A creator can no longer open a matter they cannot enter

Matter create used to be editor-only, required an editor lead, and could leave the creator off the roster. Opening create by merely granting members matters:write would have exposed every existing firm-wide matter mutation. Story-3386 opens only the create door and makes staffing part of the same transaction as the matter itself.

active member + matters:create
              │
              ├─ names self ───────────────→ creator = lead
              │
              └─ names another active user → named user = lead
                                         └─→ creator = member

missing lead / inactive target → whole transaction rolls back

The trap: a global key opens too many doors

matters:write is a firm-wide key used by older mutation routes. Giving that key to members would solve create by silently granting unrelated powers. The dedicated matters:create permission is instead issued to all three active firm roles and gates only POST /app/matters.

One transaction is the staffing contract

InputDatabase result
Creator names selfOne assignment: creator as lead_counsel.
Creator names another active member/editor/adminNamed user is lead; creator is member.
Additional active same-firm targetsDeduplicated and stored as members, including admins.
Missing leadDTO rejects the request before service work begins.
Inactive, suspended, or cross-firm target403; no matter or assignment survives.

The database seed function locks and validates the creator, lead, and every requested teammate before its first assignment insert. It then writes the compulsory lead and creator under the existing one-lead unique index. The existing matter.created event is now the single combined audit record: its metadata and secondary subjects identify both the creator staffing and the initial lead assignment.

The RLS bridge is temporary because content visibility stays unchanged

A plain member cannot read an unassigned client or insert an unassigned matter. That is correct after create, but it forms a bootstrap loop during create. A narrow security-definer validator locks the supplied active client and creator membership through commit. The matter reader recognizes only the exact transaction-local UUID being created, and the seed clears that UUID as soon as it writes the roster. From then on, the ordinary assignment-based entry gate is the only path.

can_read_matter still denies every committed unstaffed matter. The create branch uses COALESCE(..., false) so an unset PostgreSQL GUC cannot become a NULL authorization result and slip past a PL/pgSQL IF NOT guard.

Proof

ScenarioEvidence
S1A member creates with self as lead, becomes the sole teammate, and opens the new matter.
S2Member, editor, and admin lead targets all work; the creator is a member, immediately opens the matter, and one combined audit event is stored.
S3Omitting the lead returns 422 and writes no matter.
S4An inactive lead returns 403 and writes no matter; an inactive initial teammate is also atomic.
RLS regressionThe existing cross-matter roster proof stays green, including an unstaffed member denial.
Focused regression40 focused Story/team scenarios pass against isolated PostgreSQL.
Repository regression6,346 tests pass and 3 skip; the sole unrelated order-dependent document-card failure passes on its immediate isolated rerun.

One-sentence recall

Because create must not become a back door to global write or an inaccessible matter, matters:create opens exactly one route and the database atomically seats both compulsory lead and creator before commit.