12 KiB
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:
- 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.
- 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).
- 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 DEVENT_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 aWORKFLOW_AUTHORITY_NAME+WORKFLOW_AUTHORITY_JWK_URLpair 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:
- The instrument leg has produced valid dispatch evidence: an authenticated
MT760issuance acknowledgment or an ISO 20022 instrument-specific equivalent, signed and time-stamped.- The payment leg has produced valid settlement evidence — not merely acceptance. Valid settlement evidence is one of:
pacs.002with statusACSCon the pacs.009 / pacs.008 interbank leg;camt.025with statusACSC;camt.054credit notification referencing the expectedEndToEndIdentification,InstructedAmount, andCurrency;- An
MT910(credit confirmation) on the beneficiary side or anMT900(debit confirmation) on the originator side with matching transaction reference and amount.- The Coordinator has run the
VALIDATINGphase (§4.3 / PR B) and all reconciliation checks have passed — in particular amount, currency, credit/debit direction, and end-to-end identifier.- No outstanding exception blocks remain in the Exception Manager (§5.9 / PR B).
ACCP/ACSP/PDNGstatuses 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 inVALIDATING. On timer expiry, the Coordinator transitions toABORTEDunder §9.3 timing-exception rules.
Implementation impact
orchestrator/src/services/swift/camt.ts(PR E):parseCamt025already distinguishesACCP | ACSC | ACSP | RJCT | PDNG. TheExecutionCoordinatormust only acceptACSC(camt.025) orCRDT(camt.054 matching reconciliation) as the settlement trigger. Tracked follow-up: wire this intoexecutionCoordinator.validatePlan()so thatVALIDATING → COMMITTEDis blocked when the latest camt message isACCP/ACSP. Current code does not reference these statuses yet; correctness is preserved today only because the mocked dispatch always synthesisesACSC.orchestrator/src/services/exceptionManager.ts(PR B): add aTiming.settlementDeadlineExpiredclass routed toABORTED. Current taxonomy has genericTiming.dispatchTimeout/acknowledgmentDelaywhich 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_PENDINGstate 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 ofABORTED:
Instrument-leg observable state Instrument unwind action instrument.dispatchednot yet emittedWithdraw — cancel before dispatch. No counter-instrument needed. instrument.dispatchedemitted,instrument.acknowledgednot yet emittedRecall request — non-binding; beneficiary's bank MAY reject. If rejected, fall through to "acknowledged" row. instrument.acknowledgedemittedIrrevocable — 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.dispatchednot yet emittedWithhold — do not dispatch. payment.dispatchedemitted,payment.acceptednot yet emittedRecall ( pacs.028request-to-modify /camt.056request-for-cancellation). Best-effort.payment.acceptedbut notpayment.settledRecall before settlement — MAY succeed depending on receiver bank's processing window. payment.settledReturn payment — requires a fresh, separately-instructed return payment ( pacs.009in reverse direction). Not an unwind of the original; a compensating transfer.
UNWIND_PENDINGis a state of the orchestrator, not a guarantee that the underlying banking artefacts can be reversed. On entry toUNWIND_PENDING, the Coordinator SHALL record in the State Registry the observable state of each leg at the moment ofABORTEDand 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 → CLOSEDremains valid). What changes is the payload recorded on theABORTED → UNWIND_PENDINGtransition — thereasonfield must include the instrument-leg and payment-leg observable states.orchestrator/src/services/execution.ts: on entry toUNWIND_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 driveinstrument.dispatched/instrument.acknowledged/payment.*events is a separate coordinator-focused PR).- Portal
/transactionspage (PR G): the audit-trail card already rendersreasoninline; 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:
WORKFLOW_AUTHORITY_NAME+ JWK URL in orchestrator env (Amendment 1).- Wire
executionCoordinator.validatePlan()to discriminateACCP/ACSPfromACSC/CRDTusing the PR E parsers (Amendment 2). - Add
Timing.settlementDeadlineExpiredto the Exception taxonomy (Amendment 2). - Capture instrument-leg and payment-leg observable state in the
ABORTED → UNWIND_PENDINGtransitionreasonfield (Amendment 3). - 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.