# Lifecycle Drift Matrix — vs Canonical Design

> **Scope**: gap analysis fra il design canonico in `docs/INVOICE_LIFECYCLE_DESIGN.md` v0.1 e l'implementazione attuale.
> **Data audit**: 2026-04-25
> **Branch**: `claude/clarify-invoice-state-flow-HVkRa`
> **Decisione di sign-off**: tutti i drift items vengono risolti come `FIX` (allineare il codice al design). La remediation è schedulata in 6 tranche (vedi §4).

## Legenda

### Drift type

| Tipo | Significato |
|---|---|
| `MISSING` | Il design prescrive un artifact / status / handler che non esiste |
| `EXTRA` | Esiste un artifact / status / handler non previsto dal design |
| `WRONG_ORDER` | Sequenza non corretta vs design |
| `WRONG_BINDING` | Riferimento errato (es. check su step sbagliato) |
| `UNREACHABLE` | Status / handler / branch dichiarato ma mai raggiunto in runtime |
| `OBSOLETE` | Codice / data ancora presente ma logicamente rimosso |
| `NAMING` | Solo nomenclatura (semantica corretta, label sbagliata) |
| `CONTRACT` | Comportamento non documentato che dovrebbe esserlo |
| `UI_VISIBILITY` | Visibility expression mancante o errata in annotation FE |

### Severity

| Severity | Criterio |
|---|---|
| `BLOCKING` | Blocca avanzamento lifecycle, porta a stato inconsistente, audit gap SOX, o data corruption |
| `MAJOR` | UX rotta, risk surface, drift che si propaga ad altri artifact |
| `MINOR` | Documentazione, nomenclatura, leggibilità |

### Decisione

Tutti i drift items sono decisi come `FIX` per il sign-off 2026-04-25. La sessione di remediation successiva implementa nelle 6 tranche descritte in §4.

---

## §1 — Drift findings

