# UAT gap register — Phase 5C operational interface

Baseline commit: `303b29c` (Phase 5B). Every row below was reproduced by an
executed test against real services with non-staff personas and synthetic data.
No gap is inferred, speculative or carried over from an earlier phase. Verified
outcomes that required no change are recorded as PASS so the register is a
complete account of what was inspected, not only a defect list.

Classification: PASS, UX GAP, INTEGRATION GAP, AUTH GAP, DATA GAP, REPORTING GAP,
OUT OF SCOPE.
Severity: HIGH (blocks or corrupts the journey), MEDIUM (blocks a step or hides
required evidence), LOW (cosmetic or discoverability).

## Fixed in Phase 5C

| ID | Classification | Severity | Finding | Evidence | Resolution |
| --- | --- | --- | --- | --- | --- |
| 5C-01 | UX GAP | MEDIUM | The operational "Reconcile invoice" screen rendered three empty formsets. Reconciliation replaces the complete allocation, confirmation and adjustment set, so submitting the screen unchanged silently discarded the deterministic automatic allocation prepared by `prepare_service_invoice`, leaving an unfinalizable draft. | `test_invoice_reconcile_prefills_and_preserves_prepared_allocation` (prefill assertion failed before the fix) | `commercial_workflows.forms_for` now builds the initial sets from `invoice_lines` and `invoice_allocations`, exactly as the trusted Admin does, enables `can_delete`, mirrors the Admin's management-form bounds and adds a reconciliation note field. The authoritative service still revalidates every input. |
| 5C-02 | UX GAP | MEDIUM | The operational quotation-line screen always rendered one spare blank line. Because the frozen `LineForm` has `initial=0` on the required `discount` and `tax_rate` fields, the untouched spare row counted as *changed* and failed validation on every required field, so an existing draft could not be saved without filling or deleting the spare row. | `test_quotation_revision_after_rejection_reaches_an_approved_quote` (spare-row submission returned HTTP 400 before the fix) | The formset is now constructed with one explicitly seeded empty spare row, so an untouched row is recognised as empty. A completed row must still state every value, including `discount` and `tax_rate`. The frozen `LineForm` and the trusted Admin are unchanged. |
| 5C-03 | UX GAP | LOW | The quotation-line screen offered every catalogue spare part, including parts incompatible with the job's device. `set_quotation_lines` refuses such parts, so the offer could only ever fail. | `test_quotation_lines_offer_only_compatible_parts` | The screen now offers only parts compatible with the exact device model or variant, plus any part the draft has already priced, so an existing selection is never silently dropped. |
| 5C-04 | UX GAP | MEDIUM | Every parts operation rendered the commercial workflow template and showed no parts evidence: no demand, no reservation, no custody or disposition state and no visible stock. An operator had to leave the job to reason about a reservation or consumption. | `test_parts_workflow_evidence_and_signed_command_replay` | Added `operations/parts_workflow.html` and a scoped evidence context: this job's parts requests with lines, all reservations with their disposition, issued parts with resolved quantities, and the actor-visible stock positions of the job's compatible parts. |
| 5C-05 | UX GAP | LOW | Customer-owned device registration accepted a `product_variant` belonging to a different product model. `locked_catalog` rejects the combination, so the operator only saw an opaque service failure after submitting. | `test_device_registration_rejects_a_variant_of_another_model` | `RegistrationForm.clean` reports the mismatch on the field. The authoritative catalog lock still enforces it. |

## Verified, no change required

