commit 77e72cd258a642c08648c8e666cc7c918abccc6a
parent 45def97aa7b1113b423f56eea4692b4750e1e901
Author: Christian Grothoff <christian@grothoff.org>
Date: Thu, 6 Aug 2026 15:43:34 +0200
expand auditor spec
Diffstat:
2 files changed, 55 insertions(+), 37 deletions(-)
diff --git a/core/api-auditor.rst b/core/api-auditor.rst
@@ -486,29 +486,36 @@ paid; a ``wire_out`` row is. This endpoint lists the transfers that are stuck
in between, one row per wire transfer identifier, and the row disappears once
the transfer is made.
-The exchange has one legitimate reason to sit on such funds: it may not pay
-out to an account whose KYC/AML requirements are still open. ``aml_hold``
-says whether the auditor found such a requirement. The two cases are also
-totalled separately, as the ``total_aml_hold`` and ``total_transfer_lag``
-balances (see :ref:`balances-list`), so that "the exchange is waiting for
-legitimization data" and "the exchange is not paying" cannot be confused with
-each other or with having paid.
+The exchange has two legitimate reasons to sit on such funds. It may not pay
+out to an account whose KYC/AML requirements are still open; and it need not
+make a transfer whose amount would not survive the wire fee, in which case it
+waits for further deposits to the same account. ``deferral_reason`` says which
+of the two the exchange gave, or ``NONE`` if it gave neither. The three cases
+are also totalled separately, as the ``total_aml_hold``,
+``total_small_aggregate`` and ``total_transfer_lag`` balances (see
+:ref:`balances-list`), so that "the exchange is waiting for legitimization
+data", "the exchange is waiting for more deposits" and "the exchange is not
+paying" cannot be confused with each other or with having paid.
Whether a hold is *justified* is a question for a human, not for the auditor:
the auditor reports what is being withheld and from whom, and leaves the
judgement to the reader. What it does check is that the exchange does not get
-to withhold money silently.
+to withhold money silently, and that the reason it gives is consistent with
+everything else the auditor can see.
.. note::
- ``aml_hold`` is the auditor's own inference, drawn from the KYC measures on
- file for the recipient, not a claim replicated from the exchange. The
- exchange records its reason in ``aggregation_transient``, a table that has no
- serial ID and is updated in place, so ``taler-auditor-sync`` cannot replicate
- it and an external auditor never sees it. An exchange-internal audit (the
- ``-i`` option of **taler-helper-auditor-transfer**) does read that table, and
- reports any disagreement between it and the inference as a row inconsistency
- rather than silently deferring to the exchange.
+ ``deferral_reason`` is the exchange's own statement, taken from its
+ append-only ``aggregation_deferrals`` table, which is replicated to the
+ auditor. The auditor does not overrule it, but it does check it: the amount
+ against its own recomputation of what the aggregate is worth, and a ``KYC``
+ claim against the ``legitimization_measures`` and
+ ``legitimization_processes`` tables. If the exchange names a measure that is
+ not open, the hold is still reported as a ``KYC`` hold and a row
+ inconsistency against ``aggregation_deferrals`` is raised alongside it. An
+ exchange-internal audit (the ``-i`` option of
+ **taler-helper-auditor-transfer**) additionally compares the claim with the
+ live ``aggregation_transient`` table, which cannot be replicated.
.. include:: auditor/get-monitoring-aml-holds.rst
@@ -605,23 +612,30 @@ Balances
Returns the various balances the auditor tracks for the exchange, such as
coins in circulation, fees earned, losses experienced, etc.
-Two of the keys concern money the exchange owes a merchant and has not sent:
+Three of the keys concern money the exchange owes a merchant and has not sent.
+Which one an amount lands in follows the reason the exchange gave for not
+sending it:
``total_aml_hold``
- The exchange decided what it owes on a wire transfer, did not execute it, and
- has a KYC measure open against the recipient that explains the delay. This
- is money withheld pending legitimization.
+ The exchange says a KYC/AML requirement against the recipient is still open.
+ This is money withheld pending legitimization.
+
+``total_small_aggregate``
+ The exchange says what it has collected for the account so far would not
+ cover the wire fee, so it is waiting for further deposits. This is normal
+ operation rather than an irregularity; it has a key of its own so that it
+ does not inflate the other two, which are not.
``total_transfer_lag``
- The same, except that the auditor found no legitimization requirement for the
- recipient. This is money the exchange simply has not paid out.
-
-Both are current state rather than a running tally: the amount is recomputed
-from the outstanding transfers on every audit run, so it falls again as
-transfers are executed, and a transfer moves between the two keys if the KYC
-measure that explained it is decided (or if one appears). They are distinct
-from ``total_amount_lag``, which counts deposits the exchange has not
-aggregated at all. The individual transfers behind both keys are listed by
+ The exchange gave no reason at all. This is money the exchange simply has
+ not paid out.
+
+All three are current state rather than a running tally: the amount is
+recomputed from the outstanding transfers on every audit run, so it falls again
+as transfers are executed, and a transfer moves between the keys if the
+exchange changes what it says about it. They are distinct from
+``total_amount_lag``, which counts deposits the exchange has not aggregated at
+all. The individual transfers behind all three keys are listed by
:ref:`aml-holds-list`.
.. include:: auditor/get-monitoring-balances.rst
diff --git a/core/auditor/get-monitoring-aml-holds.rst b/core/auditor/get-monitoring-aml-holds.rst
@@ -54,14 +54,18 @@
// fees. The wire fee is not yet deducted.
amount : Amount;
- // True if the exchange has a KYC measure open against the
- // recipient, so that withholding the funds is explained.
- // False if the auditor found no legitimization requirement,
- // in which case the exchange is simply not paying.
- aml_hold : boolean;
-
- // The open measure the hold was attributed to, or 0 if
- // aml_hold is false.
+ // Reason the exchange gave for not making the transfer:
+ // "KYC" if a legitimization requirement against the recipient
+ // is still open, "AMOUNT_TOO_SMALL" if the aggregate would not
+ // cover the wire fee and the exchange is waiting for further
+ // deposits, "NONE" if the exchange gave no reason at all.
+ deferral_reason : "KYC" | "AMOUNT_TOO_SMALL" | "NONE";
+
+ // The measure the exchange named as blocking the payout, or 0
+ // if it named none. Note that the auditor checks whether that
+ // measure is in fact open and raises a row inconsistency if it
+ // is not, but still reports the hold as the exchange classified
+ // it.
legitimization_measure_serial_id : Integer;
// When the auditor first saw this transfer being withheld.