| # | Artifact | Path | Cosa dice oggi | Cosa dice il design | Drift type | Severity | Fix proposto | Decisione |
|---|---|---|---|---|---|---|---|---|
| **D1** | `ProcessTemplate STANDARD_IT` | `db/data/sap.passive.invoice-ProcessTemplateStep.csv` row `b...0006` | `RISK_ANALYSIS` ha `SeqNumber=60` (esecuzione DOPO `PO_MATCH=40` e `THREE_WAY_MATCH=50`) | `RISK_ANALYSIS` deve avere `SeqNumber=40` (esecuzione PRIMA di matching, in F3 Verifiche). Specularmente `PO_MATCH` → 50, `THREE_WAY_MATCH` → 60 | `WRONG_ORDER` | `MAJOR` | Riallocare `SeqNumber` in CSV; aggiornare i 4 template (STANDARD_IT, TOUCHLESS, CREDIT_NOTE, INTERCOMPANY) | `FIX` |
| **D2** | `PHASE_BOUNDARIES` runtime | `srv/handlers/_invoiceActionsShared.ts:151` | `'COST_ALLOCATION_PENDING': { next: 'PENDING_APPROVAL' }` salta `COST_ALLOCATED` e `COST_VALIDATED` come step intermedi via `confirmPhase` | La transizione `COST_ALLOCATION_PENDING → COST_ALLOCATED/_VALIDATED` è action-driven (`confirmCostAllocation`), non `confirmPhase`. Documentare in commento codice e in design | `CONTRACT` | `MAJOR` | Aggiungere commento esplicativo in `_invoiceActionsShared.ts`; valutare rimozione row `COST_ALLOCATION_PENDING → PENDING_APPROVAL` (escape hatch admin documentato) | `FIX` |
| **D3** | Status `CREDIT_MATCHING` | `srv/process/statusTransitions.ts:51` definito in `STATUS_ORDER` + nessun handler lo setta | Solo `CREDIT_MATCHED` viene scritto da `creditNoteActions.ts:99` | F3_BIS deve avere status di entry `CREDIT_MATCHING` settato all'ingresso del branch quando `confirmPhase` da `RISK_ASSESSED` rileva `SDIDocumentType='TD04'` | `UNREACHABLE` | `MAJOR` | Aggiungere boundary `RISK_ASSESSED → CREDIT_MATCHING` quando `SDIDocumentType='TD04'`; aggiungere handler entry-point in `creditNoteActions` che setta `CREDIT_MATCHING` come status di "in lavorazione". `CREDIT_MATCHING` viene mantenuto reachable | `FIX` |
| **D3-bis** | Status `CREDIT_UNMATCHED` | `srv/handlers/creditNoteActions.ts:116` setta `ProcessingStatus='CREDIT_UNMATCHED'` | `STATUS_ORDER` non lo contiene (rimosso in commento `srv/process/statusTransitions.ts:40`); `novaInvoiceMCP.ts:199`, `autoAdvancementJob.ts:52`, `adminOpsActions.ts:355` ancora lo referenziano | `CREDIT_UNMATCHED` non deve esistere. Failure path: lasciare `RISK_ASSESSED` (o status pre-branch) + raise exception `CREDIT_NOTE_MATCH_MISSING` OPEN | `OBSOLETE` | `BLOCKING` | Rimuovere set in `creditNoteActions.ts:116` (sostituire con raise exception); rimuovere referenze da `novaInvoiceMCP`, `autoAdvancementJob`, `adminOpsActions` | `FIX` |
| **D4** | `ProcessStepCheck CREDIT_NOTE_MATCH_MISSING` | `db/data/sap.passive.invoice-ProcessStepCheck.csv:46` row `aaaaaaaa-aaaa-aaaa-aaaa-000000000004` | `step_ID = DUPLICATE_CHECK` | `step_ID = CREDIT_NOTE_MATCHING` (semantica corretta — appartiene a F3_BIS, non a F3) | `WRONG_BINDING` | `MAJOR` | Update CSV `step_ID`; verificare downstream auto-dispatch + UI references | `FIX` |
| **D5** | `ProcessStep PARKING` + `REVERSAL` | `db/data/sap.passive.invoice-ProcessStep.csv:11`, `:13` | Esistono nel catalog ma in nessuna `ProcessTemplateStep.csv` | Devono essere classificati come **deviation steps** (eseguiti via action diretta). Aggiungere colonna `IsDeviation Boolean` in `ProcessStep.cds` schema | `MISSING` (per template) o `CONTRACT` (per documentazione) | `MINOR` | Add column `IsDeviation`; aggiornare `processEngine.advanceAfterStep` per skipparli | `FIX` |
| **D6** | FE `ProgressFormatter` | `app/passive-invoice-manager-ui/webapp/ext/progress/ProgressFormatter.ts:3` | `STATUS_STEP` mappa 5-step (Reception+BP unificate) | Design canonico è 6-step (vedi §6.1). `BP_RESOLVED` deve passare a step `2` non `1` | `WRONG_ORDER` | `MAJOR` | Update `STATUS_STEP` mapping; aggiornare i18n (aggiungere `wizard_step_bp`); update `WizardStateModel.ts` per gestire 6 step + sub-step F3_BIS condizionale; update `processFlowStatuses.test.js` | `FIX` |
| **D7** | `COST_ALLOCATION_REQUIRED` classification | `db/data/sap.passive.invoice-ProcessStepCheck.csv:45` (step_ID=COST_ALLOCATION) + `srv/matching/nonPoPhaseGate.ts:124` (raised via boundary) | Check classificato come step check ma raised come **phase gate**. Ambiguità step-vs-gate | Distinzione esplicita: aggiungere flag `IsPhaseGate Boolean` in `ProcessStepCheck.cds` | `CONTRACT` | `MAJOR` | Add column `IsPhaseGate` + valorizzare `true` per `COST_ALLOCATION_REQUIRED` e `CREDIT_NOTE_MATCH_MISSING`; aggiornare auto-dispatch policy per skipparli da chain step | `FIX` |
| **D8** | F3 internal ordering non garantito | `srv/handlers/complianceDuplicateActions.ts:59`, `complianceFiscalActions.ts:96`, `complianceRiskActions.ts:61` | Ognuno dei 3 handler setta `ProcessingStatus` solo se `_isBefore(current, target)`. Eseguendo i 3 handler in qualsiasi ordine, lo status finale è non deterministico | F3 deve garantire ordering canonico `DUPLICATE_CHECK → VALIDATED → RISK_ASSESSED`. Garantito da: (a) `processEngine.advanceAfterStep` per modalità auto, (b) prerequisite check sui 3 handler manuali | `CONTRACT` | `MAJOR` | Aggiungere prereq guard nei 3 handler: `validateFiscal` rejecta se `DuplicateCheckStatus='NOT_CHECKED'`; `analyzeRisk` rejecta se `FiscalValidationStatus` null. Test T-4 da §7.7 | `FIX` |
| **D9** | `TOUCHLESS` template idempotency contract | `db/data/sap.passive.invoice-ProcessTemplateStep.csv:12-21` | TOUCHLESS ha tutti gli step `IsAutomatic=true` ma nessuna documentazione del contratto idempotency richiesto agli handler | Documentare in CLAUDE.md `Conventions`: handler bindati a step `IsAutomatic=true` devono essere idempotenti. Audit dei 10 handler | `CONTRACT` | `MINOR` | Aggiungere sezione CLAUDE.md; auditare i 10 handler bindati a step | `FIX` |
| **D10** | Facets ObjectPage mutex mancante | `app/annotations.cds` `SectionMatching` row 251 + `SectionNonPO` row 276 | Entrambi sempre visibili (nessun `@UI.Hidden` expression). L'utente vede 2 tab per F4 | `SectionMatching` ha `@UI.Hidden: HideIfNonPo`; `SectionNonPO` ha `@UI.Hidden: HideIfPo` | `UI_VISIBILITY` | `MINOR` | Aggiungere 2 virtual computed in `srv/handlers/invoiceReadEnrichment.ts`; applicare in annotations | `FIX` |
| **D10-bis** | Facet `SectionRegistrazione` mancante | `app/annotations.cds` (no row corrispondente) | F6 (Posting + Archiving + Reversal) non ha facet ObjectPage dedicato. Campi posting/archive sparsi in `SectionMatching` | Design §6.2: facet `SectionRegistrazione` per F6 con sub-sezioni Posting / Archive / Reversal | `MISSING` | `MINOR` | Creare nuovo `UI.CollectionFacet` `SectionRegistrazione`; spostare campi `PostedInvoiceNumber`/`PostingDate`/`ArchiveDocumentId`/`ArchiveStatus`/`ReversalStatus`/`ReversalDocNumber` | `FIX` |
| **D11** | `STATUS_ORDER` parity | `srv/process/statusTransitions.ts:42-60` | Parity con `ProcessingStatuses.csv` già garantita da `assertStatusOrderParity` | OK — mantenere | (no drift) | — | nessuno | OK |
| **D12** | `STEP_TO_STATUS` map | `srv/process/statusTransitions.ts:67-81` | Manca `CREDIT_NOTE_MATCHING → CREDIT_MATCHED` | Aggiungere row per coerenza | `MISSING` | `MINOR` | Add row in `STEP_TO_STATUS`; verificare downstream `canExecuteStep` | `FIX` |
| **D13** | `PHASE_ADVANCE_MAP` legacy | `srv/handlers/_invoiceActionsShared.ts:66-82` | Map statico legacy "tenuto per shape contract test". Drifta con `PHASE_BOUNDARIES` runtime | Eliminare `PHASE_ADVANCE_MAP` — il test va riscritto contro `buildPhaseBoundaries()` | `OBSOLETE` | `MINOR` | Remove map; aggiornare `processFlowStatuses.test.js` | `FIX` |
| **D14** | `ROLLBACK_TARGETS` set | `srv/handlers/_invoiceActionsShared.ts:88-91` | Contiene `NEW, BP_RESOLVED, DUPLICATE_CHECK, MATCHING_PENDING, COST_ALLOCATION_PENDING, PENDING_APPROVAL, POSTING_PENDING` | Manca `CREDIT_MATCHING` come target (TD04 in `CREDIT_MATCHED` deve poter tornare a `CREDIT_MATCHING`) | `MISSING` | `MINOR` | Add `CREDIT_MATCHING` (decisione D3 conferma reachable) | `FIX` |
| **D15** | Compliance handler `complianceFiscalActions` advance condition | `srv/handlers/complianceFiscalActions.ts:99-102` | `validateFiscal` advance a `VALIDATED` solo se `result.status === 'PASSED'` | Design §2 F3: `VALIDATED` deve essere settato anche per `result.status === 'WARNING'` (warning non blocca) | `WRONG_ORDER` | `MAJOR` | Verificare contract: WARNING deve advance? Se sì, fix condition; se no, design specifica | `FIX` |
| **D16** | `processEngine.advanceAfterStep` | `srv/process/processEngine.ts:629-709` | Auto-chain step automatici fino a `MAX_AUTO_CHAIN_DEPTH`. Manca gestione esplicita di deviation steps (`PARKING`, `REVERSAL`) | `advanceAfterStep` deve skippare step `IsDeviation=true` | `CONTRACT` | `MINOR` | Add filter `step.IsDeviation !== true` quando D5 fix è applicato | `FIX` |
| **D17** | `exceptionAutoDispatcher.autoDispatch` | `srv/utils/exceptionAutoDispatcher.ts` | Phase gates processed come step checks. Se utente ha `IsDisabled=true` in `CompanyCheckOverride` per `COST_ALLOCATION_REQUIRED`, l'auto-dispatch chiude come `SYSTEM_DISABLED_CHECK` — comportamento ambiguo | Phase gates non possono essere disabilitati a livello `CompanyCheckOverride` (gate è governance globale). Aggiungere guard quando `check.IsPhaseGate=true` | `CONTRACT` | `MAJOR` | Add guard in `exceptionAutoDispatcher` (D7 fix) | `FIX` |
| **D18** | `creditNoteActions::matchCreditNoteToOriginal` reachability path | `srv/handlers/creditNoteActions.ts` | Manca check che `SDIDocumentType='TD04'` | Action callable solo per TD04 (semantic guard backend-side) | `MISSING` (guard) | `MINOR` | Add early-return `if (invoice.SDIDocumentType !== 'TD04') return req.reject(409, ...)` | `FIX` |
| **D19** | `costAllocationActions::confirmCostAllocation` produces `COST_VALIDATED` o `COST_ALLOCATED` | `srv/handlers/matchingActions.ts:594-606` | Status target dipende da `validation.enabled` (config flag `NONPO_COST_OBJECT_VALIDATION`) | OK come implementazione | (no drift) | — | nessuno | OK |
| **D20** | `nonPoPhaseGate::raiseNonPoPhaseGateExceptions` ordering | `srv/handlers/invoiceLifecycleActions.ts:121-128` | Phase gates raised quando `nextStatus === 'PENDING_APPROVAL'` — solo F5 entry. Per F3_BIS il check è raised ma il status non è `CREDIT_MATCHING` (D3) né `PENDING_APPROVAL` | Gate `CREDIT_NOTE_MATCH_MISSING` raised al boundary `RISK_ASSESSED → next` quando `SDIDocumentType='TD04'` (entry F3_BIS) | `WRONG_BINDING` | `MAJOR` | Update boundary check: set di trigger conditions che dipende dal gate | `FIX` |
| **D21** | `USER_GUIDE.md` §5 | `docs/USER_GUIDE.md:100-289` | 6 fasi + sequence MA `Phase 5 Approvazione` lista status drift; omette branch TD04; `Phase 4` ambiguo PO/NON_PO | Riscrivere §5 per allineare al design canonico OPPURE deprecato e sostituito da link al design doc | `OBSOLETE` | `MINOR` | Rewrite §5 in remediation; in alternativa, disclaimer "Per il lifecycle vedi `docs/INVOICE_LIFECYCLE_DESIGN.md`" | `FIX` |
| **D22** | `processFlowStatuses.test.js` | `test/unit/processFlowStatuses.test.js:44-51` | `STATUS_STEP` test contiene mapping 5-step (legacy). Manca `CREDIT_MATCHING`/`CREDIT_MATCHED`/`COST_VALIDATED` | Test deve riflettere design 6-step + branch TD04. Riscrivere mapping coerente con `ProgressFormatter.ts` post-D6 fix | `WRONG_ORDER` | `MINOR` | Update test mapping; abilita parity test di `ProgressFormatter` | `FIX` |
| **D23** | `srv/orchestratorSrv.cds` action labels | various | Alcune action labels (`@title`) sono in italiano, altre in inglese o assenti | Standardizzare: ogni action user-facing ha `@title` italiano + i18n key | `NAMING` | `MINOR` | Tracking item per UX consistency; passata sistematica in remediation | `FIX` |
| **D24** | `xs-security.json` ruoli vs azioni cross-fase | `xs-security.json` + `srv/services-auth.cds` | 15 ruoli canonici. Tabella §5 design doc menziona ruoli specifici per ogni action; nessuna verifica automatica vs `xs-security.json` | Test parity: ogni action elencata in §5 deve avere `@restrict` in `services-auth.cds` con i ruoli del design | `CONTRACT` | `MAJOR` | Aggiungere parity test `roleMatrixParity.test.js` | `FIX` |
| **D25** | `app/passive-invoice-approvals` workflow visibility | `app/passive-invoice-approvals/webapp/manifest.json` | App approvals mostra `InvoiceWorkflowItem` PENDING/IN_APPROVAL. Non ha visibility separata per `EXCEPTION_OVERRIDE` vs `PHASE_APPROVAL` | Design §5 distingue 2 ItemType. UX dovrebbe filtrarli/raggrupparli | `UI_VISIBILITY` | `MINOR` | Add filter/grouping in approvals app | `FIX` |

