<?xml version='1.0' encoding='utf-8'?>
<?xml-stylesheet type="text/xsl" href="rfc2629.xslt" ?>
<rfc category="info" docName="draft-das-payment-execution-finality-00"
     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-00"/>
    <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="August" day="27"/>
    <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.</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="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, optionally lock nonce, do not
        post.</li>
        <li>On pass: commit Protected Validation Evidence.</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 verifies live fields against
        authority immediately before post.</li>
        <li>Atomic consume + post, or deny with no post.</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="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="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 Note</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>
    </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>
