Story-2916
Uncurated matter types no longer dead-end create
Design X keeps the read contract stable and fixes the create validator. A matter type with no live offered client-side participant labels now accepts any live participant label from the caller's firm. A curated matter type still admits only its live attached client-side set.
The validator decision
POST /app/matters
│
▼
query live client-side labels offered by the selected matter type
│
├─ non-empty → use that offered set
│
└─ empty ───→ use the live participant-label catalogue for this firm
(covers no mappings and archived-only mappings)
Match the "us" party by id or case-folded name.
No match on either path → existing
pramaan.matter.participant_label_not_allowed validation error
Acceptance behavior
| Scenario | Observed result |
|---|---|
| PLM-02 / PLM-03: uncurated type + live firm catalogue label | Create returns 201 for both participantLabelId and participantLabelName. |
| Type whose only client-side mapping points to an archived label | Read returns an empty offered set; create accepts a live firm catalogue label with 201. |
| PLM-05: curated type + non-offered live label | Create returns the existing 422 pramaan.matter.participant_label_not_allowed. |
| PLM-24: empty catalogue + advisory posture | Create returns 201 without a participant label. |
| Empty catalogue + adversarial label attempt | Create returns the existing 422 domain validation error; no 500 and no matter row. |
Why the response contract stays unchanged
MatterTypeParticipantLabelService.list_client_side_labels,
every DTO, and every route response remain untouched. The read endpoint
still returns the attached set, including an empty list for an uncurated
type. The app-lane consumer can detect that empty list and use the
existing participant-label catalogue endpoint without new codegen.
Red-first and verification evidence
- Before the initial service change, both new no-mapping cases failed with 422.
- The archived-only regression was also observed failing with GET 200 and an empty offered set followed by POST 422. It passes with POST 201 after the validator branches on the live-offered query result.
- The focused ASGI + PostgreSQL suite passes all eight allowed-set and empty-catalogue cases.
-
After synchronizing with
main, the full repository test suite passes:4343 passed, 3 skipped. -
make openapiproduced no snapshot diff.openapi/app-api.jsonretained SHA-256e693a2078145fee1c5e2f042eca4c54be7b05d42232f0e0b2892b7b0129e60c1.
Files
-
src/domains/matter/service.py— validator-side empty-live-offered fallback; the curated allowed-set query is the branch predicate. -
tests/integration/test_app_matters_participant_label_allowed_set.py— full-route coverage for identifier forms, curated rejection, archived-only mappings, and empty-catalogue posture behavior. -
docs/walkthroughs/story-2916-walkthrough.html— this acceptance walkthrough.