feat: Phase-11 field-ownership application (ADR-037 §4–§7) — CWCA provider portal
Summary
The surface-bound field-ownership application (ADR-037 §4–§7), deferred from Plan Y to Phase 11 (the CWCA provider portal) — the field-ownership feature's real first consumer and second tenant.
Plan Y delivered the surface-agnostic evaluation core (ADR-037 §2/§3): AuthzEngine::resolve_field_permission(&Claims, ResourceRef, field, field_owner) -> Result<FieldPermission, ApiError> in crates/craig-authz (FieldPermission { Read, Write, Propose, None }, fixture-proven). This issue tracks the application that consumes it on a real multi-tenant edit surface.
Field ownership is intrinsically cross-tenant (state agency vs external provider). Building §4–§7 ahead of the portal would be either a fig leaf (a synthetic intra-agency surface that mis-fits the state/provider/shared taxonomy) or throwaway (a synthetic host the portal replaces). It lands with its consumer — exactly as craig-composition reads rulesets/ directly rather than consuming an unbuilt bundle axis.
ADR-037 is unedited and is satisfied incrementally: Plan Y = §2/§3, this issue = §4–§7. Epic &50 stays open as the durable tracker until this lands.
Acceptance criteria (ADR-037 §4–§7)
- §4 —
field_ownershipbundle axis. Add thefield_ownership: FieldOwnershipContributionfield toBundleContribution, landing with the consumer surface (mirrors composition'srulesets/-direct pattern; no dormant axis ahead of its consumer). - §5 — per-surface ownership tables.
rulesets/<jurisdiction>/<surface>_ownership.tomlmapping each field to its owner classification (state/provider/shared), loaded + validated at boot on the OWNING backend service; boot fails closed on an unknown field/owner. - §6 — propose/accept/reject queue + field audit.
pending_edits_<surface>table on the owning backend; a non-owner's write to asharedfield is queued (not applied); the owner accepts (single transaction reusingAction::Approve) or rejects; field-attached audit on every read AND write via the existing per-service audit pipeline (ADR-022 outbox). - §7 — BFF reflection + Studio. A per-field permission-map endpoint the BFF calls; lock-icon reflection in the edit template (a reflection of the backend decision, not the source of truth, ADR-004 preserved); a Studio pending-changes view for the owner's accept/reject queue.
- Owning-service rubric (resolve in Phase 11). Pin to
services/craig-casesfor case-attached fields, OR spin a NEWservices/craig-cwca-providerif the CWCA boundary is substantial (field count, distinct session/auth boundary, >2 new tables) — see the rubric in the archived Plan Y body and ADR-037. A new service triggers the full devstack service-add obligation set (OIDC principal, audience mappers on all clients,ResourceTypecoverage, etc.). - The application calls
resolve_field_permissionunchanged (no new authz primitives needed — the evaluation core is done).
References
- ADR-037 —
docs/modules/ROOT/pages/adrs/adr-037-field-ownership-authz.adoc(the Phase-11 spec, incl. the verifiedpending_edits_<surface>shape, single-transaction accept, per-GET read audit, owning-service rubric, encryption-aware PII writes). - Plan Y (archived) —
docs/modules/ROOT/pages/plans/archive/field-ownership-authz.adoc(§ "The Phase-11 hand-off"). - Plan S umbrella —
docs/modules/ROOT/pages/plans/multi-jurisdiction-foundation.adoc(the sequencing authority; service-boundary table). - Implementation guide —
docs/modules/ROOT/pages/implementation-guide.adoc(Phase 11 — Constituent & Provider Portals). - The shipped evaluation core —
crates/craig-authz(AuthzEngine::resolve_field_permission,FieldPermission); registered indocs/modules/ROOT/pages/shared-crates.adoc. - Epic &50 (this issue's parent; kept open as the durable tracker).