| ID | Classification | Severity | Finding | Evidence |
| --- | --- | --- | --- | --- |
| 5C-P1 | PASS | — | The complete non-staff operational journey runs through HTTP from appointment intake to closure with parts consumption, a single part returned, QC pass, invoice, payment, handover and closure. | `test_operational_technical_and_payment_forms` |
| 5C-P2 | PASS | — | Engineer assignment, reassignment and unassignment all succeed; assignment history and the end reason are retained; an unassigned engineer loses access to the diagnosis stage. | `test_engineer_reassignment_and_unassignment_through_operational_ui` |
| 5C-P3 | PASS | — | Diagnosis, finding, completion, repair planning, action performance and completion are reachable only by the currently assigned engineer. | `test_operational_technical_and_payment_forms`, `test_new_endpoint_persona_denial_and_cross_company_idor` |
| 5C-P4 | PASS | — | Quality control requires an independent eligible inspector; failed QC returns the case to `DIAGNOSED` and a second execution and QC record are created. | `test_qc_rework_loop_through_operational_ui` |
| 5C-P5 | PASS | — | Performed rework scope is refused with "Repair scope changed; submit and approve a new quotation revision" until a revised quotation is approved. This is the frozen commercial/technical coupling, correctly surfaced by the operational screen. | `test_qc_rework_loop_through_operational_ui` |
| 5C-P6 | PASS | — | Rejected quotation blocks work; warranty responsibility needs no customer payment and still allows delivery. | `test_rejected_customer_quote_cannot_perform_work`, `test_warranty_responsibility_needs_no_customer_payment` |
| 5C-P7 | PASS | — | Parts receipt, request, approval, reservation, issue, consumption and return are all reachable and reconcile against the ledger. Reservation release restores available stock and permits a second reservation. | `test_reservation_release_then_reserve_again`, `JourneyTests.reconcile` |
| 5C-P8 | PASS | — | Replaying an identical signed parts receipt submission returns HTTP 400 and posts nothing; the ledger and stock on hand are unchanged. | `test_parts_workflow_evidence_and_signed_command_replay` |
| 5C-P9 | PASS | — | Quotation rejection then revision produces revision 2 as a prefillable DRAFT; the spare row adds a line; re-submission and approval produce an invoice for the approved total. | `test_quotation_revision_after_rejection_reaches_an_approved_quote` |
| 5C-P10 | PASS | — | Only consumption is prepared automatically. A labour-only invoice draft has no lines until labour is explicitly confirmed, and then bills exactly the confirmed quantity. This is the frozen reconciliation contract. | `test_quotation_revision_after_rejection_reaches_an_approved_quote` |
| 5C-P11 | PASS | — | Handover is refused while a finalized invoice carries an outstanding balance, and is permitted after an explicitly authorized financial release. Repeated attempts never create a release. | `test_handover_needs_settlement_or_an_authorized_financial_release` |
| 5C-P12 | PASS | — | Payment reversal voids the payment, retains the receipt, restores the balance and remains unavailable to the payment-intake persona. | `test_payment_reversal_restores_the_outstanding_balance` |
| 5C-P13 | PASS | — | Customer-owned device registration is atomic: a missing required identifier, a duplicate identity or a failure after device creation leaves no orphan device and no ownership row. | `test_customer_owned_device_registration_is_scoped_and_atomic` |
| 5C-P14 | PASS | — | Cross-company and cross-center requests return 404 for the record and 403 for the center, on every new mutation endpoint, for GET and POST. | `test_new_actions_csrf_idor_stale_get_and_persona_isolation`, `test_new_endpoint_persona_denial_and_cross_company_idor` |
| 5C-P15 | PASS | — | A second engineer scoped to another center in the same region, and a department-scoped engineer, both receive 404 for the operational workflow; a region-scoped engineer receives 200. | `test_region_center_and_department_scope_at_new_boundaries` |
| 5C-P16 | PASS | — | POST without a CSRF token is 403, PUT is 405, and a stale signed form is 400 without performing any service call. | `test_new_actions_csrf_idor_stale_get_and_persona_isolation` |
| 5C-P17 | PASS | — | All 15 operational screens share the shell, declare the viewport, keep the skip link and navigation, avoid fixed desktop widths and data tables, and put a CSRF token in every POST form. | `test_operational_screens_mobile_and_desktop_contract` |
| 5C-P18 | PASS | — | SLA observation is `DUE_SOON` at the exact due instant, `OVERDUE` after one microsecond, `CANCELLED` after cancellation, and monitoring does not duplicate an escalation. | `test_sla_exact_due_overdue_then_cancellation` |
| 5C-P19 | PASS | — | A workflow notification is idempotent per event, and a send with no configured provider leaves it `FAILED` without disturbing the recorded intake. | `test_appointment_parts_paid_closed_reconciles` |
| 5C-P20 | PASS | — | Reconciliation: ledger, custody, dispositions, quotation, decision, invoice, allocation, receipt, settlement, SLA completion, notification count and the operational, inventory, commercial and service reports all agree with the recorded journey. | `JourneyTests.reconcile` |
| 5C-P21 | PASS | — | No schema, permission or lifecycle change was introduced; `makemigrations --check` reports no drift and no historical migration was touched. | `python manage.py makemigrations --check` |
| 5C-P22 | PASS | — | Communication provider delivery is a configured external integration; UAT verifies the boundary and its failure mode only, never an external transport. | `test_appointment_parts_paid_closed_reconciles` |

## Out of scope

| ID | Classification | Severity | Item | Reason |
| --- | --- | --- | --- | --- |
| 5C-O1 | OUT OF SCOPE | — | External SMS/email delivery to a real customer. | Phase 5B froze the communications provider boundary; UAT verifies authorization, idempotency and failure handling without a live transport. |
| 5C-O2 | OUT OF SCOPE | — | Headless-browser pixel rendering at exactly 390px and 1366px. | No browser automation is available in this project. The layout contract is verified structurally against the shared stylesheet and progressive-enhancement script instead. |
| 5C-O3 | OUT OF SCOPE | — | Serialized-stock unit selection for reservations, issues, consumption and returns. | The UAT part is `NOT_SERIALIZED`. The screens expose the existing unit field for serialized parts, but exercising unit custody requires a separate serialized-part journey. |
| 5C-O4 | OUT OF SCOPE | — | Intake accessory return quantities and condition notes at handover. | The frozen handover form and service support accessory reconciliation; no UAT fixture records intake accessories, so no variant was fabricated for it. |
| 5C-O5 | OUT OF SCOPE | — | Stock counts, adjustments, transfers, defective-component recovery and goods-receipt documents. | These are inventory administration workflows with no service-case binding and no Phase 5C operational entry point. |

## Severity summary

| Severity | Fixed | Unresolved |
| --- | --- | --- |
| HIGH | 0 | 0 |
| MEDIUM | 3 (5C-01, 5C-02, 5C-04) | 0 |
| LOW | 2 (5C-03, 5C-05) | 0 |

No AUTH GAP, DATA GAP or REPORTING GAP was found. All five verified gaps were
presentation-only; in every case the authoritative service already refused the
unsafe input, and Phase 5C only removed the misleading or destructive interface
path to it.