# Architecture Note — Amendments **Reference:** *Multi-Layer Atomic Settlement Architecture for SBLC Issuance and Payment Coordination* (Draft 1.0). **Purpose:** Three amendments identified during the CurrenciCombo gap-analysis (§2 of `docs/ADRs` / gap-analysis note) that tighten the contract between the note and the orchestrator implementation landing in PRs A–G. These amendments are **normative**: where the text here conflicts with the original draft, this document takes precedence. --- ## Amendment 1 — §5.1 Transaction Coordinator (trust model) ### Problem The original §5.1 names the Transaction Coordinator but does not specify **who runs it** or **what trust assumptions the other participants must accept** to use it. In a multi-bank SBLC + payment flow this is not a detail — the Coordinator holds the state registry, issues `transaction.prepared` instructions, and decides `COMMITTED` vs `ABORTED`. Whoever runs it is, in effect, the workflow authority. Three candidate topologies exist: 1. **Single-party hosted** — one participant (e.g. the issuing bank, the beneficiary's bank, or a shared utility) runs a single Coordinator instance and the rest consume its API. 2. **Federated** — each participant runs their own Coordinator; they reach consensus over the state via signed events exchanged peer-to-peer (the architecture note §7 normalised events). 3. **Neutral third-party utility** — a non-participant (e.g. a FinTech utility, a central bank–adjacent entity, or an SRO) runs the Coordinator under a published operating model. ### Amendment text (replaces §5.1) > **5.1 Transaction Coordinator.** Central orchestration service that manages > the lifecycle of a transaction instance. The operator of the Coordinator > SHALL be named in the governing documents (§4.1) as the *Workflow Authority*. > The Workflow Authority: > > - is a single named legal entity for any given transaction; > - MUST be a participant in, or a party contractually bound to, that > transaction's governing documents; > - MUST NOT be the same entity that provides the Identity and Authorization > Service (§5.8) or the Ledger Anchor (§5.7) — separation of the control > plane, the trust anchor, and the audit anchor is a requirement, not an > option; > - MUST publish its operating model, availability commitments, and exception > escalation paths to all participants; > - MUST sign every state transition it records to the State Registry (§5.6) > with a key bound to its identity in the Identity and Authorization > Service; participants verify those signatures before accepting a state as > canonical. > > CurrenciCombo's reference topology is (1) **single-party hosted**, where > the issuing bank of the SBLC operates the Coordinator and the payment-side > bank, beneficiary, and applicant consume its API. Federated (2) and > neutral-utility (3) topologies are out of scope for v1 but are not > prohibited — they can be layered on top by replacing the Coordinator > implementation while preserving the API surface. ### Implementation impact - **`orchestrator` (CurrenciCombo)**: the orchestrator IS the Coordinator. `NOTARY_REGISTRY_ADDRESS` (Ledger Anchor) and the signing key used for the event bus (§5.8 / PR D `EVENT_BUS_SECRET`) must be held in separate key stores. PR A's SoD matrix (`stateMachine.ts`) already prevents a single actor from driving the 4 SoD-gated transitions. - **Operational**: the config (`orchestrator/src/config/env.ts`) SHOULD grow a `WORKFLOW_AUTHORITY_NAME` + `WORKFLOW_AUTHORITY_JWK_URL` pair so consumers can resolve and verify the Coordinator's identity without out-of-band trust. Tracked as a follow-up ticket; not blocking. --- ## Amendment 2 — §9.2 Commit Rule ("accepted ≠ settled") ### Problem §9.2 currently reads: > A transaction may enter **COMMITTED** only when: > - the instrument leg has produced valid dispatch evidence > - the payment leg has produced valid settlement or **accepted completion > evidence** > - all key transaction attributes reconcile against expected values > - no outstanding exception blocks remain The phrase "accepted completion evidence" is too loose. In SWIFT and ISO 20022 terms, **acceptance is not settlement**: | Message | Meaning | Is settlement? | |------------------|-------------------------------------------------|----------------| | `pacs.002 ACCP` | Instruction technically accepted by receiver | **No** | | `pacs.002 ACSP` | Accepted, settlement in process | **No** | | `pacs.002 ACSC` | Accepted, settlement completed | Yes | | `camt.025 ACCP` | Receipt: accepted | **No** | | `camt.025 ACSC` | Receipt: settlement completed | Yes | | `camt.054 CRDT` | Account credit notification | Yes (on receiver) | | MT910 | Confirmation of credit | Yes | | MT900 | Confirmation of debit | Yes (on sender) | Treating `ACCP` as sufficient for `COMMITTED` introduces a window where the Coordinator has locked-in the issuance but the payment has not cleared — the exact failure mode the two-phase commit was meant to prevent. ### Amendment text (replaces §9.2) > **9.2 Commit Rule.** A transaction may enter **COMMITTED** only when **all** > of the following are true: > > 1. The instrument leg has produced valid dispatch evidence: an authenticated > `MT760` issuance acknowledgment or an ISO 20022 instrument-specific > equivalent, signed and time-stamped. > 2. The payment leg has produced valid **settlement** evidence — not merely > acceptance. Valid settlement evidence is one of: > - `pacs.002` with status `ACSC` on the pacs.009 / pacs.008 interbank > leg; > - `camt.025` with status `ACSC`; > - `camt.054` credit notification referencing the expected > `EndToEndIdentification`, `InstructedAmount`, and `Currency`; > - An `MT910` (credit confirmation) on the beneficiary side or an > `MT900` (debit confirmation) on the originator side with matching > transaction reference and amount. > 3. The Coordinator has run the `VALIDATING` phase (§4.3 / PR B) and all > reconciliation checks have passed — in particular amount, currency, > credit/debit direction, and end-to-end identifier. > 4. No outstanding exception blocks remain in the Exception Manager (§5.9 / > PR B). > > `ACCP` / `ACSP` / `PDNG` statuses SHALL NOT satisfy (2) on their own. If > only acceptance-level evidence has arrived and the settlement-deadline > timer has not expired, the transaction remains in `VALIDATING`. On timer > expiry, the Coordinator transitions to `ABORTED` under §9.3 timing-exception > rules. ### Implementation impact - **`orchestrator/src/services/swift/camt.ts` (PR E)**: `parseCamt025` already distinguishes `ACCP | ACSC | ACSP | RJCT | PDNG`. The `ExecutionCoordinator` must only accept `ACSC` (camt.025) or `CRDT` (camt.054 matching reconciliation) as the settlement trigger. **Tracked follow-up**: wire this into `executionCoordinator.validatePlan()` so that `VALIDATING → COMMITTED` is blocked when the latest camt message is `ACCP` / `ACSP`. Current code does not reference these statuses yet; correctness is preserved today only because the mocked dispatch always synthesises `ACSC`. - **`orchestrator/src/services/exceptionManager.ts` (PR B)**: add a `Timing.settlementDeadlineExpired` class routed to `ABORTED`. Current taxonomy has generic `Timing.dispatchTimeout` / `acknowledgmentDelay` which is too coarse for this distinction. --- ## Amendment 3 — §4.1 UNWIND_PENDING matrix (MT760 irrevocability) ### Problem §8.1 defines a single `UNWIND_PENDING` state after `ABORTED`, and §11 Phase 6 says "if needed, initiate unwind process". This glosses over a hard banking fact: **an issued MT760 guarantee/SBLC is irrevocable under UCP 600 / URDG 758** once it has been dispatched to the beneficiary's bank. The set of "unwind" actions is therefore not uniform — it depends on *which leg* has progressed how far. The original note's state diagram implies `UNWIND_PENDING` is reachable from any `ABORTED`, regardless of what was already dispatched. That is operationally wrong: if the MT760 has left the issuing bank and been authenticated by the beneficiary's bank, the instrument itself **cannot be withdrawn unilaterally** — it can only be discharged (on expiry or on beneficiary release) or replaced by a counter-instrument. ### Amendment text (adds §4.1.1 and refines §8.1 / §11 Phase 6) > **4.1.1 Instrument irrevocability matrix.** The `UNWIND_PENDING` state > SHALL NOT imply that the instrument leg is reversible. The set of unwind > actions available depends on the observable state of each leg at the > moment of `ABORTED`: > > | Instrument-leg observable state | Instrument unwind action | > |--------------------------------------------|--------------------------| > | `instrument.dispatched` not yet emitted | **Withdraw** — cancel before dispatch. No counter-instrument needed. | > | `instrument.dispatched` emitted, `instrument.acknowledged` not yet emitted | **Recall request** — non-binding; beneficiary's bank MAY reject. If rejected, fall through to "acknowledged" row. | > | `instrument.acknowledged` emitted | **Irrevocable — no unwind available.** The instrument stands until expiry (§11 tenor) or beneficiary-side release. The only control-plane actions are (a) accelerated expiry on mutual written consent, (b) issuance of a **counter-guarantee** from the beneficiary of the original instrument back to the applicant, (c) legal discharge via governing-law procedure. | > > | Payment-leg observable state | Payment unwind action | > |--------------------------------------------|--------------------------| > | `payment.dispatched` not yet emitted | **Withhold** — do not dispatch. | > | `payment.dispatched` emitted, `payment.accepted` not yet emitted | **Recall** (`pacs.028` request-to-modify / `camt.056` request-for-cancellation). Best-effort. | > | `payment.accepted` but not `payment.settled` | **Recall before settlement** — MAY succeed depending on receiver bank's processing window. | > | `payment.settled` | **Return payment** — requires a fresh, separately-instructed return payment (`pacs.009` in reverse direction). Not an unwind of the original; a compensating transfer. | > > `UNWIND_PENDING` is a **state of the orchestrator**, not a guarantee that > the underlying banking artefacts can be reversed. On entry to > `UNWIND_PENDING`, the Coordinator SHALL record in the State Registry the > observable state of each leg at the moment of `ABORTED` and the unwind > action selected from the matrix above. ### Implementation impact - **`orchestrator/src/services/stateMachine.ts` (PR A)**: the transition table is unchanged (`ABORTED → UNWIND_PENDING → CLOSED` remains valid). What changes is the *payload* recorded on the `ABORTED → UNWIND_PENDING` transition — the `reason` field must include the instrument-leg and payment-leg observable states. - **`orchestrator/src/services/execution.ts`**: on entry to `UNWIND_PENDING`, the Coordinator must select and persist the unwind actions per the matrix. **Tracked follow-up**: this requires the ExecutionCoordinator to consume real SWIFT events (PR E outbound + inbound parsers are in place; wiring them to drive `instrument.dispatched` / `instrument.acknowledged` / `payment.*` events is a separate coordinator-focused PR). - **Portal `/transactions` page (PR G)**: the audit-trail card already renders `reason` inline; no UI change required. The unwind-action tracking will naturally surface as additional event rows. --- ## Summary of downstream tickets Tracked as separate work items, not blockers for A–G: 1. `WORKFLOW_AUTHORITY_NAME` + JWK URL in orchestrator env (Amendment 1). 2. Wire `executionCoordinator.validatePlan()` to discriminate `ACCP`/`ACSP` from `ACSC`/`CRDT` using the PR E parsers (Amendment 2). 3. Add `Timing.settlementDeadlineExpired` to the Exception taxonomy (Amendment 2). 4. Capture instrument-leg and payment-leg observable state in the `ABORTED → UNWIND_PENDING` transition `reason` field (Amendment 3). 5. Persist the selected unwind action per the matrix in Amendment 3. None of the five items regress A–G — they extend behaviour on top of already-landed structures.