---

## §2 — Drift summary per Phase ID

| Phase ID | Drift items impattanti | Severity max |
|---|---|---|
| `F1_RECEPTION` | nessuno | — |
| `F2_BP_RESOLUTION` | D6 | MAJOR |
| `F3_VERIFICATION` | D1, D6, D8, D15 | MAJOR |
| `F3_BIS_CREDIT` | D3, D3-bis, D4, D14, D18, D20 | BLOCKING |
| `F4_PO_MATCHING` | D1, D6 | MAJOR |
| `F4_NON_PO_CODING` | D2, D7, D17 | MAJOR |
| `F5_APPROVAL` | D24, D25 | MAJOR |
| `F6_POSTING` | D5, D6, D10-bis, D16 | MINOR |
| Cross-fase | D9, D10, D13, D21, D22, D23 | MAJOR |

---

## §3 — Drift summary per Severity

| Severity | Count | Items |
|---|---|---|
| `BLOCKING` | 1 | D3-bis |
| `MAJOR` | 12 | D1, D2, D3, D4, D6, D7, D8, D15, D17, D20, D24, D10-bis (escalation per facet missing) |
| `MINOR` | 12 | D5, D9, D10, D12, D13, D14, D16, D18, D21, D22, D23, D25 |
| `(no drift)` | 2 | D11, D19 |

