<?xml version='1.0' encoding='utf-8'?>
<?xml-stylesheet type="text/xsl" href="rfc2629.xslt" ?>
<rfc category="info" docName="draft-das-payment-execution-finality-01"
     ipr="trust200902" submissionType="IETF" xml:lang="en" version="3"
     tocInclude="true" tocDepth="3" symRefs="true" sortRefs="true">
  <front>
    <title abbrev="Payment Execution Finality">A Signed Instruction Is Not Settlement: Finality for Agentic and API Payments</title>
    <seriesInfo name="Internet-Draft" value="draft-das-payment-execution-finality-01"/>
    <author fullname="Sangam Das" initials="S." surname="Das">
      <organization>Independent Inventor</organization>
      <address>
        <postal>
          <city>Balasore</city>
          <region>Odisha</region>
          <code>756001</code>
          <country>India</country>
        </postal>
        <email>info@sangamdas.com</email>
      </address>
    </author>
    <date year="2026" month="September" day="9"/>
    <area>Security</area>
    <keyword>payments</keyword>
    <keyword>settlement</keyword>
    <keyword>agentic AI</keyword>
    <keyword>execution finality</keyword>
    <keyword>ISO 20022</keyword>
    <abstract>
      <t>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.</t>
      <t>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.</t>
      <t>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.</t>
    </abstract>
  </front>
  <middle>
    <section anchor="intro">
      <name>Introduction</name>
      <t>The payment path, whether a human treasury screen, a
      batch file, or an agent tool call, is now:</t>
      <artwork><![CDATA[
policy / model / ERP / agent
        -> payment instruction {amount, payee, rail}
        -> signed or tokenized submit
        -> PSP / core / card / CBDC engine
        -> funds move
]]></artwork>
      <t>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. <xref target="problem"/> enumerates the
      failure classes this profile treats as first-class;
      <xref target="existing"/> surveys what messaging standards,
      tokens, step-up authentication, ledgers, mandates, and
      dual control already bind and what each leaves open;
      <xref target="difference"/> states the difference from
      those traditional channel and class controls and what
      this profile adds at the settlement sink.</t>
      <t>The profile uses the two-boundary chain in
      <xref target="I-D.das-6g-finality"/> and the tool-dispatch
      objects in <xref target="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.</t>
    </section>

    <section anchor="theft">
      <name>How Payment Abuse Changed</name>
      <section>
        <name>Past: Steal the Instrument</name>
        <t>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.</t>
      </section>
      <section>
        <name>Present: Steal the Session that Can Pay</name>
        <t>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.</t>
        <t>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.</t>
      </section>
      <section>
        <name>Future: Compute the Payment, Then Settle It</name>
        <t>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.</t>
        <t>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."</t>
      </section>
    </section>

    <section anchor="rfc2119">
      <name>Requirements Language</name>
      <t>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
      <xref target="RFC2119"/> <xref target="RFC8174"/> when,
      and only when, they appear in all capitals, as shown
      here.</t>
      <t>Failure to establish current payment-finality authority
      MUST NOT be converted into permission to post, capture,
      release, or otherwise settle funds.</t>
    </section>

    <section anchor="problem">
      <name>Problem Space</name>
      <t>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:</t>
      <ul>
        <li>Amount substitution after approval of 50.00 as
        5,000.00 on the same beneficiary and rail.</li>
        <li>Beneficiary substitution after approval of a named
        payee.</li>
        <li>Rail substitution (internal book-transfer authority
        reused on an external instant rail).</li>
        <li>Replay of a consumed instruction under a new
        idempotency key or on a second PSP.</li>
        <li>Mandate or purpose drift (invoice-pay authority
        used for payroll or cross-border).</li>
        <li>Agent or worker substitution (approved bot A,
        executing bot B).</li>
        <li>Destination-account reuse after revocation or
        beneficiary-risk list update.</li>
      </ul>
      <t>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.</t>
    </section>

    <section anchor="existing">
      <name>Existing Solutions and What They Do Not Bind</name>
      <section>
        <name>ISO 20022, SWIFT, ACH, and Instant Rails</name>
        <t>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."</t>
      </section>
      <section>
        <name>OAuth, API Keys, and Open-Banking Consents</name>
        <t>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.</t>
      </section>
      <section>
        <name>3-D Secure, SCA, and Step-Up</name>
        <t>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.</t>
      </section>
      <section>
        <name>Ledgers, Blockchains, and Smart Contracts</name>
        <t>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.</t>
      </section>
      <section>
        <name>Mandates, Standing Orders, and Vendor Master Data</name>
        <t>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.</t>
      </section>
      <section>
        <name>Human Dual Control</name>
        <t>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.</t>
      </section>
      <section>
        <name>What This Profile Adds</name>
        <ul>
          <li>the instruction remains a Payment Candidate Act
          after the model, ERP, or file emitter produces
          it;</li>
          <li>amount, currency, beneficiary, rail, and purpose
          are in the digest;</li>
          <li>authority is non-bearer, sink-bound, epoch-bound,
          and consumed;</li>
          <li>idempotency is consume-once of that digest, not
          a client-supplied string the attacker can
          refresh;</li>
          <li>legacy rails remain the transport; the sink
          check is the gate;</li>
          <li>hot-path envelopes exist so instant payments
          are not forced through a remote ledger round
          trip.</li>
        </ul>
      </section>
    </section>

    <section anchor="difference">
      <name>Difference from Traditional Systems</name>
      <t>Traditional payment systems authenticate who may
      submit and that the message is well-formed. This
      profile binds whether this exact instruction may
      change a balance now. That is the difference.</t>

      <section>
        <name>What a Traditional System Binds</name>
        <t>A normal path is:</t>
        <artwork><![CDATA[
policy / ERP / agent
  -> instruction {amount, payee, rail}
  -> signed or tokenized submit
  -> PSP / core / card / CBDC
  -> funds move
]]></artwork>
        <t>Controls on that path answer a channel or class
        question. They do not answer the sink question.</t>
        <ul>
          <li>ISO 20022, SWIFT, ACH, and instant messages
          bind structure and sender authentication. They
          do not consume single-use authority at the
          posting engine for this amount, beneficiary,
          and rail.</li>
          <li>OAuth, API keys, and open-banking consents
          bind that a client may call a payment API for
          an account and period. Possession of the token
          is bearer-like for every instruction that
          token can form.</li>
          <li>3-D Secure and SCA bind that a human was
          present for a merchant session. They do not
          hash an agent-built split of amount and payee
          after the session, and they do not sit on a
          payout API that never sees a cardholder.</li>
          <li>A mandate, standing order, or vendor master
          record is a class approval or a directory. It
          is not an act-specific, amount-bound,
          nonce-bound capability at the sink. An agent
          allowed to "pay approved vendors" can still
          pick the wrong vendor row or the wrong
          invoice total.</li>
          <li>A client idempotency key binds that a
          string was seen. An attacker can keep the key
          and change the body, or mint a new key for
          the same act on a second PSP.</li>
          <li>A ledger or 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.</li>
          <li>Maker-checker binds two humans to a screen.
          An API can still post without those shares.
          Agents exist to skip the queue.</li>
        </ul>
        <t>The traditional failure mode is: channel valid,
        act wrong. 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 workers industrialize that pattern. Fraud
        used to steal the instrument. It now steals the
        session that can pay.</t>
      </section>

      <section>
        <name>Substitutions the Traditional Stack Does Not Stop at the Sink</name>
        <ul>
          <li>Amount substitution after approval of 50.00
          as 5,000.00 on the same beneficiary and
          rail.</li>
          <li>Beneficiary substitution after approval of
          a named payee.</li>
          <li>Rail substitution (internal book-transfer
          authority reused on an external instant
          rail).</li>
          <li>Replay of a consumed instruction under a
          new idempotency key or on a second PSP.</li>
          <li>Mandate or purpose drift (invoice-pay
          authority used for payroll or
          cross-border).</li>
          <li>Agent or worker substitution (approved bot
          A, executing bot B).</li>
          <li>Destination-account reuse after revocation
          or beneficiary-risk list update.</li>
        </ul>
        <t>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 whether
        this exact instruction may settle now.</t>
      </section>

      <section>
        <name>What This Profile Adds</name>
        <t>The profile inserts a hold state and a second
        check immediately before balance change.</t>
        <artwork><![CDATA[
instruction emitted
        |
        v
PAYMENT CANDIDATE ACT     (NON_EFFECTIVE)
        |
        v
Protected Enforcement Domain
  binds payer, account, amount, CCY,
        beneficiary, rail, purpose,
        mandate/SCA inputs, epochs, sink
        |
        v
evidence committed + scoped authority
        |
        v
SETTLEMENT SINK
  PASS -> post once, consume authority
  FAIL -> no balance change
]]></artwork>
        <t>The following properties are not protocol
        properties of ISO 20022, OAuth, 3-D Secure, or
        mandates. They are required by this profile:</t>
        <ol>
          <li>The generated instruction is a Payment
          Candidate Act. It is not settlement. Status
          starts NON_EFFECTIVE.</li>
          <li>An instruction digest covers canonical
          amount, currency, beneficiary, rail, account
          references, purpose, and other load-bearing
          fields. Changing any of them MUST change the
          digest. Authority for 150.00 EUR / vendor-441
          / SEPA_INSTANT / digest D1 MUST NOT authorize
          1,500.00, USD, vendor-442, ACH, or digest
          D2.</li>
          <li>The Protected Enforcement Domain validates
          predicates, commits Protected Validation
          Evidence, then issues scoped Payment Finality
          Authority. A PED that returns ALLOW=true
          without a stored evidence_id has issued a
          boolean, not this profile. Authority is
          non-bearer, sink-bound, epoch-bound, and
          single-use.</li>
          <li>The Settlement Sink is the component that
          would actually post, capture, or release. It
          verifies the live instruction against that
          authority and consumes the authority
          atomically with the post. post_to_rail() is
          unreachable when any check fails. A 200 from
          an inner HTTP client after a failed check is
          non-conforming. Timeout MUST NOT post.</li>
          <li>Idempotency is consume-once of the
          instruction digest and authority_id, not a
          client-supplied string the attacker can
          refresh.</li>
          <li>A mandate remains a class-level input to
          PED validation. It MUST NOT by itself be
          settlement authority for a particular act.</li>
          <li>A hot-path envelope MAY cache predicates
          (payer, rail, currency, ceiling, beneficiary
          set, epochs, sink). It MUST still compare the
          digest and sink identifier and MUST still
          consume. Cold path MUST NOT be skipped by
          timeout.</li>
          <li>When the instruction is born as a model
          tool call, both an agent or tool-dispatch
          check and a payment-sink check are required.
          If only the dispatcher checks, a handler bug
          can still post. If only the posting sink
          checks, a raw HTTP call to the PSP can still
          fire.</li>
          <li>Legacy rails remain the transport. ISO
          20022, ACH, cards, and instant APIs are not
          replaced. The admission gate sits immediately
          before the component that marks the
          instruction released to clearing or capture.</li>
        </ol>
      </section>

      <section>
        <name>Contrast</name>
        <t>Traditional controls bind the channel and the
        class of payer. This profile binds the candidate
        act at the settlement sink: amount, currency,
        beneficiary, rail, purpose, epoch, and
        consume-once authority.</t>
        <ul>
          <li>Traditional: a signature, token, mandate,
          or 3-D Secure result means the worker may
          pay. This profile: those results are inputs.
          Only consumed, sink-bound, digest-matched
          authority means this act may settle.</li>
          <li>Traditional: settlement is treated as
          equivalent to "the API returned 200." This
          profile: settlement is atomic consume plus
          post at the sink.</li>
          <li>Traditional: defense is instrument-centric
          and channel-centric. This profile: defense is
          act-centric at effectuation time.</li>
          <li>Traditional: replay and field substitution
          are application defects. This profile: they
          are protocol denies
          (EF_INSTRUCTION_SUBSTITUTION, EF_SINK_MISMATCH,
          EF_AUTHORITY_ALREADY_USED, and related
          codes).</li>
        </ul>
        <t>This document does not specify a new rail, a
        blockchain on the posting path, or a requirement
        that the model detect fraud. It does not claim
        to replace PSD2, SCA, PCI DSS, AML, or
        sanctions screening. Those systems produce
        inputs. A clean screen of vendor-441 does not
        authorize a post to attacker-992. A signed
        instruction is not settlement.</t>
      </section>
    </section>

    <section anchor="industry">
      <name>Industrial Applicability</name>
      <section>
        <name>PSP and Platform Checkout</name>
        <t>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.</t>
      </section>
      <section>
        <name>Bank Core, ACH, SEPA, and Instant Credit</name>
        <t>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.</t>
      </section>
      <section>
        <name>Corporate AP and Treasury Agents</name>
        <t>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.</t>
      </section>
      <section>
        <name>Claims, Insurance, and Benefit Payout</name>
        <t>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.</t>
      </section>
      <section>
        <name>Payroll and Marketplace Split</name>
        <t>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.</t>
      </section>
      <section>
        <name>CBDC, Stored Value, and On-Us Books</name>
        <t>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.</t>
      </section>
      <section>
        <name>Agentic Wallets and MCP Payment Tools</name>
        <t>payout.create on an MCP server is a tool call
        under <xref target="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.</t>
      </section>
      <section>
        <name>How a Bank Would Pilot in One Quarter</name>
        <t>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.</t>
      </section>
      <section>
        <name>How a PSP Would Expose It to Platforms</name>
        <t>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.</t>
      </section>
    </section>

    <section anchor="terminology">
      <name>Terminology</name>
      <dl newline="true">
        <dt>Payment Candidate Act</dt>
        <dd>A generated or selected instruction that would
        debit, credit, capture, refund, release, or otherwise
        change a balance, but has not been permitted to do
        so.</dd>
        <dt>Settlement Sink</dt>
        <dd>The component that would actually post the
        instruction to a ledger, clearing file, card
        acquirer, CBDC engine, or stored-value book. If
        that component can be skipped, it is not the
        sink.</dd>
        <dt>Instruction digest</dt>
        <dd>A hash over the canonical amount, currency,
        beneficiary, rail, account references, purpose, and
        other load-bearing fields. Changing any of them
        MUST change the digest.</dd>
        <dt>Mandate</dt>
        <dd>A class-level approval (vendor, merchant,
        account, ceiling). A mandate MAY be an input to
        PED validation. It MUST NOT by itself be
        settlement authority for a particular act.</dd>
      </dl>
    </section>

    <section anchor="architecture">
      <name>Architecture</name>
      <figure>
        <name>Settlement-time finality</name>
        <artwork><![CDATA[
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
]]></artwork>
      </figure>
    </section>

    <section anchor="workflow">
      <name>End-to-End Workflow</name>
      <ol>
        <li>Instruction constructed by agent, API, file, or
        human UI. Status NON_EFFECTIVE.</li>
        <li>Canonicalize load-bearing fields. Compute
        instruction digest.</li>
        <li>Build PaymentCandidateAct including payer,
        accounts, amount, currency, beneficiary, rail,
        purpose, freshness, sink, epochs.</li>
        <li>PED validates predicates, including mandate
        envelope and risk lists where required.</li>
        <li>On fail: deny; lock or quarantine the affected
        nonce, quota, or protected state to prevent replay,
        rollback, stale retry, substitution, or cross-sink
        reuse of the denied Candidate Act; commit denial
        evidence (a denial LAVR or equivalent protected
        enforcement record) as evidence of non-completion.
        Do not post.</li>
        <li>On pass: update and consume the relevant protected
        state (nonce, quota, budget, session, revocation, or
        monotonic counter) so that a concurrent or later
        validation pass cannot reuse the same protected state
        before evidence is committed.</li>
        <li>Commit Protected Validation Evidence, referencing
        the protected-state transition performed in the
        previous step.</li>
        <li>Issue scoped Payment Finality Authority bound to
        the digest, sink, epochs, and single-use flag.</li>
        <li>Transport the instruction on the legacy rail
        (ISO, ACH, card, HTTP) together with the
        authority reference or an attached object.</li>
        <li>Settlement Sink builds or reconstructs a sink-side
        descriptor of the live instruction and local
        effectuation boundary.</li>
        <li>Settlement Sink verifies the scoped authority
        against that sink-side descriptor, the digest, sink,
        boundary, nonce, policy epoch, evidence reference, and
        scope, immediately before post.</li>
        <li>Atomic consume + post, or fail-closed deny with no
        post if sink verification fails.</li>
        <li>Record sink-side settlement evidence. Do not
        treat rail ACK as a substitute for consume.</li>
      </ol>
      <t>Stage notes that implementations get wrong:</t>
      <t>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.</t>
      <t>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.</t>
      <t>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.</t>
      <t>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.</t>
    </section>

    <section anchor="pseudocode">
      <name>Settlement Pseudocode</name>
      <sourcecode type="pseudocode"><![CDATA[
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
]]></sourcecode>
      <t>post_to_rail() is unreachable when any check fails.
      A 200 from an inner HTTP client after a failed check
      is non-conforming.</t>
    </section>

    <section anchor="json">
      <name>JSON Interoperability Profile</name>
      <t>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.</t>

      <section>
        <name>PaymentCandidateAct Schema</name>
        <sourcecode type="json"><![CDATA[
{
  "$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"
          ]
        }
      }
    }
  }
}
]]></sourcecode>
      </section>

      <section>
        <name>Evidence and Authority</name>
        <sourcecode type="json"><![CDATA[
{
  "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"
  }
}
]]></sourcecode>
        <sourcecode type="json"><![CDATA[
{
  "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
  }
}
]]></sourcecode>
      </section>

      <section>
        <name>Sink Verify Allow and Deny</name>
        <sourcecode type="json"><![CDATA[
{
  "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"
  }
}
]]></sourcecode>
        <sourcecode type="json"><![CDATA[
{
  "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 }
}
]]></sourcecode>
      </section>

      <section>
        <name>Complete Allow Transaction</name>
        <sourcecode type="json"><![CDATA[
{
  "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"
}
]]></sourcecode>
      </section>
    </section>

    <section anchor="bindings">
      <name>Load-Bearing Bindings</name>
      <t>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.</t>
      <t>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.</t>
    </section>

    <section anchor="feasibility">
      <name>Practical Feasibility: Latency and Legacy</name>
      <t>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.</t>

      <section>
        <name>Where the Microseconds Go</name>
        <t>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:</t>
        <ul>
          <li>hot path, local policy and local consume table:
          about 1-10 ms added to an already-authorized
          envelope;</li>
          <li>card or PSP capture on the same host as the
          enforcer: similar;</li>
          <li>cold path with fresh beneficiary risk, new
          country, or amount above envelope: tens to
          hundreds of ms plus any human share;</li>
          <li>external transparency log or chain anchor:
          asynchronous after post, never on the enable
          path.</li>
        </ul>
        <t>The architectural split is the same as in
        <xref target="I-D.das-6g-finality"/>: hot path still
        verifies at the sink; cold path must not be skipped
        by timeout.</t>
      </section>

      <section>
        <name>Hot-Path Envelope</name>
        <t>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.</t>
      </section>

      <section>
        <name>Coexistence with ISO 20022 and Clearing Files</name>
        <t>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.</t>
        <t>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.</t>
      </section>
      <section>
        <name>Coexistence with Card Acquirers</name>
        <t>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.</t>
      </section>
      <section>
        <name>Coexistence with Core Banking</name>
        <t>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.</t>
      </section>
      <section>
        <name>Coexistence with Agent Runtimes</name>
        <t>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
        <xref target="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.</t>
      </section>
      <section>
        <name>Failure Modes that Legacy Staff Will Recognize</name>
        <t>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.</t>
        <t>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.</t>
      </section>
      <section>
        <name>Informative Mapping onto ISO 20022 Credit Transfer</name>
        <t>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.</t>
        <ul>
          <li>candidate_act_id — may populate or hash into
          EndToEndId / InstructionId.</li>
          <li>amount + currency — InstdAmt / EqvtAmt and
          Ccy.</li>
          <li>beneficiary account_ref or account_digest —
          CdtrAcct.Id (digest preferred in authority
          objects that leave the host).</li>
          <li>purpose.invoice_ref — RmtInf or
          Purp.</li>
          <li>rail_id SEPA / SWIFT / FedNow — clearing
          channel already selected by the core.</li>
          <li>authority_id — private supplementary data or
          an API header on the submit that precedes
          clearing release.</li>
        </ul>
        <t>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.</t>
      </section>
      <section>
        <name>Informative Mapping onto Card Capture</name>
        <t>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.</t>
      </section>
      <section>
        <name>What This Profile Does Not Require</name>
        <t>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.</t>
      </section>
    </section>

    <section anchor="crossborder">
      <name>Cross-Border, FX, and Split Legs</name>
      <t>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.</t>
      <t>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.</t>
    </section>

    <section anchor="dual">
      <name>Dual Control and Threshold Authority</name>
      <t>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.</t>
      <t>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.</t>
    </section>

    <section anchor="threat-model">
      <name>Threat Model</name>

      <section anchor="adversary">
        <name>Trust Boundary and Adversary Capabilities</name>
        <t>This profile assumes an adversary who can:</t>
        <ul>
          <li>compromise the Compute / Workload Plane — the
          model, agent, ERP, checkout flow, or API caller
          that constructs a payment instruction — including
          full control of its process, its tool-call
          arguments, and any client-supplied idempotency
          key;</li>
          <li>steal or forge session-level credentials
          reaching that plane, including OAuth tokens, API
          keys, agent seats, and 3-D Secure or SCA session
          artifacts;</li>
          <li>inject or alter upstream content that a human
          or an agent reads before constructing a Candidate
          Act, such as a vendor-change email or a modified
          invoice;</li>
          <li>replay a previously observed instruction,
          signed message, or idempotency key at a second
          acquirer, PSP, or sink;</li>
          <li>observe network traffic between the Compute /
          Workload Plane and the Protected Enforcement
          Domain.</li>
        </ul>
        <t>This profile assumes an adversary cannot, absent a
        separate compromise stated as out of scope below:</t>
        <ul>
          <li>forge a valid Payment Finality Authority without
          the Protected Enforcement Domain's signing or MAC
          key;</li>
          <li>cause the Settlement Sink to accept an authority
          whose digest does not match the live instruction it
          verifies against;</li>
          <li>cause the durable consume-state store to
          transition an already-consumed authority back to
          unused.</li>
        </ul>
        <t>The trust boundary therefore separates the Compute /
        Workload Plane, which this profile treats as
        untrusted and fully attacker-reachable, from the
        Protected Enforcement Domain and the Settlement Sink,
        which this profile treats as protected. Everything
        upstream of the Protected Enforcement Domain may be
        computed by, or under the influence of, an attacker;
        nothing downstream of it becomes externally effective
        without independent verification against a live
        instruction and consumption of a scoped, single-use
        authority.</t>
        <artwork><![CDATA[
+-------------------------+   trust boundary   +-------------------------+
|  Compute / Workload     |         |          |  Protected Enforcement  |
|  Plane (UNTRUSTED)      |         |          |  Domain (TRUSTED)       |
|                         |         |          |                         |
|  agent, model, ERP,     |  ---->  |  ---->   |  validate predicates    |
|  API caller             |         |          |  commit evidence        |
|                         |         |          |  issue scoped authority |
|  produces:              |         |          |                         |
|  Candidate Act          |         |          |                         |
|  (NON_EFFECTIVE)        |         |          |                         |
+-------------------------+         |          +-------------------------+
                                     |                        |
                                     |                        v
                                     |          +-------------------------+
                                     |          |  Settlement Sink        |
                                     |          |  (TRUSTED)              |
                                     |          |                         |
                                     |          |  verify live digest     |
                                     |          |  vs authority           |
                                     |          |  atomic consume + post  |
                                     |          |  or fail-closed deny    |
                                     |          +-------------------------+
]]></artwork>
        <t>An instruction that never crosses the trust
        boundary with matching evidence and unused authority
        cannot become externally effective, regardless of
        what the attacker was able to do on the untrusted
        side.</t>
      </section>

      <section anchor="out-of-scope">
        <name>Out-of-Scope Threats</name>
        <t>This profile does not defend against, and does not
        claim to mitigate:</t>
        <ul>
          <li>compromise of the Protected Enforcement Domain's
          signing or MAC key itself, including key
          exfiltration from a software-only HMAC deployment
          (<xref target="feasibility"/> discusses hardware-
          backed alternatives);</li>
          <li>a colluding or compromised Settlement Sink
          operator who chooses not to verify, or who verifies
          against a falsified live instruction it controls;</li>
          <li>social engineering that occurs before the
          Candidate Act is constructed, such as convincing an
          authorized human to approve a fraudulent beneficiary
          in the first instance; the profile binds what was
          approved, not whether the approval itself was
          well-founded;</li>
          <li>compromise of upstream identity or credential
          issuance that causes the Protected Enforcement
          Domain to accept a forged agent or workload
          identity as genuine;</li>
          <li>multi-sink deployments that share consume state
          incorrectly, such that two independently protected
          sinks each treat a single-use authority as unused;</li>
          <li>denial of service against the Protected
          Enforcement Domain or Settlement Sink;</li>
          <li>compliance failures in sanctions, AML, or
          beneficiary-risk screening systems that feed a
          predicate into the Protected Enforcement Domain,
          beyond binding the screen's epoch or list identifier
          as stated in <xref target="security"/>.</li>
        </ul>
        <t>These are stated explicitly because a profile that
        is silent on its own limits invites reviewers to
        assume broader coverage than the architecture
        provides.</t>
      </section>

      <section anchor="threat-mapping">
        <name>Threat-to-Mitigation Mapping</name>
        <t>Each threat in <xref target="threats"/> is mapped
        below to the architectural control that defeats it
        and the residual risk that remains after the control
        is applied.</t>
        <table>
          <thead>
            <tr><th>Threat</th><th>Defeated by</th><th>Residual risk</th></tr>
          </thead>
          <tbody>
            <tr><td>T1 Stolen PSP/core API token</td><td>Token authenticates a client only; digest-bound scoped authority is still required at the sink</td><td>Attacker who also compromises the PED validation path</td></tr>
            <tr><td>T2 Stolen agent seat with payout tools</td><td>Agent/workload identity bound into digest and authority; sink checks holder binding</td><td>Attacker who compromises the same agent/workload identity itself</td></tr>
            <tr><td>T3 Injected invoice / vendor-change mail</td><td>Beneficiary identity/account load-bearing in digest; PED beneficiary-risk predicate</td><td>Social engineering before Candidate Act construction (out of scope)</td></tr>
            <tr><td>T4 Amount substitution after approval</td><td>Amount normalized and load-bearing; sink reconstructs and compares live digest</td><td>Compromised canonicalization implementation or PED/sink collusion</td></tr>
            <tr><td>T5 Beneficiary substitution / BEC</td><td>Beneficiary ID and account load-bearing; digest mismatch denies</td><td>Compromised PED signing/MAC key (out of scope)</td></tr>
            <tr><td>T6 Rail substitution</td><td>Rail is a load-bearing field; different rail invalidates the authority</td><td>Sink implementation defect that skips rail comparison</td></tr>
            <tr><td>T7 Replay on second acquirer/PSP</td><td>Sink-bound, single-use authority; consume-once state keyed by digest and authority ID</td><td>Multi-sink deployments sharing consume state incorrectly (out of scope)</td></tr>
            <tr><td>T8 Idempotency-key reuse with new body</td><td>Consume table keyed by instruction digest and authority ID, not client string</td><td>Non-conformant implementation that still trusts a client idempotency key alone</td></tr>
            <tr><td>T9 Mandate used off-purpose</td><td>Purpose/mandate ID load-bearing; PED mandate predicate</td><td>Compromised mandate policy data itself</td></tr>
            <tr><td>T10 Payroll file row injection</td><td>One-row-equals-one-Candidate-Act model; per-row digest</td><td>Batch/manifest profile for partial settlement not yet specified (<xref target="industry"/>)</td></tr>
            <tr><td>T11 FX-leg split to move value cheaply</td><td>FX fields load-bearing where modeled; linked-act requirement</td><td>Current profile models one leg only, no atomic cross-sink settlement (<xref target="industry"/>)</td></tr>
            <tr><td>T12 Risk-list epoch rollback</td><td>Risk-list epoch bound into authority; stale epoch invalidates unused authority</td><td>Compromise of the epoch source itself</td></tr>
            <tr><td>T13 Checker UI bypass via API</td><td>Dual-control predicate enforced at the PED, not only at the UI</td><td>PED misconfigured to treat the API path as pre-approved</td></tr>
            <tr><td>T14 Agent A authority used by agent B</td><td>Agent/workload identity bound into digest and authority; sink checks holder match</td><td>Shared credential material between agents (identity-issuance flaw, out of scope)</td></tr>
            <tr><td>T15 Refund used as origination</td><td>Act type (capture vs. refund) load-bearing; distinct Candidate Acts</td><td>Incorrect act-type classification at construction time</td></tr>
          </tbody>
        </table>
      </section>
    </section>

    <section anchor="threats">
      <name>Payment Threat Catalog</name>
      <ul>
        <li>T1 Stolen PSP or core API token</li>
        <li>T2 Stolen enterprise agent seat with payout
        tools</li>
        <li>T3 Injected invoice or vendor-change mail</li>
        <li>T4 Amount substitution after UI approval</li>
        <li>T5 Beneficiary substitution / BEC</li>
        <li>T6 Rail substitution (on-us to instant)</li>
        <li>T7 Replay on a second acquirer or PSP</li>
        <li>T8 Idempotency-key reuse with new body</li>
        <li>T9 Mandate used off-purpose</li>
        <li>T10 Payroll file row injection</li>
        <li>T11 FX-leg split to move value cheaply</li>
        <li>T12 Risk-list epoch rollback</li>
        <li>T13 Checker UI bypass via API</li>
        <li>T14 Agent A authority used by agent B</li>
        <li>T15 Refund used as origination</li>
      </ul>
      <t>The protocol does not require the model to label
      these attacks. It requires post_to_rail() to be
      unreachable when the corresponding bind fails.</t>
    </section>

    <section anchor="example2">
      <name>Additional Worked Denials</name>
      <sourcecode type="json"><![CDATA[
{
  "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
  }
}
]]></sourcecode>
      <sourcecode type="json"><![CDATA[
{
  "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"
  }
}
]]></sourcecode>
      <sourcecode type="json"><![CDATA[
{
  "scenario": "AGENT_AMOUNT_ENVELOPE",
  "hot_ceiling": "500.00",
  "requested": "5000.00",
  "decision": "DENY",
  "code": "EF_AMOUNT_ENVELOPE",
  "remediation": "ESCALATE_TO_CHECKER"
}
]]></sourcecode>
    </section>

    <section anchor="regulatory">
      <name>Regulatory and Operational Notes</name>
      <t>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.</t>
      <t>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.</t>
    </section>

    <section anchor="operation">
      <name>Failure Codes and Denial Behavior</name>
      <t>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.</t>
      <t>A denial MAY name a remediation (re-quote amount,
      escalate checker, refresh risk list) but MUST NOT
      post. Timeout MUST NOT post.</t>
    </section>

    <section anchor="combined">
      <name>Combined Agent Tool Call and Payment Act</name>
      <t>When the instruction is born as a model tool call,
      two objects SHOULD be created from the same
      canonical arguments:</t>
      <sourcecode type="json"><![CDATA[
{
  "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"
}
]]></sourcecode>
      <t>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.</t>
    </section>

    <section anchor="security">
      <name>Security Considerations</name>
      <t>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.</t>
      <t>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.</t>
      <t>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.</t>
      <t>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.</t>
    </section>

    <section anchor="privacy">
      <name>Privacy Considerations</name>
      <t>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.</t>
    </section>

    <section anchor="wg-relevance">
      <name>Relevance to IETF Working Groups</name>
      <t>No current IETF working group owns settlement-sink
      admission control for payment instructions specifically,
      so this document is filed as an individual submission
      pending a dispatch decision. The following existing
      groups and their outputs bound adjacent, but distinct,
      parts of the problem:</t>
      <ul>
        <li><t>SECDISPATCH: the appropriate routing venue for
        a new security problem statement that does not have
        an obvious home. This document is a candidate for a
        SECDISPATCH presentation asking whether payment-side
        execution finality should become chartered work, and
        if so, in which group.</t></li>
        <li><t>GNAP (Grant Negotiation and Authorization
        Protocol): GNAP delegates and negotiates authorization
        grants between a client and a resource; it is the
        closest existing analog to scoped, non-bearer authority
        issuance. It does not specify a digest-bound, single-use
        capability that a settlement sink independently
        reconstructs and verifies against a live instruction,
        which is the gap this document addresses.</t></li>
        <li><t>OAUTH: bearer and sender-constrained tokens
        (RFC 8693, DPoP, mTLS-bound tokens) authenticate a
        client or session. <xref target="existing"/> explains
        why token possession is not equivalent to act-specific,
        amount-bound authority at the point funds actually
        move.</t></li>
        <li><t>SCITT (Supply Chain Integrity, Transparency,
        and Trust): SCITT's transparency-service and signed-
        statement model is structurally close to this
        document's evidence-before-authority ordering
        (<xref target="architecture"/>). A SCITT-style receipt
        could serve as the durable evidence artifact the
        Protected Enforcement Domain commits before issuing
        authority; this document does not currently depend on
        SCITT and notes the alignment for future harmonization.</t></li>
        <li><t>RATS (Remote ATtestation procedureS): where the
        Protected Enforcement Domain or Settlement Sink runs in
        an attested environment, RATS conceptual messages
        (Evidence, Endorsement, Attestation Result) can supply
        one input predicate to the finality decision. This
        document treats attestation as an optional predicate,
        not a substitute for digest-bound, consume-once
        authority; see also <xref target="I-D.das-6g-finality"/>
        for the companion attestation-adjacent profile.</t></li>
        <li><t>STIR/SHAKEN and related identity-assurance work:
        provide caller and channel identity assurance in
        adjacent telecom signaling contexts. They are cited
        here only to distinguish channel-identity assurance
        from act-specific, amount-bound settlement authority,
        which is this document's subject.</t></li>
      </ul>
      <t>This document does not ask any of the above groups to
      adopt it as-is. It is offered as an input problem
      statement so that chairs and area directors can decide
      whether the work belongs in an existing group, a new
      SECDISPATCH-routed effort, or remains an informational
      individual submission.</t>
    </section>

    <section anchor="checklist">
      <name>Implementation Checklist</name>
      <t>A deployment may call itself aligned with this
      profile when all of the following are true on the
      covered rail:</t>
      <ol>
        <li>Every post has an authority_id that was
        consumed atomically with the post.</li>
        <li>Instruction digest covers amount, currency,
        beneficiary, and rail.</li>
        <li>Amount, payee, or rail mismatch is a deny,
        not a log line.</li>
        <li>Replay at a second sink fails.</li>
        <li>Timeout does not post.</li>
        <li>Agent payout tools cannot reach the PSP SDK
        without the sink function.</li>
        <li>Added hot-path latency is measured and sits
        in the 1-10 ms class or is explained.</li>
        <li>Legacy paths that can emit the same clearing
        message are inventoried; those not yet gated
        are listed as known alternate paths with a
        closure date.</li>
      </ol>
    </section>

    <section anchor="iana">
      <name>IANA Considerations</name>
      <t>This document requests no IANA actions.</t>
    </section>

    <section anchor="ipr-note">
      <name>Intellectual Property and Licensing</name>
      <t>Concepts in this profile are associated with the
      DAS Protocols family, including International
      Application PCT/IB2026/055615. IETF disclosure
      should follow BCP 79 <xref target="RFC8179"/>.</t>
      <t>By default, the reference-implementation repository
      accompanying this document (<xref target="resources"/>)
      is licensed under the Creative Commons
      Attribution-NonCommercial 4.0 International (CC BY-NC
      4.0) licence. Attribution is required, and commercial
      use is not permitted without separate written
      permission.</t>
      <t>Associated patent rights, including those relating
      to the DAS Protocols family and PCT/IB2026/055615, are
      not licensed under CC BY-NC 4.0. Commercial
      implementation of the profile described in this
      document may require a separate patent licence.</t>
      <t>Should this specification, or a specification
      derived from it, become IETF-standardized work (see
      <xref target="wg-relevance"/>), the default patent
      licensing basis for that standardized specification is
      Fair, Reasonable, and Non-Discriminatory (FRAND) terms,
      consistent with the disclosure obligations of BCP 79
      <xref target="RFC8179"/>. Any commercial patent licence
      offered for implementations of an IETF-standardized
      version of this specification will be offered on FRAND
      terms. The CC BY-NC 4.0 licence governs copyrightable
      material only, namely the text of this document and the
      reference-implementation source code; it does not itself
      constitute, extend, or limit any patent licence, and it
      does not alter the FRAND commitment applicable to an
      IETF-standardized version of this specification.</t>
    </section>

    <section anchor="resources">
      <name>Reference Implementation</name>
      <t>A runnable, vendor-neutral reference implementation of this
      profile is maintained outside this document. Full architecture
      notes, source layout, cryptographic and canonicalization
      choices, test coverage, benchmark methodology, failure codes,
      and stated limitations are documented at the GitHub
      implementation reference summary URL below
      (<eref target="https://github.com/sangmdas/Payment-Execution-Finality-for-Agentic-API-and-Automated-Payments-Runnable-Reference-Implementation">https://github.com/sangmdas/Payment-Execution-Finality-for-Agentic-API-and-Automated-Payments-Runnable-Reference-Implementation</eref>);
      readers should consult that repository, not this section,
      for implementation-level detail.</t>
      <t>In summary, the reference implementation:</t>
      <ul>
        <li>is written in Python 3.10 or newer and reproduces the
        Payment Candidate Act, Protected Enforcement Domain,
        scoped non-bearer authority, and Settlement-Sink
        verify-and-consume flow defined in this document;</li>
        <li>uses HMAC-SHA-256 behind a replaceable authenticator
        interface, SQLite for durable consume-once state, and a
        restricted canonicalization profile (EF-JCS-SUBSET-1) for
        the load-bearing payment projection and instruction
        digest;</li>
        <li>ships a cross-language test vector
        (<tt>test-vectors/ef-pay-1.json</tt>) so independent
        language ports can verify digest interoperability before
        claiming conformance;</li>
        <li>includes 52 executable tests covering amount
        normalization, instruction substitution, replay and
        concurrency, epoch and expiration freshness, and rail
        failure classification (definite vs. uncertain);</li>
        <li>reports a local reference benchmark (p50/p95/p99 per
        stage) consistent with the target latency class in
        <xref target="feasibility"/>, measured on an in-memory
        store and a simulated, non-effecting rail adapter; and</li>
        <li>is explicitly not a certified payment system: it does
        not integrate a live rail, does not claim HSM-grade key
        isolation, and does not claim regulatory or PCI DSS
        compliance. See the repository's own limitations
        documentation for the full list.</li>
      </ul>
      <t>The repository is licensed under CC BY-NC 4.0 by
      default, per <xref target="ipr-note"/>. Patent rights
      are addressed separately from the copyright licence;
      see <xref target="ipr-note"/> for the FRAND commitment
      applicable if this specification becomes
      IETF-standardized work.</t>
      <t>This document is part of a related family of
      execution-finality Internet-Drafts by the same author,
      applying the same Candidate Act / Protected Enforcement
      Domain / Finality Sink architecture to other domains.
      Readers evaluating this profile may also find the
      following relevant:</t>
      <ul>
        <li><eref target="https://datatracker.ietf.org/doc/draft-das-agentic-tool-binding/">draft-das-agentic-tool-binding</eref> — binds
        execution finality to tool_use/function-calling and
        MCP dispatch;</li>
        <li><eref target="https://datatracker.ietf.org/doc/draft-das-execution-finality-protocol-layer/">draft-das-execution-finality-protocol-layer</eref> —
        umbrella/synthesis draft formalizing the shared
        vocabulary (Candidate Act, Non-Effective State,
        Protected Enforcement Domain, Finality Sink) used
        across the family;</li>
        <li><eref target="https://datatracker.ietf.org/doc/draft-das-eu-ai-act-execution-enforcement/">draft-das-eu-ai-act-execution-enforcement</eref>;</li>
        <li><eref target="https://datatracker.ietf.org/doc/draft-das-global-privacy-execution-enforcement/">draft-das-global-privacy-execution-enforcement</eref>;</li>
        <li><eref target="https://datatracker.ietf.org/doc/draft-das-digital-sovereignty-finality/">draft-das-digital-sovereignty-finality</eref>;</li>
        <li><eref target="https://datatracker.ietf.org/doc/draft-das-ntn-rf-execution-finality/">draft-das-ntn-rf-execution-finality</eref>;</li>
        <li><eref target="https://datatracker.ietf.org/doc/draft-das-payment-execution-finality/">draft-das-payment-execution-finality</eref> — this
        document's own datatracker entry, listed here for
        completeness of the series;</li>
        <li><eref target="https://datatracker.ietf.org/doc/draft-das-agentic-execution-finality/">draft-das-agentic-execution-finality</eref>;</li>
        <li><eref target="https://datatracker.ietf.org/doc/draft-das-ot-actuation-finality/">draft-das-ot-actuation-finality</eref>;</li>
        <li><eref target="https://datatracker.ietf.org/doc/draft-das-protocols-candidate-act-finality/">draft-das-protocols-candidate-act-finality</eref>;</li>
        <li><eref target="https://datatracker.ietf.org/doc/draft-agentic-ai-tool-execution-finality/">draft-agentic-ai-tool-execution-finality</eref>
        (no "das-" prefix); and</li>
        <li><eref target="https://datatracker.ietf.org/doc/draft-das-hardware-enforced-execution-finality/">draft-das-hardware-enforced-execution-finality</eref>.</li>
      </ul>
      <t>Links above point to each draft's datatracker
      overview page, which always resolves to the current
      revision, rather than to a specific dash-numbered
      version.</t>
    </section>

    <section anchor="conclusion">
      <name>Conclusion</name>
      <t>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.</t>
    </section>
  </middle>
  <back>
    <references>
      <name>Normative References</name>
      <reference anchor="RFC2119" target="https://www.rfc-editor.org/info/rfc2119">
        <front>
          <title>Key words for use in RFCs to Indicate Requirement Levels</title>
          <author initials="S." surname="Bradner" fullname="S. Bradner"/>
          <date year="1997" month="March"/>
        </front>
        <seriesInfo name="BCP" value="14"/>
        <seriesInfo name="RFC" value="2119"/>
      </reference>
      <reference anchor="RFC8174" target="https://www.rfc-editor.org/info/rfc8174">
        <front>
          <title>Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words</title>
          <author initials="B." surname="Leiba" fullname="B. Leiba"/>
          <date year="2017" month="May"/>
        </front>
        <seriesInfo name="BCP" value="14"/>
        <seriesInfo name="RFC" value="8174"/>
      </reference>
      <reference anchor="RFC8179" target="https://www.rfc-editor.org/info/rfc8179">
        <front>
          <title>Intellectual Property Rights in IETF Technology</title>
          <author initials="S." surname="Bradner" fullname="S. Bradner"/>
          <author initials="J." surname="Contreras" fullname="J. Contreras"/>
          <date year="2017" month="May"/>
        </front>
        <seriesInfo name="BCP" value="79"/>
        <seriesInfo name="RFC" value="8179"/>
      </reference>
    </references>
    <references>
      <name>Informative References</name>
      <reference anchor="I-D.das-6g-finality">
        <front>
          <title>Execution-Finality for AI-Native 5G/6G and O-RAN</title>
          <author fullname="Sangam Das" initials="S." surname="Das"/>
          <date year="2026" month="August"/>
        </front>
        <seriesInfo name="Internet-Draft" value="draft-das-ai-native-6g-execution-finality-01"/>
      </reference>
      <reference anchor="I-D.das-agentic">
        <front>
          <title>Tool Selection Is Not Execution: Finality for Agentic Tool Dispatch</title>
          <author fullname="Sangam Das" initials="S." surname="Das"/>
          <date year="2026" month="August"/>
        </front>
        <seriesInfo name="Internet-Draft" value="draft-das-agentic-execution-finality-01"/>
      </reference>
    </references>
  </back>
</rfc>
