commit 005d1960b981176633f6519e40b8af67cbbd3964
parent 94229c03d8e56dc54cceb24893e0a4a96486810d
Author: Antoine A <>
Date: Fri, 18 Sep 2026 17:16:10 +0200
DD104
Diffstat:
3 files changed, 240 insertions(+), 232 deletions(-)
diff --git a/design-documents/104-status.rst b/design-documents/104-status.rst
@@ -1,231 +0,0 @@
-DD 104: Wire Gateway Observability and Administration
-#####################################################
-
-:Design status: Draft
-:Implementation status: Not started
-:DD shepherd: Antoine d'Aligny
-:Historical contributors: Antoine d'Aligny
-:First published: 2026-09-18
-:Last substantive change: 2026-09-18
-
-Summary
-=======
-
-This design document presents the challenges we currently face when handling
-transaction failures in wire gateway adapters and proposes a solution for
-addressing them.
-
-Motivation
-==========
-
-In the current design and implementations, both incoming and outgoing
-transactions can fail without administrators being notified or having the
-ability to take corrective action.
-
-Requirements
-============
-
-* The solution must allow administrators to see failures, understand their
- causes, and possibly fix them.
-* The solution must be lightweight to implement in wire gateway adapters, as
- the goal of this API is to provide a thin wrapper around how each
- implementation works. Common logic that can be extracted into another
- component should be.
-
-Proposed Solution
-=================
-
-Terminology
------------
-
-* `in_tx`: final incoming transaction
-* `out_tx`: final outgoing transaction
-* `talerable_in_tx`: talerable final incoming transaction (RESERVE | KYC | MAP)
-* `talerable_out_tx`: talerable final outgoing transaction (deposit)
-* `transfer`: initiated talerable outgoing transaction
-* `initiated`: initiated outgoing transaction
-* `bounce`: bounced invalid outgoing transaction
-
-Current API State
------------------
-
-We already have endpoints for querying Talerable transactions:
-`talerable_in_tx` with `GET /history/incoming` and `talerable_out_tx` with
-`GET /history/outgoing`.
-
-We also already have endpoints for querying `transfer` with `GET /transfers`
-and `GET /transfers/$ROW_ID`.
-
-What is missing is access to raw (incompled, malformed, etc) transactions, `in_tx` and `out_tx`, as well
-as initiated and bounced transactions, `initiated` and `bounce`.
-
-Generic API Design
-------------------
-
-Each adapter works differently. In most adapters, `transfer` and `bounce`
-produce an `initiated` operation, while other payment systems, such as
-Cyclos, provide a proper way to perform a bounce idempotently and have a
-specific bounce operation.
-
-Each adapter also has different internal unique identifiers. For example,
-`libeufin_nexus` uses three of them while `depolymerizer-bitcoin` has a
-single transaction ID.
-
-We need an API that can expose enough detail for manual reconciliation while
-remaining generic enough to reuse the same logic across all adapters.
-
-For `transfer`, we already have the paginated `GET /transfers` endpoint and
-the single-entry `GET /transfers/$ROW_ID` endpoint. The problem with this
-design is that it is not well suited to following the progress of transfers.
-Transfers do not progress or reach finality in the order in which they are
-created.
-
-I propose adding a new status-history endpoint,
-`GET /transfers/status-history`. This will allow administrators to understand
-the lifecycle of a transaction and track the progress of all transfers in
-real time.
-
-We also need new endpoints for `incoming` and `outgoing` transactions,
-including paginated and single-entry endpoints, so that administrators can
-inspect all transactions, including incomplete or invalid ones. This is
-necessary to understand and debug why an incoming transaction never reaches
-the Talerable history.
-
-Finally, we need a way to track both `initiated` and `bounce` transactions
-using new endpoints providing paginated listings, status histories and
-single-entry views.
-
-We could potentially remove all `transfer` endpoints, since transfers are a
-subset of `initiated` operatuibs. Whether to do so depends on whether the
-transfer-specific API remains useful as a higher-level abstraction.
-
-We could also enforce a single `initiated` operation concept, event if underneath
-bounces works differently. As long as doing this it not too messy for the
-database layer we should pursue this simplification.
-
-.. ts:def:: InTx
-
- interface InTx {
- // Opaque identifier of the returned record.
- row_id: SafeUint64;
-
- // Unstructured implementation specific fields that must be shown.
- // Contains at least the unique identifiers to be used for manual reconciliation.
- details: {string: string};
-
- // Date of the transaction.
- date: Timestamp;
-
- // Amount received before credit_fee.
- amount?: Amount;
-
- // Unstructured transaction subject
- subject?: string;
-
- // Fee paid by the creditor.
- // If not null, creditor actually received amount - credit_fee
- credit_fee?: Amount;
-
- // Full payto URI to identify the sender of funds.
- debit_account?: string;
-
- // ID of the bounce operation if bounced
- bounce_id?: SafeUint64;
- }
-
-.. ts:def:: OutTx
-
- interface OutTx {
- // Opaque identifier of the returned record.
- row_id: SafeUint64;
-
- // Unstructured implementation specific fields that must be shown.
- // Contains at least the unique identifiers to be used for manual reconciliation.
- details: {string: string};
-
- // Date of the transaction.
- date: Timestamp;
-
- // Amount received before credit_fee.
- amount?: Amount;
-
- // Unstructured transaction subject
- subject?: string;
-
- // Fee paid by the debtor.
- // If not null, debtor actually paid amount + debit_fee
- debit_fee?: Amount;
-
- // Full payto URI to identify the receiver of funds.
- credit_account?: string;
- }
-
-.. ts:def:: InitiatedTx
-
- interface InitiatedTx {
- // Opaque ID of this operation.
- operation_id: SafeUint64;
-
- // Amount to transfer.
- amount: Amount;
-
- // The recipient's account identifier as a full payto:// URI.
- credit_account: string;
-
- // Optional ID of the wire transfer initiation if operation is a transfer
- transfer_id?: SafeUint64;
-
- // Optional ID of the bounced incoming transaction if operation is a bounce
- bounced_id?: SafeUint64;
-
- // Current status
- // pending: in progress
- // transient_failure: has failed but may succeed later
- // permanent_failure: has failed permanently and will never reach finality
- // success: has succeeded and reached finality
- // late_failure: has failed permanently after reaching the success status
- status: "pending" | "transient_failure" | "permanent_failure" | "success" | "late_failure";
-
- // Timestamp that indicates when this status was reached.
- timestamp: Timestamp;
- }
-
-.. ts:def:: InitiatedStatus
-
- interface InitiatedStatus {
- // Opaque ID of this operation.
- operation_id: SafeUint64;
-
- // Opaque ID of this status, used for pagination.
- row_id: SafeUint64;
-
- // pending: in progress
- // transient_failure: has failed but may succeed later
- // permanent_failure: has failed permanently and will never reach finality
- // success: has succeeded and reached finality
- // late_failure: has failed permanently after reaching the success status
- status: "pending" | "transient_failure" | "permanent_failure" | "success" | "late_failure";
-
- // Optional unstructured messages about the status. Can be used to document the reasons for failure or the state of progress.
- status_msg?: string;
-
- // Timestamp that indicates when this status was reached.
- timestamp: Timestamp;
- }
-
-Test Plan
-=========
-
-
-
-Definition of Done
-==================
-
-Alternatives
-============
-
-Drawbacks
-=========
-
-Discussion / Q&A
-================
-\ No newline at end of file
diff --git a/design-documents/104-wire-gateway-management-API.rst b/design-documents/104-wire-gateway-management-API.rst
@@ -0,0 +1,238 @@
+DD 104: Wire Gateway Management API
+###################################
+
+:Design status: Draft
+:Implementation status: Not started
+:DD shepherd: Antoine d'Aligny
+:Historical contributors: Antoine d'Aligny
+:First published: 2026-09-18
+:Last substantive change: 2026-09-18
+
+Summary
+=======
+
+This design document presents the challenges we currently face when handling
+transaction failures in wire gateway adapters and proposes a solution for
+addressing them.
+
+Motivation
+==========
+
+In the current design and implementations, both incoming and outgoing
+transactions can fail without administrators being notified or having the
+ability to take corrective action.
+
+Requirements
+============
+
+* The solution must allow administrators to see failures, understand their
+ causes, and possibly fix them.
+* The solution must be lightweight to implement in wire gateway adapters, as
+ the goal of this API is to provide a thin wrapper around how each
+ implementation works. Common logic that can be extracted into another
+ component should be.
+
+Proposed Solution
+=================
+
+Terminology
+-----------
+
+* `in_tx`: final incoming transaction
+* `out_tx`: final outgoing transaction
+* `talerable_in_tx`: talerable final incoming transaction (RESERVE | KYC | MAP)
+* `talerable_out_tx`: talerable final outgoing transaction (deposit)
+* `transfer`: initiated talerable outgoing transaction
+* `initiated`: initiated outgoing transaction
+* `bounce`: bounced invalid outgoing transaction
+
+Current API State
+-----------------
+
+We already have endpoints for querying Talerable transactions:
+`talerable_in_tx` with `GET /history/incoming` and `talerable_out_tx` with
+`GET /history/outgoing`.
+
+We also already have endpoints for querying `transfer` with `GET /transfers`
+and `GET /transfers/$ROW_ID`.
+
+What is missing is access to raw (incompled, malformed, etc) transactions, `in_tx` and `out_tx`, as well
+as initiated and bounced transactions, `initiated` and `bounce`.
+
+Generic API Design
+------------------
+
+Each adapter works differently. In most adapters, `transfer` and `bounce`
+produce an `initiated` operation, while other payment systems, such as
+Cyclos, provide a proper way to perform a bounce idempotently and have a
+specific bounce operation.
+
+Each adapter also has different internal unique identifiers. For example,
+`libeufin_nexus` uses three of them while `depolymerizer-bitcoin` has a
+single transaction ID.
+
+We need an API that can expose enough detail for manual reconciliation while
+remaining generic enough to reuse the same logic across all adapters.
+
+For `transfer`, we already have the paginated `GET /transfers` endpoint and
+the single-entry `GET /transfers/$ROW_ID` endpoint. The problem with this
+design is that it is not well suited to following the progress of transfers.
+Transfers do not progress or reach finality in the order in which they are
+created.
+
+I propose adding a new status-history endpoint,
+`GET /transfers/status-history`. This will allow administrators to understand
+the lifecycle of a transaction and track the progress of all transfers in
+real time.
+
+We also need new endpoints for `incoming` and `outgoing` transactions,
+including paginated and single-entry endpoints, so that administrators can
+inspect all transactions, including incomplete or invalid ones. This is
+necessary to understand and debug why an incoming transaction never reaches
+the Talerable history.
+
+Finally, we need a way to track both `initiated` and `bounce` transactions
+using new endpoints providing paginated listings, status histories and
+single-entry views.
+
+We could potentially remove all `transfer` endpoints, since transfers are a
+subset of `initiated` operatuibs. Whether to do so depends on whether the
+transfer-specific API remains useful as a higher-level abstraction.
+
+We could also enforce a single `initiated` operation concept, event if underneath
+bounces works differently. As long as doing this it not too messy for the
+database layer we should pursue this simplification.
+
+.. ts:def:: InTx
+
+ interface InTx {
+ // Opaque identifier of the returned record.
+ row_id: SafeUint64;
+
+ // Unstructured implementation specific fields that must be shown.
+ // Contains at least the unique identifiers to be used for manual reconciliation.
+ details: {string: string};
+
+ // Date of the transaction.
+ date: Timestamp;
+
+ // Amount received before credit_fee.
+ amount?: Amount;
+
+ // Unstructured transaction subject
+ subject?: string;
+
+ // Fee paid by the creditor.
+ // If not null, creditor actually received amount - credit_fee
+ credit_fee?: Amount;
+
+ // Full payto URI to identify the sender of funds.
+ debit_account?: string;
+
+ // ID of the bounce operation if bounced
+ bounce_id?: SafeUint64;
+ }
+
+.. ts:def:: OutTx
+
+ interface OutTx {
+ // Opaque identifier of the returned record.
+ row_id: SafeUint64;
+
+ // Unstructured implementation specific fields that must be shown.
+ // Contains at least the unique identifiers to be used for manual reconciliation.
+ details: {string: string};
+
+ // Date of the transaction.
+ date: Timestamp;
+
+ // Amount received before credit_fee.
+ amount?: Amount;
+
+ // Unstructured transaction subject
+ subject?: string;
+
+ // Fee paid by the debtor.
+ // If not null, debtor actually paid amount + debit_fee
+ debit_fee?: Amount;
+
+ // Full payto URI to identify the receiver of funds.
+ credit_account?: string;
+ }
+
+.. ts:def:: InitiatedTx
+
+ interface InitiatedTx {
+ // Opaque ID of this operation.
+ operation_id: SafeUint64;
+
+ // Amount to transfer.
+ amount: Amount;
+
+ // The recipient's account identifier as a full payto:// URI.
+ credit_account: string;
+
+ // Optional ID of the wire transfer initiation if operation is a transfer
+ transfer_id?: SafeUint64;
+
+ // Optional ID of the bounced incoming transaction if operation is a bounce
+ bounced_id?: SafeUint64;
+
+ // Current status
+ // pending: in progress
+ // transient_failure: has failed but may succeed later
+ // permanent_failure: has failed permanently and will never reach finality
+ // success: has succeeded and reached finality
+ // late_failure: has failed permanently after reaching the success status
+ status: "pending" | "transient_failure" | "permanent_failure" | "success" | "late_failure";
+
+ // Timestamp that indicates when this status was reached.
+ timestamp: Timestamp;
+ }
+
+.. ts:def:: InitiatedStatus
+
+ interface InitiatedStatus {
+ // Opaque ID of this operation.
+ operation_id: SafeUint64;
+
+ // Opaque ID of this status, used for pagination.
+ row_id: SafeUint64;
+
+ // pending: in progress
+ // transient_failure: has failed but may succeed later
+ // permanent_failure: has failed permanently and will never reach finality
+ // success: has succeeded and reached finality
+ // late_failure: has failed permanently after reaching the success status
+ status: "pending" | "transient_failure" | "permanent_failure" | "success" | "late_failure";
+
+ // Optional unstructured messages about the status. Can be used to document the reasons for failure or the state of progress.
+ status_msg?: string;
+
+ // Timestamp that indicates when this status was reached.
+ timestamp: Timestamp;
+ }
+
+Management logic
+----------------
+
+On top of this API, we could build a Management SPA to support administrative operations.
+The UI would show recent failures and track whether someone has reviewed them and made a decision on how to handle them.
+We could also extend the API with administrative operations, such as manually requesting a retry.
+
+Test Plan
+=========
+
+
+
+Definition of Done
+==================
+
+Alternatives
+============
+
+Drawbacks
+=========
+
+Discussion / Q&A
+================
+\ No newline at end of file
diff --git a/design-documents/index.rst b/design-documents/index.rst
@@ -117,4 +117,5 @@ documents. The lifecycle metadata in each document is authoritative. See
101-semantic-token-families
102-systemd-service-restart-policy
103-https-wallet-links
+ 104-wire-gateway-management-API
999-template