> **Nota**: il vero `BLOCKING` è solo D3-bis (handler setta status non in `STATUS_ORDER` → potenziali `statusIndex=-1` → bypass logico di `isBefore`/`isAtOrBefore`). Gli altri MAJOR sono "alignment debt" che producono UX/audit gap ma non rompono il runtime.

---

## §4 — Ordine di remediation

### Dipendenze fra fix items

```
D3-bis (BLOCKING) ──┐
                    ├─→ D3 (CREDIT_MATCHING reachable)
                    │      ├─→ D14, D20
                    │
D7 (gate-vs-step) ──┼─→ D17 (auto-dispatch phase gate)
                    │      └─→ D4 (rebinding CREDIT_NOTE_MATCH_MISSING)
                    │
D1 (RISK SeqNumber) ─→ D22 (test mapping)
                    │
D6 (wizard 6-step) ──┴─→ D22 (test mapping)
```

### Tranche di remediation proposte

| Tranche | Fix items | Effort stimato | Risk |
|---|---|---|---|
| **R1 — Fix BLOCKING + obsolete** | D3-bis, D13 | 0.5 gg | basso (pure code removal + test update) |
| **R2 — F3_BIS reachability** | D3, D4, D14, D18, D20 | 1.5 gg | medio (modifiche cross-handler + nuovi test) |
| **R3 — Phase gate vs step check** | D7 (CDS schema column `IsPhaseGate`), D17, D2 (commento contract) | 1 gg | basso (additive change) |
| **R4 — Sequence alignment** | D1, D8, D15, D22 | 1 gg | medio (re-run lifecycle test) |
| **R5 — FE wizard alignment** | D6, D22 (FE side), D9 (CLAUDE.md docs) | 1 gg | basso (FE-only) |
| **R6 — UX polish + governance** | D5, D10, D10-bis, D12, D16, D21, D23, D24, D25 | 1.5 gg | basso |

**Totale R1-R6**: ~6.5 gg di remediation. R1+R2+R4 sono prerequisiti per i parity test che chiudono il loop di drift.

---

## §5 — Sign-off

Decisioni 2026-04-25:

- ✅ Tutti i drift items risolti come `FIX` (no `ACCEPT_DEBT`, no `REVISE_DESIGN`)
- ✅ Ordine di remediation R1→R6 approvato
- ✅ Drift D3 risolto: `CREDIT_MATCHING` viene mantenuto reachable (handler entry-point + boundary `RISK_ASSESSED → CREDIT_MATCHING` quando TD04)

Output: backlog di 25 fix items distribuiti in 6 tranche per la sessione di remediation successiva. Design doc canonico (`docs/INVOICE_LIFECYCLE_DESIGN.md`) come contratto vincolante. Drift matrix come ledger di tracking.