| Internet-Draft | Payment Execution Finality | August 2026 |
| Das | Expires 28 February 2027 | [Page] |
Payment rails already know how to move money. They do not know whether this generated instruction — this amount, this beneficiary, this rail, this purpose, from this agent or API worker — is the instruction that was authorized to move. A signed ISO 20022 message, an OAuth token on a PSP, a stored mandate, or a pass through 3-D Secure can all be valid while the act is wrong. The signature authenticates a channel. It does not bind a Candidate Act at the settlement sink.¶
That gap is now an agent gap. A model that can call payout.create, a RPA job that submits ACH, or a checkout agent that captures a card will treat tool selection as settlement authority. Fraud used to steal credentials and replay files. It now steals a seat or injects a document and asks the authorized worker to pay a new beneficiary at the old amount, or the old beneficiary at a new amount.¶
This document specifies a payment-side execution-finality profile. An instruction remains a Payment Candidate Act. A Protected Enforcement Domain binds principal, wallet or account, amount, currency, beneficiary, rail, purpose, policy epoch, and intended settlement sink, then commits evidence before scoped non-bearer authority is issued. The sink that would actually post, capture, or release funds verifies that authority against the live instruction and consumes it. A signed instruction is not settlement.¶
This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.¶
Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.¶
Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."¶
This Internet-Draft will expire on 28 February 2027.¶
Copyright (c) 2026 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License.¶
The payment path, whether a human treasury screen, a batch file, or an agent tool call, is now:¶
policy / model / ERP / agent
-> payment instruction {amount, payee, rail}
-> signed or tokenized submit
-> PSP / core / card / CBDC engine
-> funds move
¶
This document controls the last arrow. The computer may compute an instruction. The rail may authenticate the sender. Neither fact is authority for this amount to leave this account toward this beneficiary on this rail in this epoch.¶
The profile uses the two-boundary chain in [I-D.das-6g-finality] and the tool-dispatch objects in [I-D.das-agentic]. It specifies only payment predicates those drafts leave as an enum: amount and currency binding, beneficiary and rail binding, mandate and purpose binding, idempotency as consume-once authority, and coexistence with ISO 20022, ACH, card capture, and instant-payment APIs.¶
Historical payment crime stole the thing that was already money: a card number, a cheque, a batch file, a SWIFT token on a compromised treasury PC. Defense was instrument-centric. Reissue the card. Rotate the MAC. Watch for a file that did not match yesterday's template. The valuable object was a credential or a pre-built instruction the business had already approved as a class.¶
Present abuse often leaves the instrument in place. The attacker takes an ERP session, a PSP dashboard cookie, an agent seat with payout.create enabled, or a poisoned invoice PDF that the AP bot is allowed to read. Strong customer authentication and OAuth are up. The worker is allowed to pay. The instruction it emits is not the one the mandate described.¶
Business email compromise already showed the pattern without models: a lawful payer, a new beneficiary string, a valid signature on the channel. Agentic checkout and AP agents industrialize that pattern. Every tool-enabled seat is a treasury clerk that never sleeps and that will treat retrieved text as an instruction.¶
The coming incident does not need the attacker to format ISO 20022. The model formats it. A reconstructed "we owe this vendor" join from mail plus ERP plus a prompt-injected statement becomes a Candidate Act that looks like ordinary automation. If the settlement engine accepts any authenticated submit from that worker, reconstruction becomes cash.¶
This profile exists so that even a correctly signed, schema-valid, token-bearing instruction remains non-effective until the sink checks the act digest against current authority. Settlement is not a synonym for "the API returned 200."¶
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.¶
Failure to establish current payment-finality authority MUST NOT be converted into permission to post, capture, release, or otherwise settle funds.¶
A payment instruction is a Candidate Act with a consequence class that is almost never informational. Once the rail posts, reversal is a business process, not a protocol rollback. The failures this profile treats as first-class are:¶
In each case some upstream control can be green: the OAuth token is live, the ISO message verifies, the mandate exists, the agent is in the allowlist. The missing question is the sink question: may this exact instruction settle now.¶
Messaging standards authenticate and structure instructions. They bind fields inside a message. They do not, by themselves, consume single-use authority at the posting engine, and they do not stop a second valid message that differs in one load-bearing field after a human or model "approved payments."¶
Tokens establish that a client may call a payment API. Consent often names an account and a period. Possession of the token is bearer-like with respect to every instruction that token can form. Act digest, beneficiary bind, and consume-once are application additions, not properties of the token.¶
Payer authentication answers "is a human present for this merchant session." It does not hash the agent-generated split of amount and payee after the session, and it does not sit in a payout API that never sees a cardholder.¶
A chain can make a transfer final among participants. It does not decide whether the off-chain agent was authorized to construct that transfer. Posting a stolen-but-valid signed payload is still settlement. External anchoring MUST NOT retroactively authorize an act that was invalid at effectuation time.¶
A mandate is a class approval. Vendor master data is a directory. Neither is an act-specific, amount-bound, nonce-bound capability at the sink. An agent that is allowed to "pay approved vendors" can still pick the wrong vendor row or the wrong invoice total.¶
Maker-checker is the correct industrial instinct. It is slow, and agents exist to skip the queue. This profile does not delete dual control. It makes the checker a predicate that can be a human share, a policy share, or a threshold of both, and it still requires the sink consume the resulting authority.¶
A commerce agent or checkout API already holds a capture token. The sink is the capture or refund call. Authority issued for capture 40.00 at merchant M MUST fail for 4,000.00 or merchant M2.¶
The sink is the posting service that would debit the payer and emit the clearing message. ISO 20022 remains the payload. The Finality Sink verifies the Payment Candidate Act before the message is considered released to clearing.¶
ERP and TMS tools already batch pay. An agent that reads invoices is the present-theft seat. The RAO-like purpose bind is invoice-pay. Payroll and tax rails are different consequence scopes.¶
A claims agent proposing a payee from a repair invoice is a beneficiary-substitution surface. Settlement authority MUST bind payee account digest, not only claim-id.¶
Many small legs share one worker token. Each leg is its own Candidate Act. A digest for worker A / 1200.00 MUST not authorize worker B / 1200.00.¶
Whether the engine is a CBDC participant, an e-money ledger, or an internal book, the sink is the component that would change balances. This document does not specify a CBDC protocol. It specifies that balance change is effectuation.¶
payout.create on an MCP server is a tool call under [I-D.das-agentic] and a payment act under this profile. Both checks are required when the tool would move value. Discovery of the server is not mandate authority.¶
Pick one outbound API used by a new AP agent or by a single PSP payout route. Deploy the consume table next to the existing idempotency store. Issue authority only for that route's currency and ceiling. Leave the green-screen path on maker-checker. Measure posts without authority_id, digest mismatches, replay attempts, and added latency at p50 and p99. Expand rail by rail. This is a pilot, not a core replacement.¶
Offer an optional header carrying the authority object and the instruction digest the platform computed. If the header is absent, today's token behavior remains for legacy platforms. If the header is present, the PSP becomes a cooperating sink and rejects mismatch. Platforms that run agents SHOULD move to header-present. That is how the profile meets the industry without a flag day.¶
ERP / agent / checkout emits instruction
|
v
PAYMENT CANDIDATE ACT
|
v
Non-Effective State
|
v
Protected Enforcement Domain
payer, account, amount, CCY
beneficiary, rail, purpose
mandate / SCA inputs
policy and revocation epochs
intended settlement sink
|
v
evidence + scoped authority
|
v
SETTLEMENT SINK
|
+-- PASS -> post once, consume authority
|
`-- FAIL -> no balance change
Stage notes that implementations get wrong:¶
Canonicalization MUST include amount scale. 150, 150.0, and 150.00 are the same act only if the canonical form says so. Implementations SHOULD normalize to a fixed scale per currency (two for EUR, zero for JPY unless policy says otherwise) before hashing.¶
expires_at on the Candidate Act is not the rail cut-off time. It is the authority TTL. A SEPA instant that settles in two seconds still uses a short TTL so a queued instruction cannot sit overnight and post after a risk-list change.¶
Evidence commitment is constitutive. A PED that returns ALLOW=true without a stored evidence_id has issued a boolean, not this profile. The sink MUST be able to resolve the evidence or MUST treat the authority as invalid.¶
Consume and post SHOULD be one database transaction or an equivalent compare-and-swap. Consume-then- crash-then-repost without a durable consume bit is a replay window. Post-then-crash-then-fail-to- consume is a double-post window on retry. The safe pattern is: write consumed+pending, post, write posted; retries read the row.¶
function ON_PAYMENT_INSTRUCTION(ins, ctx):
act = PaymentCandidateAct{
candidate_act_id: fresh_id(),
act_type: map_rail(ins.rail),
payer: ctx.payer,
amount: ins.amount,
currency: ins.currency,
beneficiary: canonical_payee(ins),
rail: ins.rail,
purpose: ins.purpose,
instruction_digest: HASH(canonicalize(ins)),
policy_state: current_epochs(),
freshness: {nonce: fresh_nonce()},
finality_sink: ctx.settlement_sink,
expires_at: now() + short_ttl
}
HOLD_NON_EFFECTIVE(act)
return PED_VALIDATE(act, ins)
function PED_VALIDATE(act, ins):
if malformed(act) or amount <= 0:
return DENY(MALFORMED_ACT)
if not fresh(act.freshness.nonce):
return DENY(REPLAY_OR_STALE)
if act.policy_state != current_epochs():
return DENY(EPOCH_MISMATCH)
if not mandate_covers(act):
return DENY(MANDATE_MISS)
if beneficiary_blocked(act.beneficiary):
return DENY(PAYEE_BLOCKED)
if act.amount > hot_envelope_ceiling(act) and
not cold_path_approved(act):
return ESCALATE_OR_DENY(AMOUNT_ENVELOPE)
evidence = COMMIT_PROTECTED_EVIDENCE(act)
authority = ISSUE_SCOPED_AUTHORITY(act, evidence)
return ALLOW(authority)
function SETTLEMENT_SINK_POST(act, authority, live):
live_digest = HASH(canonicalize(live))
if authority missing or integrity_fail(authority):
return DENY(NO_OR_INVALID_AUTHORITY)
if live_digest != act.instruction_digest:
return DENY(INSTRUCTION_SUBSTITUTION)
if authority.sink_id != THIS_SINK:
return DENY(SINK_MISMATCH)
if live.amount != authority.amount or
live.currency != authority.currency or
canonical_payee(live) != authority.beneficiary:
return DENY(FIELD_MISMATCH)
if expired(authority) or consumed(authority):
return DENY(STALE_OR_USED)
ATOMICALLY:
consume(authority)
advance_replay_state(act.freshness.nonce)
result = post_to_rail(live)
record_sink_evidence(act, authority, result)
return result
¶
post_to_rail() is unreachable when any check fails. A 200 from an inner HTTP client after a failed check is non-conforming.¶
Objects are UTF-8 JSON. additionalProperties MUST be treated as reject for load-bearing objects. Amounts SHOULD be decimal strings to avoid binary float drift. Canonicalization for the digest SHOULD be JCS or an implementation-defined stable form declared in the digest object.¶
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "urn:ietf:params:json-schema:payment-finality:candidate-act:1",
"title": "PaymentCandidateAct",
"type": "object",
"additionalProperties": false,
"required": [
"version", "object_type", "candidate_act_id", "act_type",
"created_at", "expires_at", "payer", "amount", "currency",
"beneficiary", "rail", "purpose", "instruction_digest",
"policy_state", "freshness", "finality_sink"
],
"properties": {
"version": { "type": "string", "const": "1.0" },
"object_type": {
"type": "string",
"const": "payment_candidate_act"
},
"candidate_act_id": {
"type": "string",
"minLength": 16,
"maxLength": 128
},
"act_type": {
"type": "string",
"enum": [
"CREDIT_TRANSFER", "DIRECT_DEBIT", "CARD_CAPTURE",
"CARD_REFUND", "INSTANT_CREDIT", "BOOK_TRANSFER",
"STORED_VALUE", "CBDC_TRANSFER", "PAYOUT",
"FX_PAYOUT", "OTHER"
]
},
"created_at": { "type": "string", "format": "date-time" },
"expires_at": { "type": "string", "format": "date-time" },
"payer": {
"type": "object",
"required": ["payer_id", "account_ref"],
"properties": {
"payer_id": { "type": "string" },
"payer_type": {
"type": "string",
"enum": ["person", "merchant", "enterprise", "agent", "service"]
},
"account_ref": { "type": "string" },
"agent_id": { "type": "string" },
"workload_id": { "type": "string" }
}
},
"amount": {
"type": "string",
"pattern": "^[0-9]+(\\.[0-9]{1,18})?$"
},
"currency": {
"type": "string",
"minLength": 3,
"maxLength": 8
},
"beneficiary": {
"type": "object",
"required": ["beneficiary_id"],
"properties": {
"beneficiary_id": { "type": "string" },
"account_ref": { "type": "string" },
"account_digest": { "type": "string" },
"name_digest": { "type": "string" },
"jurisdiction": { "type": "string" }
}
},
"rail": {
"type": "object",
"required": ["rail_id"],
"properties": {
"rail_id": {
"type": "string",
"enum": [
"ISO20022", "ACH", "SEPA", "SEPA_INSTANT",
"FEDNOW", "CARD", "SWIFT", "ON_US",
"CBDC", "STORED_VALUE", "OTHER"
]
},
"scheme": { "type": "string" },
"clearing_system": { "type": "string" }
}
},
"purpose": {
"type": "object",
"required": ["purpose_id"],
"properties": {
"purpose_id": { "type": "string" },
"declared_purpose": { "type": "string" },
"invoice_ref": { "type": "string" },
"mandate_id": { "type": "string" },
"end_to_end_id": { "type": "string" }
}
},
"instruction_digest": {
"type": "object",
"required": ["algorithm", "value", "canonicalization"],
"properties": {
"algorithm": {
"type": "string",
"enum": ["SHA-256", "SHA-384", "SHA-512"]
},
"value": { "type": "string" },
"canonicalization": {
"type": "string",
"enum": ["JCS", "implementation-defined"]
}
}
},
"policy_state": {
"type": "object",
"required": ["policy_epoch", "revocation_epoch"],
"properties": {
"policy_epoch": { "type": "integer", "minimum": 0 },
"revocation_epoch": { "type": "integer", "minimum": 0 },
"risk_list_epoch": { "type": "integer", "minimum": 0 }
}
},
"freshness": {
"type": "object",
"required": ["nonce"],
"properties": {
"nonce": { "type": "string", "minLength": 16 },
"sequence": { "type": "integer", "minimum": 0 },
"idempotency_key": { "type": "string" }
}
},
"finality_sink": {
"type": "object",
"required": ["sink_id", "sink_type"],
"properties": {
"sink_id": { "type": "string" },
"sink_type": {
"type": "string",
"enum": [
"CORE_POST", "PSP_CAPTURE", "ACH_RELEASE",
"INSTANT_RELEASE", "CARD_ACQUIRER",
"CBDC_ENGINE", "LEDGER_POST", "OTHER"
]
}
}
}
}
}
¶
{
"version": "1.0",
"object_type": "protected_validation_evidence",
"evidence_id": "pve-pay-01",
"candidate_act_id": "act-pay-7f39",
"decision": "ALLOW",
"validated_predicates": {
"mandate_covers": true,
"amount_in_envelope": true,
"beneficiary_not_blocked": true,
"rail_permitted": true,
"purpose_valid": true,
"epoch_valid": true,
"freshness_valid": true,
"sink_binding_valid": true
},
"issued_at": "2026-08-27T02:15:01Z",
"protector": {
"type": "HSM_SIGNATURE",
"key_id": "ped-pay-3",
"signature": "base64url-signature"
}
}
¶
{
"version": "1.0",
"object_type": "payment_finality_authority",
"authority_id": "pfa-88c0",
"candidate_act_id": "act-pay-7f39",
"evidence_id": "pve-pay-01",
"scope": {
"act_type": "INSTANT_CREDIT",
"amount": "150.00",
"currency": "EUR",
"beneficiary_id": "vendor-441",
"rail_id": "SEPA_INSTANT"
},
"binding": {
"instruction_digest": {
"algorithm": "SHA-256",
"value": "base64url-ins-digest"
},
"nonce": "A81D4F42B779CC21",
"policy_epoch": 12,
"revocation_epoch": 4,
"finality_sink_id": "core-post-eu-1"
},
"lifetime": {
"issued_at": "2026-08-27T02:15:01Z",
"expires_at": "2026-08-27T02:15:11Z",
"single_use": true
}
}
¶
{
"operation": "SettlementSinkVerify",
"request_id": "stl-req-09",
"decision": "ALLOW",
"verification": {
"authority_signature": "VALID",
"instruction_digest": "MATCH",
"amount": "MATCH",
"beneficiary": "MATCH",
"rail": "MATCH",
"nonce": "FRESH",
"epochs": "CURRENT",
"consumption_state": "UNUSED",
"sink_binding": "MATCH"
},
"consumption": {
"authority_id": "pfa-88c0",
"status": "CONSUMED"
},
"effectuation": {
"permitted": true,
"settlement_ref": "e2e-441-19"
}
}
¶
{
"operation": "SettlementSinkVerify",
"request_id": "stl-req-10",
"decision": "DENY",
"error": {
"code": "EF_INSTRUCTION_SUBSTITUTION",
"message": "Live amount does not match authorized digest.",
"retryable": false
},
"effectuation": { "permitted": false }
}
¶
{
"step_1_instruction": {
"act_type": "INSTANT_CREDIT",
"amount": "150.00",
"currency": "EUR",
"beneficiary_id": "vendor-441",
"rail_id": "SEPA_INSTANT",
"purpose_id": "invoice-pay",
"status": "NON_EFFECTIVE"
},
"step_2_digest": {
"algorithm": "SHA-256",
"value": "base64url-ins-digest"
},
"step_3_authority": {
"authority_id": "pfa-88c0",
"single_use": true,
"expires_at": "2026-08-27T02:15:11Z"
},
"step_4_sink": {
"decision": "ALLOW",
"authority_consumed": true
},
"step_5_effect": "SEPA instant posted once for digest-bound 150.00 EUR"
}
¶
Authority for amount 150.00 / EUR / vendor-441 / SEPA_INSTANT / digest D1 MUST NOT authorize 1,500.00, USD, vendor-442, ACH, or digest D2. DISPLAY-equivalent mistakes in this profile are amount substitution, beneficiary substitution, and rail substitution.¶
Book-transfer authority MUST NOT automatically authorize an external instant rail. Refund authority MUST NOT automatically authorize a new capture. Authority created under revocation epoch N MUST fail at N+1, including after a beneficiary-risk-list update.¶
A profile that cannot sit on a 200 ms instant-payment path, or that demands a greenfield ledger, will not be deployed. This section states how the checks fit existing engines.¶
The load-bearing work at the sink is: parse live fields, hash the canonical instruction, verify a signature or MAC on the authority, compare digest and sink id, test consume-once, post. That is the same order of cost as an existing idempotency-key table plus an HSM MAC verify. Representative targets, not protocol constants:¶
The architectural split is the same as in [I-D.das-6g-finality]: hot path still verifies at the sink; cold path must not be skipped by timeout.¶
A cold evaluation MAY establish a bounded envelope: payer account A, rail SEPA_INSTANT, currency EUR, max amount 500.00, beneficiary set S, purpose invoice-pay, epochs (P,R), sink core-post-eu-1, valid-until T. Later instructions inside that envelope MAY use local validation. Any change of beneficiary, rail, currency, ceiling, or epoch MUST escalate. "Hot" means cached predicates. It does not mean skip consume or skip digest compare.¶
The Finality Sink SHOULD sit immediately before the component that marks an instruction released to clearing. The ISO message can carry an end-to-end id that equals or hashes to candidate_act_id. The authority object MAY travel in an accompanying API header, a private supplementary data block, or a side channel keyed by that id. Clearing houses that cannot parse the object still receive only messages the sink has already admitted.¶
Batch ACH or payroll files are N Candidate Acts, not one. A file-level signature is channel authentication. Each row still needs a digest and consume, or a tightly bound envelope that lists row digests.¶
Capture and refund APIs already have idempotency keys. This profile treats that key as insufficient if the body can change under the same key, and as insufficient if a second acquirer accepts the same logical act. The sink is the acquirer-facing submitter in the merchant or platform, or the acquirer itself where the acquirer agrees to verify the authority object.¶
Cores already implement posting, holds, and double-entry. The feasible placement is a pre-post adapter: no new general ledger, no new rail. The consume table can be a compare-and-swap row next to the existing idempotency store. If the core cannot host the adapter, the adapter MUST be in-line so that bypassing it makes posting impossible.¶
For MCP and function-calling agents the payment tool handler MUST call SETTLEMENT_SINK_POST before any PSP SDK. A local Python or TypeScript enforcer that only checks allowlists is not this profile unless it consumes digest-bound authority. Combining this document with [I-D.das-agentic] means: build AgentCandidateAct for the tool call and PaymentCandidateAct for the value move, or one object that satisfies both required sets.¶
Timeout toward the PED is hold-or-deny, the same instinct as "do not auto-approve an SCA fail." Duplicate file submission is consume-once, the same instinct as ACH duplicate detection, but keyed by digest not by filename. Beneficiary change after checker approval is field mismatch, the same instinct as maker-checker, automated.¶
A pilot that only wraps the payout API used by the new agent, and leaves the green-screen treasury path on dual control, is a conforming incremental deployment. Alternate-path closure says the green-screen path MUST eventually grow the same consume semantics if it can emit the same rail message. It does not require a big-bang cutover on day one.¶
The following mapping is informative and does not modify ISO 20022. It exists so a payments engineer can see that this profile is an admission gate, not a competing message.¶
If InstdAmt changes after authority issuance, the live digest MUST fail. If only ReltdRmtInf narrative changes and policy treats remittance as non-load-bearing, implementations MAY exclude it from the digest. That exclusion MUST be explicit. Silent exclusion of creditor account is non-conforming.¶
amount and currency map to capture amount. beneficiary_id maps to merchant or sub-merchant. rail CARD maps to the acquirer route. purpose may carry order_id. A refund is a different act_type. Using a capture authority to refund, or a 40.00 capture authority to capture 400.00, MUST deny. 3DS or SCA results MAY appear as PED inputs. They MUST NOT appear as a reason to skip sink consume.¶
It does not require replacing SWIFT, cards, or ISO 20022. It does not require a blockchain on the posting path. It does not require the model to detect fraud. It does not require every micropayment to do remote attestation. It requires that the component which can change a balance refuse to do so without current, consumed, digest-bound authority.¶
An FX payout is two load-bearing facts: the debit in currency C1 and the credit in currency C2 toward a beneficiary in administration J. Implementations MAY model this as one Candidate Act whose digest includes both amounts, both currencies, the FX rate or rate source epoch, and J, or as two acts that MUST NOT settle independently if the business meaning is a single payout. Silent settlement of only the cheap leg is a substitution.¶
Correspondent or cover payments MUST NOT inherit authority issued for the customer credit. A cover bank is another sink. Authority sink-bound to core-post-eu-1 MUST fail at correspondent-post-us-9 unless a new act is authorized.¶
Where policy requires maker-checker, the PED MUST treat missing checker shares as validation failure, not as a warning. Shares MAY be human approvals, independent policy engines, or a k-of-n MAC. The issued Payment Finality Authority SHOULD list the share identifiers. The sink MUST reject an authority whose share set no longer meets k.¶
Agents do not replace the checker. An agent MAY be the maker. If an agent is also allowed to be a checker, that fact MUST be explicit in policy and SHOULD be excluded for FINANCIAL class above the hot envelope. Dual control that lives only in a UI, while an API posts without shares, is an alternate path and is non-conforming for that API.¶
The protocol does not require the model to label these attacks. It requires post_to_rail() to be unreachable when the corresponding bind fails.¶
{
"scenario": "BEC_BENEFICIARY_SWAP",
"authorized": {
"amount": "150.00",
"currency": "EUR",
"beneficiary_id": "vendor-441"
},
"live": {
"amount": "150.00",
"currency": "EUR",
"beneficiary_id": "attacker-992"
},
"sink": {
"decision": "DENY",
"code": "EF_BENEFICIARY_MISMATCH",
"posted": false
}
}
¶
{
"scenario": "REPLAY_SECOND_PSP",
"authority_id": "pfa-88c0",
"first_sink": {
"sink_id": "core-post-eu-1",
"status": "CONSUMED"
},
"second_sink": {
"sink_id": "psp-alt-2",
"decision": "DENY",
"code": "EF_SINK_MISMATCH"
}
}
¶
{
"scenario": "AGENT_AMOUNT_ENVELOPE",
"hot_ceiling": "500.00",
"requested": "5000.00",
"decision": "DENY",
"code": "EF_AMOUNT_ENVELOPE",
"remediation": "ESCALATE_TO_CHECKER"
}
¶
This document does not claim to satisfy PSD2, SCA, PCI DSS, AML, or sanctions-screening obligations. Those systems produce inputs (SCA result, screening epoch, card-data scope) that the PED MAY require. A clean screening result is not settlement authority for a substituted beneficiary.¶
Operations staff SHOULD log authority_id, instruction_digest, consume time, and settlement_ref together. That tuple is what a scheme or correspondent will ask for after a dispute. Logging the model prompt instead of the digest is not a substitute.¶
Illustrative codes, not IANA assignments: EF-002 NO_FINALITY_AUTHORITY, EF-005 AUTHORITY_ALREADY_USED, EF-006 REPLAY_DETECTED, EF-023 INSTRUCTION_SUBSTITUTION, EF-024 AMOUNT_MISMATCH, EF-025 BENEFICIARY_MISMATCH, EF-026 RAIL_MISMATCH, EF-027 MANDATE_MISS, EF-028 PAYEE_BLOCKED, EF-029 AMOUNT_ENVELOPE, EF-040 SINK_MISMATCH, EF-070 ESCALATION_REQUIRED, EF-080 FAIL_CLOSED.¶
A denial MAY name a remediation (re-quote amount, escalate checker, refresh risk list) but MUST NOT post. Timeout MUST NOT post.¶
When the instruction is born as a model tool call, two objects SHOULD be created from the same canonical arguments:¶
{
"agent_candidate_act": {
"object_type": "agent_candidate_act",
"act_type": "TOOL_CALL",
"tool": {
"tool_id": "payout.create",
"function_id": "create",
"tool_protocol": "MCP"
},
"arguments_digest": {
"algorithm": "SHA-256",
"value": "base64url-args"
},
"consequence_class": "FINANCIAL",
"finality_sink": {
"sink_id": "mcp-dispatch-1",
"sink_type": "TOOL_DISPATCH"
}
},
"payment_candidate_act": {
"object_type": "payment_candidate_act",
"act_type": "PAYOUT",
"amount": "150.00",
"currency": "EUR",
"beneficiary": { "beneficiary_id": "vendor-441" },
"rail": { "rail_id": "SEPA_INSTANT" },
"instruction_digest": {
"algorithm": "SHA-256",
"value": "base64url-ins-digest"
},
"finality_sink": {
"sink_id": "core-post-eu-1",
"sink_type": "CORE_POST"
}
},
"rule": "dispatch sink may run the tool handler; posting sink may move funds; neither check replaces the other"
}
¶
If only the tool dispatcher checks, a handler bug can still post. If only the posting sink checks, a different tool (raw HTTP to the PSP) can still fire. Alternate-path closure requires both for agent-originated value movement.¶
Threats the sink is intended to make non-completable without current authority include: amount and payee substitution, rail laundering, replay across PSP and core, stolen agent seat, injected invoice, mandate over-use, risk-list staleness, and a second path (manual file drop) that posts the same funds.¶
If the Settlement Sink holds unrestricted posting rights and skips verification, the profile collapses for that path. High-assurance deployments SHOULD keep authority-signing keys off the agent host and SHOULD treat payment class as cold-path default above a ceiling.¶
Idempotency keys supplied by the client are not consume-once authority. An attacker who can change the body and keep the key, or who can mint a new key for the same digest after consume, is out of scope for "we already have idempotency." The consume table MUST be keyed by instruction digest and authority_id, not only by a client string.¶
Sanctions and AML screens are necessary and insufficient. A clean screen of beneficiary-441 does not authorize a post to attacker-992. Screen results SHOULD be bound into PED evidence as an epoch or list identifier so a later list update invalidates unused authority.¶
Instruction objects carry account references and payee identifiers. Implementations SHOULD hash account numbers in logs and SHOULD prefer account_digest in authority objects that leave the posting host. Cross-border acts MUST treat beneficiary jurisdiction as a load-bearing field, not as telemetry.¶
A deployment may call itself aligned with this profile when all of the following are true on the covered rail:¶
This document requests no IANA actions.¶
Concepts in this profile are associated with the DAS Protocols family, including International Application PCT/IB2026/055615. IETF disclosure should follow BCP 79 [RFC8179].¶
Rails move money. Agents and APIs now write the instruction. A signature on the channel, a token on the worker, or a mandate on the vendor is not authority for this amount to settle toward this beneficiary on this rail now. Bind the digest. Commit evidence. Consume at the posting sink. A signed instruction is not settlement.¶