<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE rfc [
  <!ENTITY nbsp    "&#160;">
  <!ENTITY zwsp   "&#8203;">
  <!ENTITY nbhy   "&#8209;">
  <!ENTITY wj     "&#8288;">
]>
<?rfc toc="yes"?>
<?rfc tocdepth="4"?>
<?rfc symrefs="yes"?>
<?rfc sortrefs="yes"?>
<?rfc compact="yes"?>
<?rfc subcompact="no"?>
<?rfc strict="yes"?>

<rfc xmlns:xi="http://www.w3.org/2001/XInclude"
     category="exp"
     docName="draft-venkateswaran-jamwal-twophp-00"
     ipr="trust200902"
     submissionType="independent"
     xml:lang="en"
     version="3">

  <!-- ============================================================ -->
  <!-- FRONT MATTER                                                  -->
  <!-- ============================================================ -->
  <front>
    <title abbrev="2PHP">
      2-Phase HTTP Protocol (2PHP)
    </title>

    <seriesInfo name="Internet-Draft"
                value="draft-venkateswaran-jamwal-twophp-00"/>

    <!-- ascii attribute required per RFC 7997 Section 5 when
         non-Latin scripts may appear; kept here for completeness -->
    <author asciiFullname="Barathan Venkateswaran" 
            asciiInitials="B."
            asciiSurname="Venkateswaran"
            fullname="Barathan Venkateswaran"
            initials="B."
            surname="Venkateswaran"
            >
      <organization>Salesforce</organization>
      <address>
        <email>bvenkateswaran@salesforce.com</email>
      </address>
    </author>

    <author asciiFullname="Aditya Jamwal" 
            asciiInitials="A."
            asciiSurname="Jamwal"
            fullname="Aditya Jamwal"
            initials="A."
            surname="Jamwal"
          >
      <organization>Salesforce</organization>
      <address>
        <email>ajamwal@salesforce.com</email>
      </address>
    </author>

    <date year="2026" month="Jun" day="18"/>

    <area>Applications and Real-Time</area>
    <workgroup>HTTP</workgroup>

    <!-- Keywords: ASCII-only per RFC 7997 Section 3.8 -->
    <keyword>HTTP</keyword>
    <keyword>protocol extension</keyword>
    <keyword>two-phase</keyword>
    <keyword>distributed systems</keyword>
    <keyword>intent registration</keyword>
    <keyword>ledger</keyword>
    <keyword>idempotency</keyword>
    <keyword>microservices</keyword>
    <keyword>correlation</keyword>
    <keyword>request reliability</keyword>

    <abstract>
      <t>
        This document specifies the 2-Phase HTTP Protocol (2PHP), a
        backward-compatible, opt-in extension to HTTP mutation semantics
        that introduces a two-phase handshake at service request
        boundaries. 2PHP ensures that a mutation request is durably
        registered on both the client and the server before any
        processing commences, addressing a structural gap in existing
        HTTP REST semantics where no standard mechanism exists to confirm
        bilateral intent registration prior to execution.
      </t>
      <t>
        2PHP operates at the HTTP protocol layer, requires no new
        transport mechanisms, and is composable across independently
        operating service boundaries. This document further defines a
        standardized Intent Ledger model for durable, queryable,
        cross-service correlation of request intent, distinct from
        execution telemetry provided by distributed tracing systems.
      </t>
      <t>
        This document defines the protocol headers, phase state machine,
        idempotency and replay behavior, the three-tier Intent Ledger
        architecture, cross-service correlation model, and IANA
        registration of the associated HTTP header fields.
      </t>
    </abstract>

  </front>

  <!-- ============================================================ -->
  <!-- MIDDLE MATTER                                                 -->
  <!-- ============================================================ -->
  <middle>

    <!-- Section 1 -->
    <section anchor="introduction" numbered="true" toc="default">
      <name>Introduction</name>
      <t>
        Microservices architectures built on HTTP REST APIs are widely
        deployed for distributed systems communication. Mutation
        requests&#8212;those using HTTP methods POST, PUT, PATCH, and
        DELETE&#8212;are treated as single atomic operations from the
        client&#8217;s perspective. In practice, however, no standard
        protocol mechanism exists to confirm that both the client and
        server have durably registered the intent of a request before
        processing begins.
      </t>
      <t>
        Existing approaches&#8212;including the Saga pattern
        <xref target="SAGA"/>, Transactional Outbox
        <xref target="OUTBOX"/>, and application-layer idempotency
        keys&#8212;address failure recovery after the fact or are
        implemented ad hoc without a standard contract. Distributed
        tracing systems such as OpenTelemetry <xref target="OTEL"/>
        provide execution telemetry but cannot answer whether both
        parties registered intent before processing commenced.
      </t>
      <t>
        This document specifies the 2-Phase HTTP Protocol (2PHP), an
        opt-in, backward-compatible extension to HTTP that introduces a
        lightweight two-phase handshake at each service boundary. 2PHP
        ensures bilateral durable registration of request intent before
        any business logic is executed. It further defines a
        standardized Intent Ledger model that enables cross-service
        correlation of request intent across distributed call trees.
      </t>
      <t>2PHP is designed to be:</t>
      <ul spacing="normal">
        <li>
          <strong>Backward compatible</strong>&#8212;servers and clients
          that do not implement 2PHP are unaffected.
        </li>
        <li>
          <strong>Lightweight</strong>&#8212;two HTTP round trips; no
          new transport layer.
        </li>
        <li>
          <strong>Composable</strong>&#8212;each service boundary
          operates independently; no distributed coordination is
          required.
        </li>
        <li>
          <strong>Standardized</strong>&#8212;a consistent header
          contract enabling uniform tooling, observability, and
          governance.
        </li>
      </ul>
    </section>

    <!-- Section 2 -->
    <section anchor="conventions" numbered="true" toc="default">
      <name>Conventions and Definitions</name>
      <t>
        The key words <bcp14>MUST</bcp14>, <bcp14>MUST NOT</bcp14>,
        <bcp14>REQUIRED</bcp14>, <bcp14>SHALL</bcp14>,
        <bcp14>SHALL NOT</bcp14>, <bcp14>SHOULD</bcp14>,
        <bcp14>SHOULD NOT</bcp14>, <bcp14>RECOMMENDED</bcp14>,
        <bcp14>NOT RECOMMENDED</bcp14>, <bcp14>MAY</bcp14>, and
        <bcp14>OPTIONAL</bcp14> in this document are to be interpreted
        as described in BCP&#160;14 <xref target="RFC2119"/>
        <xref target="RFC8174"/> when, and only when, they appear in
        all capitals, as shown here.
      </t>
      <dl newline="false" spacing="normal">
        <dt>2PHP:</dt>
        <dd>The 2-Phase HTTP Protocol, as defined in this document.</dd>
        <dt>Phase 1:</dt>
        <dd>
          The Intent Registration phase, in which the client sends a
          mutation request and the server durably persists the request
          payload and an initial ledger record before responding.
        </dd>
        <dt>Phase 2:</dt>
        <dd>
          The Client Confirmation phase, in which the client confirms
          receipt of the Phase&#160;1 acknowledgement, triggering
          server-side business logic execution.
        </dd>
        <dt>PONR (Point of No Return):</dt>
        <dd>
          The moment at which the server begins executing business
          logic after receiving Phase&#160;2 confirmation. Processing
          cannot be rolled back after this point.
        </dd>
        <dt>TTL (Time to Live):</dt>
        <dd>
          The server-defined duration, in milliseconds, within which
          the client <bcp14>MUST</bcp14> send Phase&#160;2
          confirmation.
        </dd>
        <dt>Delta (&#948;):</dt>
        <dd>
          A server-internal grace window applied after TTL expiry to
          handle race conditions at the PONR boundary. Not exposed
          to the client.
        </dd>
        <dt>Intent Ledger:</dt>
        <dd>
          A durable, queryable log of 2PHP transactions, distinct
          from execution telemetry.
        </dd>
        <dt>Local Ledger:</dt>
        <dd>
          A per-service, in-process ledger authoritative for that
          service&#8217;s phase states.
        </dd>
        <dt>Central Ledger:</dt>
        <dd>
          A read-optimized aggregate view of all local ledgers,
          populated via background batch sync.
        </dd>
        <dt>Client Correlation ID:</dt>
        <dd>
          A client-assigned identifier for a specific request,
          included in all protocol headers and ledger entries
          for that interaction.
        </dd>
        <dt>Server Correlation ID:</dt>
        <dd>
          A server-assigned identifier for a specific intent
          registration, returned in the Phase&#160;1 response.
        </dd>
        <dt>Parent Reference ID:</dt>
        <dd>
          The correlation ID that the immediate upstream caller
          provided when initiating the call to a downstream
          service. Used to reconstruct distributed call trees.
        </dd>
      </dl>
    </section>

    <!-- Section 3 -->
    <section anchor="problem" numbered="true" toc="default">
      <name>Problem Statement</name>

      <section anchor="problem-gap" numbered="true" toc="default">
        <name>The Request Boundary Gap</name>
        <t>
          Current HTTP REST semantics treat a mutation request as a
          single atomic operation from the client&#8217;s perspective.
          In reality, a server may:
        </t>
        <ul spacing="normal">
          <li>
            Receive and process the request without the client ever
            confirming receipt of the response.
          </li>
          <li>
            Fail mid-processing with no durable record of the
            original intent.
          </li>
          <li>
            Return a success response that is lost in transit.
          </li>
        </ul>
        <t>
          No standard HTTP mechanism exists to confirm that both the
          client and the server have registered the intent of a
          request before processing commences.
        </t>
      </section>

      <section anchor="problem-telemetry" numbered="true" toc="default">
        <name>Limitations of Existing Telemetry</name>
        <t>
          Modern distributed tracing systems provide visibility into
          what executed across services, how long operations took, and
          where failures occurred. However, they cannot answer:
        </t>
        <ul spacing="normal">
          <li>
            Did both client and server register the intent before
            processing?
          </li>
          <li>Was a request abandoned or confirmed?</li>
          <li>
            At what phase did a distributed transaction stall?
          </li>
        </ul>
      </section>

      <section anchor="problem-patterns" numbered="true" toc="default">
        <name>Limitations of Existing Patterns</name>
        <table align="center">
          <thead>
            <tr>
              <th>Pattern</th>
              <th>Scope</th>
              <th>Client Involved?</th>
              <th>Protocol Level?</th>
              <th>Intent Ledger?</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>Two-Phase Commit (2PC)</td>
              <td>Database</td>
              <td>No</td><td>No</td><td>No</td>
            </tr>
            <tr>
              <td>Saga Pattern</td>
              <td>Application</td>
              <td>No</td><td>No</td><td>No</td>
            </tr>
            <tr>
              <td>Transactional Outbox</td>
              <td>Server-side</td>
              <td>No</td><td>No</td><td>No</td>
            </tr>
            <tr>
              <td>Idempotency Keys</td>
              <td>Application</td>
              <td>Partial</td><td>No</td><td>No</td>
            </tr>
            <tr>
              <td>HTTP 100 Continue</td>
              <td>Protocol</td>
              <td>Yes</td><td>Yes</td><td>No</td>
            </tr>
            <tr>
              <td>2PHP (this document)</td>
              <td>Protocol</td>
              <td>Yes</td><td>Yes</td><td>Yes</td>
            </tr>
          </tbody>
        </table>
      </section>
    </section>

    <!-- Section 4 -->
    <section anchor="overview" numbered="true" toc="default">
      <name>Protocol Overview</name>
      <t>
        2PHP extends HTTP mutation semantics with an opt-in two-phase
        handshake. Each service boundary owns its own independent
        handshake. Cross-service traceability is the responsibility of
        the centralized Intent Ledger, not the protocol itself.
      </t>
      <figure>
        <name>2PHP Protocol Flow (Synchronous Mode)</name>
        <artwork align="left" type="ascii-art"><![CDATA[
Client                                Server
  |                                     |
  |-- Phase 1: POST + payload --------> |
  |                                     | Persist payload +
  |                                     | ledger record atomically
  | <-- 200 OK + Server-Corr-ID ------- |
  |                                     |
  | (Client writes local ledger entry)  |
  |                                     |
  |-- Phase 2: POST confirm ----------> |
  |                                     | PONR crossed;
  |                                     | business logic executes
  | <-- 200 OK + Resource-ID ---------- |
  |                                     |
        ]]></artwork>
      </figure>
      <t>
        The server <bcp14>MUST</bcp14> atomically persist the intent
        payload and the initial ledger record before returning the
        Phase&#160;1 response. The resource is created only after
        Phase&#160;2 confirmation is received and business logic has
        successfully executed.
      </t>
    </section>

    <!-- Section 5 -->
    <section anchor="spec" numbered="true" toc="default">
      <name>Protocol Specification</name>

      <section anchor="spec-optin" numbered="true" toc="default">
        <name>Opt-In Header</name>
        <t>
          Clients signal 2PHP intent by including the following header
          on any mutation request:
        </t>
        <artwork align="left" type="http-message"><![CDATA[
DTT-2PHP-Enabled: true
        ]]></artwork>
        <t>
          Servers that do not recognize this header
          <bcp14>MUST</bcp14> ignore it and process the request
          normally, preserving backward compatibility.
        </t>
      </section>

      <section anchor="spec-phase1" numbered="true" toc="default">
        <name>Phase 1 &#8212; Intent Registration</name>

        <section anchor="spec-phase1-request"
                 numbered="true" toc="default">
          <name>Client Request</name>
          <artwork align="left" type="http-message"><![CDATA[
POST /orders HTTP/1.1
DTT-2PHP-Enabled: true
DTT-2PHP-Requested-TTL: <client-requested-ttl-ms>
DTT-2PHP-Client-Correlation-ID: <client-assigned-uuid>
Content-Type: application/json

{ ...payload... }
          ]]></artwork>
          <t>
            The client <bcp14>MUST</bcp14> include
            <tt>DTT-2PHP-Client-Correlation-ID</tt> with a unique
            identifier for this request. This identifier
            <bcp14>MUST</bcp14> be used in all subsequent protocol
            exchanges and ledger entries for this interaction.
          </t>
          <t>
            The client <bcp14>MAY</bcp14> include
            <tt>DTT-2PHP-Requested-TTL</tt> (in milliseconds) to
            request that the server override its default TTL setting.
          </t>
        </section>

        <section anchor="spec-phase1-response"
                 numbered="true" toc="default">
          <name>Server Response</name>
          <t>
            The server <bcp14>MUST</bcp14> atomically persist the
            intent payload and an initial ledger record with phase
            state <tt>WAITING_CONFIRM</tt> before returning the
            response.
          </t>
          <artwork align="left" type="http-message"><![CDATA[
HTTP/1.1 200 OK
DTT-2PHP-Server-Correlation-ID: <server-assigned-uuid>
DTT-2PHP-Phase-State: WAITING_CONFIRM
DTT-2PHP-TTL: <milliseconds>
DTT-2PHP-PONR-Deadline: <ISO-8601-absolute-timestamp>
          ]]></artwork>
          <t>Response header semantics:</t>
          <dl newline="false" spacing="normal">
            <dt>DTT-2PHP-Server-Correlation-ID:</dt>
            <dd>
              Server-assigned identifier for this intent
              registration. The client <bcp14>MUST</bcp14> echo
              this value in the Phase&#160;2 confirmation.
            </dd>
            <dt>DTT-2PHP-TTL:</dt>
            <dd>
              Milliseconds the server will wait for Phase&#160;2
              confirmation. Server-defined. The client
              <bcp14>MUST</bcp14> send Phase&#160;2 before this
              duration elapses. If the client includes
              <tt>DTT-2PHP-Requested-TTL</tt>, the server
              <bcp14>MAY</bcp14> accommodate by increasing the TTL.
            </dd>
            <dt>DTT-2PHP-PONR-Deadline:</dt>
            <dd>
              Server-computed absolute deadline in ISO&#160;8601
              format. The client <bcp14>MUST</bcp14> send
              Phase&#160;2 before this timestamp.
            </dd>
            <dt>DTT-2PHP-Phase-State:</dt>
            <dd>
              Current phase state. Value is
              <tt>WAITING_CONFIRM</tt> on Phase&#160;1 response.
            </dd>
          </dl>
          <t>
            No <tt>DTT-2PHP-Resource-ID</tt> is returned in
            Phase&#160;1. The resource does not yet exist; it is
            created only after Phase&#160;2 confirmation is received
            and business logic has successfully executed.
          </t>
        </section>

        <section anchor="spec-phase1-ledger"
                 numbered="true" toc="default">
          <name>Client Ledger Write</name>
          <t>
            After receiving the Phase&#160;1 response, the client
            <bcp14>MUST</bcp14> write a ledger entry to its local
            ledger. The client writes after the Phase&#160;1 response
            so that both <tt>client_correlation_id</tt> and
            <tt>server_correlation_id</tt> are available at write
            time. The client&#8217;s <tt>parent_reference_id</tt>
            <bcp14>MUST</bcp14> be set to its own
            <tt>client_correlation_id</tt> as the root anchor.
          </t>
        </section>
      </section>

      <section anchor="spec-phase2" numbered="true" toc="default">
        <name>Phase 2 &#8212; Client Confirmation</name>

        <section anchor="spec-phase2-request"
                 numbered="true" toc="default">
          <name>Client Request</name>
          <t>
            The client confirms receipt of the Phase&#160;1
            acknowledgement:
          </t>
          <artwork align="left" type="http-message"><![CDATA[
POST /orders/confirm HTTP/1.1
DTT-2PHP-Enabled: true
DTT-2PHP-Client-Correlation-ID: <same-value-as-Phase-1>
DTT-2PHP-Server-Correlation-ID: <value-from-Phase-1-response>
          ]]></artwork>
          <t>
            The client <bcp14>MUST</bcp14> send Phase&#160;2 before
            the <tt>DTT-2PHP-PONR-Deadline</tt> timestamp received
            in the Phase&#160;1 response.
          </t>
        </section>

        <section anchor="spec-phase2-sync"
                 numbered="true" toc="default">
          <name>Server Response &#8212; Synchronous Mode</name>
          <t>
            The server crosses the PONR, executes business logic
            inline, and responds once processing is complete:
          </t>
          <artwork align="left" type="http-message"><![CDATA[
HTTP/1.1 200 OK
DTT-2PHP-Phase-State: COMMITTED
DTT-2PHP-Resource-ID: <resource-identifier>
          ]]></artwork>
          <t>
            The server <bcp14>MUST</bcp14> return <tt>200 OK</tt>
            only after business logic has completed and the resource
            is fully created. The <tt>DTT-2PHP-Resource-ID</tt> is
            authoritative&#8212;the resource exists at the point
            this response is returned.
          </t>
          <t>
            If business logic fails after PONR is crossed, the
            server <bcp14>MUST</bcp14> return
            <tt>500 Internal Server Error</tt>. If the request is
            invalid, the server <bcp14>MUST</bcp14> return the
            appropriate <tt>4xx</tt> response. In both cases the
            client receives a definitive outcome in the Phase&#160;2
            response&#8212;no polling is required.
          </t>
        </section>

        <section anchor="spec-phase2-async"
                 numbered="true" toc="default">
          <name>Server Response &#8212; Asynchronous Mode</name>
          <t>
            In asynchronous mode, the server crosses the PONR and
            returns <tt>202 Accepted</tt> immediately upon receiving
            Phase&#160;2 confirmation. Business logic executes
            out-of-band on the original request channel.
          </t>
          <artwork align="left" type="http-message"><![CDATA[
HTTP/1.1 202 Accepted
DTT-2PHP-Phase-State: PROCESSING
DTT-2PHP-Resource-ID: <provisional-resource-identifier>
          ]]></artwork>
          <t>
            The <tt>DTT-2PHP-Resource-ID</tt> returned in a
            <tt>202 Accepted</tt> response is provisional&#8212;the
            resource is not yet fully created at the point this
            response is returned. The client
            <bcp14>MUST</bcp14> poll the resource endpoint until
            <tt>DTT-2PHP-Phase-State</tt> transitions to
            <tt>COMMITTED</tt> or <tt>FAILED</tt>.
          </t>
          <t>
            The phase state machine is identical to synchronous mode.
            Only the timing of the <tt>COMMITTED</tt> transition
            differs&#8212;in asynchronous mode it occurs out-of-band
            rather than inline on the Phase&#160;2 response channel.
            <tt>202 Accepted</tt> semantics are as defined in
            <xref target="RFC9110"/>.
          </t>
        </section>
      </section>

      <section anchor="spec-ttl" numbered="true" toc="default">
        <name>TTL Expiry</name>
        <t>
          Any Phase&#160;2 confirmation received after TTL expiry
          <bcp14>MUST</bcp14> be rejected with:
        </t>
        <artwork align="left" type="http-message"><![CDATA[
HTTP/1.1 408 Request Timeout
DTT-2PHP-Phase-State: TTL_EXPIRED
DTT-2PHP-Message: Request TTL exceeded. Re-submit as new request.
        ]]></artwork>
        <t>
          The server <bcp14>MAY</bcp14> apply an internal grace
          window (&#948;) after TTL expiry to handle race conditions
          at the PONR boundary. This grace window is server-internal
          and <bcp14>MUST NOT</bcp14> be exposed in protocol headers
          or communicated to the client.
        </t>
      </section>

      <!-- Section 5.4: Auto-Confirm Mode -->
      <section anchor="spec-autoconfirm" numbered="true" toc="default">
        <name>Auto-Confirm Mode</name>

        <section anchor="spec-autoconfirm-overview"
                 numbered="true" toc="default">
          <name>Overview</name>
          <t>
            Auto-Confirm mode is an opt-in extension to 2PHP that
            eliminates the explicit Phase&#160;2 client confirmation
            round trip. When Auto-Confirm is enabled, the server
            writes the ledger record and crosses the PONR immediately
            after Phase&#160;1, then executes business logic inline
            on the original request channel. The final result
            (200 OK + Resource-ID) is returned synchronously on that
            same channel&#8212;behaviorally identical to a standard
            HTTP request from the client&#8217;s perspective.
          </t>
          <t>
            Concurrently, immediately after the ledger write and
            before processing begins, the server dispatches an
            asynchronous callback to a client-provided endpoint
            carrying the <tt>server_correlation_id</tt> and ledger
            state. This out-of-band delivery gives the client a
            durable record of the registered intent independent of
            the original response channel.
          </t>
          <t>Auto-Confirm mode is valuable for:</t>
          <ul spacing="normal">
            <li>
              <strong>Brownfield adoption</strong>&#8212;clients with
              no 2PHP implementation receive full ledger
              observability with zero behavior change.
            </li>
            <li>
              <strong>Low-latency critical paths</strong>&#8212;the
              Phase&#160;2 round trip cost is eliminated.
            </li>
            <li>
              <strong>Server-side opt-in</strong>&#8212;platform
              teams can enable ledger tracking without requiring
              client participation.
            </li>
          </ul>
        </section>

        <section anchor="spec-autoconfirm-headers"
                 numbered="true" toc="default">
          <name>Activation Headers</name>
          <artwork align="left" type="http-message"><![CDATA[
DTT-2PHP-Auto-Confirm: true
DTT-2PHP-Callback: <client-callback-endpoint-url>
          ]]></artwork>
          <dl newline="false" spacing="normal">
            <dt>DTT-2PHP-Auto-Confirm:</dt>
            <dd>
              When <tt>true</tt>, the server does not wait for a
              Phase&#160;2 confirmation before crossing the PONR
              and executing business logic.
            </dd>
            <dt>DTT-2PHP-Callback:</dt>
            <dd>
              A client-provided HTTPS endpoint to receive the
              asynchronous intent acknowledgment payload. This
              header is <bcp14>OPTIONAL</bcp14> when
              <tt>DTT-2PHP-Auto-Confirm: true</tt> is set. If
              omitted, the server proceeds in Transparent Mode
              with no client-side ledger participation.
            </dd>
          </dl>
        </section>

        <section anchor="spec-autoconfirm-flow"
                 numbered="true" toc="default">
          <name>Protocol Flow</name>
          <figure>
            <name>Auto-Confirm Mode with Callback</name>
            <artwork align="left" type="ascii-art"><![CDATA[
Client                            Server            Client Callback
  |                                 |                     |
  |-- POST /orders ------------->   |                     |
  |   DTT-2PHP-Enabled: true        |                     |
  |   DTT-2PHP-Auto-Confirm: true   |                     |
  |   DTT-2PHP-Callback: <url>      |                     |
  |   DTT-2PHP-Client-Corr-ID: C1  |                     |
  |                                 |                     |
  |             Ledger write (WAITING_CONFIRM             |
  |             -> PROCESSING, atomic)                    |
  |                                 |                     |
  |                                 |-- POST callback --> |
  |                                 |   (immediately,     |
  |                                 |   off-thread,       |
  |                                 |   BEFORE processing)|
  |                                 |                     |
  |             Server processes inline...                |
  |                                 |                     |
  | <-- 200 OK + Resource-ID ----   |                     |
  |     DTT-2PHP-Phase-State:       |                     |
  |       COMMITTED                 |                     |
  |     DTT-2PHP-Server-Corr-ID: S1 |                     |
  |                                 |                     |
            ]]></artwork>
          </figure>
          <t>
            The key invariant: the callback
            <bcp14>MUST</bcp14> be dispatched immediately after
            the ledger write and <bcp14>MUST</bcp14> fire before
            processing begins. This maximizes the client detection
            window. If the original response is never received but
            the callback was, the client knows the PONR was crossed
            and <bcp14>MAY</bcp14> query the ledger by
            <tt>server_correlation_id</tt> for final state.
          </t>
        </section>

        <section anchor="spec-autoconfirm-callback-payload"
                 numbered="true" toc="default">
          <name>Callback Payload</name>
          <table align="center">
            <thead>
              <tr><th>Field</th><th>Description</th></tr>
            </thead>
            <tbody>
              <tr>
                <td><tt>server_correlation_id</tt></td>
                <td>Server-assigned intent identifier.</td>
              </tr>
              <tr>
                <td><tt>client_correlation_id</tt></td>
                <td>Echo of the client-provided identifier.</td>
              </tr>
              <tr>
                <td><tt>phase_state</tt></td>
                <td>
                  <tt>PROCESSING</tt> at time of callback dispatch.
                </td>
              </tr>
              <tr>
                <td><tt>ponr_crossed</tt></td>
                <td>
                  Always <tt>true</tt> in Auto-Confirm mode.
                </td>
              </tr>
              <tr>
                <td><tt>callback_timestamp</tt></td>
                <td>
                  ISO&#160;8601 timestamp of callback dispatch.
                  Fired before processing begins.
                </td>
              </tr>
            </tbody>
          </table>
        </section>

        <section anchor="spec-autoconfirm-failure"
                 numbered="true" toc="default">
          <name>Failure Detection via Callback Gap</name>
          <t>
            The callback creates a first-class detectable failure
            taxonomy:
          </t>
          <table align="center">
            <thead>
              <tr>
                <th>Callback Received</th>
                <th>Original Response Received</th>
                <th>Interpretation</th>
              </tr>
            </thead>
            <tbody>
              <tr>
                <td>Yes</td><td>Yes</td>
                <td>Normal success. Reconcile by correlation IDs.</td>
              </tr>
              <tr>
                <td>Yes</td><td>No</td>
                <td>
                  PONR crossed; response lost in transit. Query
                  ledger by <tt>server_correlation_id</tt> for
                  final state.
                </td>
              </tr>
              <tr>
                <td>No</td><td>Yes</td>
                <td>
                  Callback delivery failed. Ledger entry exists
                  server-side; client has no local record.
                </td>
              </tr>
              <tr>
                <td>No</td><td>No</td>
                <td>
                  Network failure before PONR. Safe to retry as
                  a new Phase&#160;1 request.
                </td>
              </tr>
            </tbody>
          </table>
        </section>

        <section anchor="spec-autoconfirm-transparent"
                 numbered="true" toc="default">
          <name>Transparent Mode (Auto-Confirm without Callback)</name>
          <t>
            When <tt>DTT-2PHP-Auto-Confirm: true</tt> is set without
            <tt>DTT-2PHP-Callback</tt>, the server operates in
            Transparent Mode. The server records a ledger entry
            using a server-generated UUID as the
            <tt>client_correlation_id</tt> proxy. The client
            receives a standard synchronous response with no 2PHP
            headers required. The server <bcp14>MAY</bcp14> return
            <tt>DTT-2PHP-Server-Correlation-ID</tt> in the response
            for optional client-side ledger lookup.
          </t>
          <t>
            Transparent Mode provides full server-side ledger
            observability with zero client behavior change. It is
            the recommended adoption path for brownfield services.
          </t>
        </section>

        <section anchor="spec-autoconfirm-statemachine"
                 numbered="true" toc="default">
          <name>State Machine &#8212; Auto-Confirm Mode</name>
          <figure>
            <name>Auto-Confirm Phase State Machine</name>
            <artwork align="left" type="ascii-art"><![CDATA[
Client POST (Phase 1, DTT-2PHP-Auto-Confirm: true)
        |
        v
[WAITING_CONFIRM] <-- ledger write (transient, atomic)
        |
        v  (immediate -- PONR crossed, no client confirmation)
[PROCESSING] --> (off-thread) Callback dispatched to client
        |         BEFORE processing begins
        |
        +-- Success --> [COMMITTED] <-- 200 OK + Resource-ID
        |                               on original channel
        |
        +-- Failure --> [FAILED]    <-- 500 / 4xx
                                        on original channel
            ]]></artwork>
          </figure>
          <t>
            In Auto-Confirm mode, <tt>WAITING_CONFIRM</tt> is a
            transient internal state written and immediately
            superseded by <tt>PROCESSING</tt> within the same
            atomic ledger operation. It is never visible to the
            client on the original channel.
          </t>
        </section>

        <section anchor="spec-autoconfirm-comparison"
                 numbered="true" toc="default">
          <name>Mode Comparison</name>
          <table align="center">
            <thead>
              <tr>
                <th>Mode</th>
                <th>Phase 2 Required</th>
                <th>Callback</th>
                <th>Latency</th>
                <th>Client 2PHP Support</th>
                <th>Ledger Participation</th>
              </tr>
            </thead>
            <tbody>
              <tr>
                <td>Standard 2PHP</td>
                <td>Yes</td><td>No</td>
                <td>2 round trips</td>
                <td>Yes</td><td>Both sides</td>
              </tr>
              <tr>
                <td>Auto-Confirm (with callback)</td>
                <td>No</td><td>Yes (async)</td>
                <td>1 round trip</td>
                <td>Callback endpoint only</td>
                <td>Both sides</td>
              </tr>
              <tr>
                <td>Transparent Mode (no callback)</td>
                <td>No</td><td>No</td>
                <td>1 round trip</td>
                <td>None</td>
                <td>Server-side only</td>
              </tr>
            </tbody>
          </table>
        </section>

      </section>
    </section>

    <!-- Section 6 -->
    <section anchor="statemachine" numbered="true" toc="default">
      <name>State Machine</name>
      <figure>
        <name>2PHP Phase State Machine</name>
        <artwork align="left" type="ascii-art"><![CDATA[
Client POST (Phase 1)
        |
        v
[WAITING_CONFIRM]  <-- persisted in durable storage
        |
        +-- Client confirms within TTL
        |           |
        |           v
        |       [PROCESSING]  <-- PONR crossed; business logic executing
        |           |
        |           +-- Success --> [COMMITTED]  <-- 200 OK + Resource-ID
        |           |
        |           +-- Failure --> [FAILED]     <-- 500 / 4xx
        |
        +-- TTL expires without confirmation
                    |
                    v
            [TTL_EXPIRED] -- [ PONR, PONR+delta-x ) --> [ABANDONED]
        ]]></artwork>
      </figure>
    </section>

    <!-- Section 7 -->
    <section anchor="phasestates" numbered="true" toc="default">
      <name>Phase States</name>
      <table align="center">
        <thead>
          <tr>
            <th>State</th>
            <th>Description</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td><tt>WAITING_CONFIRM</tt></td>
            <td>
              Phase&#160;1 received and persisted. Awaiting client
              Phase&#160;2 confirmation.
            </td>
          </tr>
          <tr>
            <td><tt>PROCESSING</tt></td>
            <td>
              Phase&#160;2 confirmed. PONR crossed. Business logic
              executing (synchronous mode).
            </td>
          </tr>
          <tr>
            <td><tt>COMMITTED</tt></td>
            <td>
              Processing complete. Resource created. Terminal
              success state.
            </td>
          </tr>
          <tr>
            <td><tt>FAILED</tt></td>
            <td>
              Processing failed after PONR. Server returned 500
              or 4xx. Terminal failure state.
            </td>
          </tr>
          <tr>
            <td><tt>TTL_EXPIRED</tt></td>
            <td>
              Client did not confirm within TTL. Pending cleanup.
            </td>
          </tr>
          <tr>
            <td><tt>ABANDONED</tt></td>
            <td>
              Cleaned up after TTL + &#948; grace window.
              Terminal state.
            </td>
          </tr>
        </tbody>
      </table>
    </section>

    <!-- Section 8 -->
    <section anchor="idempotency" numbered="true" toc="default">
      <name>Idempotency and Replay Behavior</name>

      <section anchor="idempotency-before"
               numbered="true" toc="default">
        <name>Replay Before TTL</name>
        <t>
          If a client replays Phase&#160;2 before TTL expiry, the
          server <bcp14>MUST</bcp14> return the same response
          associated with the original
          <tt>DTT-2PHP-Server-Correlation-ID</tt>. No duplicate
          processing occurs.
        </t>
      </section>

      <section anchor="idempotency-after"
               numbered="true" toc="default">
        <name>Replay After TTL</name>
        <t>
          Two configurable server behaviors are defined, applicable
          per endpoint or globally:
        </t>
        <dl newline="false" spacing="normal">
          <dt>REJECT (default):</dt>
          <dd>
            Return <tt>408 TTL_EXPIRED</tt>. The client
            <bcp14>MUST</bcp14> re-submit Phase&#160;1 as a new
            request with a new
            <tt>DTT-2PHP-Client-Correlation-ID</tt>.
          </dd>
          <dt>REUSE:</dt>
          <dd>
            The server accepts the late Phase&#160;2 confirmation
            and uses the persisted intent, provided cleanup has not
            yet occurred within the &#948; grace window.
          </dd>
        </dl>
        <t>
          Servers <bcp14>MAY</bcp14> expose the replay policy via
          the following header:
        </t>
        <artwork align="left" type="http-message"><![CDATA[
DTT-2PHP-Replay-Policy: REJECT | REUSE
        ]]></artwork>
      </section>
    </section>

    <!-- Section 9 -->
    <section anchor="ledger" numbered="true" toc="default">
      <name>Intent Ledger</name>

      <section anchor="ledger-overview" numbered="true" toc="default">
        <name>Overview</name>
        <t>
          The Intent Ledger is a durable, queryable log of all 2PHP
          transactions. It is distinct from execution
          telemetry&#8212;it is an intent registry, not a trace of
          execution. Its purpose is to provide provable bilateral
          registration of every mutation intent across all
          participating services.
        </t>
      </section>

      <section anchor="ledger-vs-telemetry"
               numbered="true" toc="default">
        <name>Architectural Contrast: Inline DTT vs. Passive Telemetry</name>
        <t>
          Existing distributed tracing frameworks, such as
          OpenTelemetry <xref target="OTEL"/>, pass trace contexts
          out-of-band to log execution data after it happens. This
          post-mortem model suffers from ingestion lag and cannot
          stop data mismatches when a network drops right at a
          service boundary.
        </t>
        <t>
          In contrast, the 2PHP Distributed Transaction Tracker
          (DTT) Ledger operates inline as an active safety gate. It
          forces both the client and server to write an atomic record
          of intent BEFORE data processing starts. This turns passive
          logging into real-time failure prevention.
        </t>
      </section>

      <section anchor="ledger-coupling" numbered="true" toc="default">
        <name>Microservice Autonomy and Compensation Hooks</name>
        <t>
          2PHP is NOT a Distributed Transaction Coordinator (DTC).
          It requires zero cross-service thread blocking or lock
          coordination. Each service boundary handles its handshake
          independently. The 3-Tier Ledger architecture uses a
          local-first design where writes are synchronous but local,
          completely removing network hops from the runtime path.
        </t>
        <t>
          By standardizing states like <tt>WAITING_CONFIRM</tt> and
          <tt>TTL_EXPIRED</tt> across all systems, the platform
          provides a uniform data layer. Infrastructure tools can
          read these state transitions to automatically trigger
          custom application-layer compensation scripts or
          data-cleanup workflows without human intervention.
        </t>
      </section>

      <section anchor="ledger-arch" numbered="true" toc="default">
        <name>Architecture: 3-Tier Ledger Model</name>
        <t>
          The Intent Ledger uses a local-first, batch-sync
          architecture to ensure it is never on the critical path
          of service processing.
        </t>
        <dl newline="false" spacing="normal">
          <dt>Tier&#160;1 &#8212; Local Ledger:</dt>
          <dd>
            Per-service, in-process write. Synchronous but
            local&#8212;no network hop. Authoritative source of
            truth for that service&#8217;s phase states.
          </dd>
          <dt>Tier&#160;2 &#8212; Batch Sync:</dt>
          <dd>
            Scheduled background job syncing local ledger entries
            to the Central Ledger. Frequency configurable per
            service. Not on the critical path.
          </dd>
          <dt>Tier&#160;3 &#8212; Central Ledger:</dt>
          <dd>
            Read-optimized aggregate view across all services.
            Used for audit, incident investigation, and SLA
            reporting. Does not require high availability or low
            latency guarantees.
          </dd>
        </dl>
      </section>

      <section anchor="ledger-write" numbered="true" toc="default">
        <name>Write Flow</name>
        <t>
          The intent payload and ledger record
          <bcp14>MUST</bcp14> both be persisted as a single unit
          of work before <tt>200 OK</tt> is returned.
        </t>
        <figure>
          <name>Ledger Write Flow</name>
          <artwork align="left" type="ascii-art"><![CDATA[
Phase 1 received
       |
       v
Persist as single unit of work:
  - Intent payload (durable store)
  - Ledger record (Local Ledger, phase: WAITING_CONFIRM)
  `|`
  `v`
200 OK returned to client
       |
       v  (async background batch job)
Local Ledger --> Central Ledger  (eventually consistent)
          ]]></artwork>
        </figure>
        <t>
          For highly critical services, the payload and ledger
          <bcp14>SHOULD</bcp14> be co-located in the same store to
          eliminate the need for distributed transaction
          coordination. For less critical services, a temporary
          copy of the payload and ledger entry is sufficient for
          Phase&#160;1 and Phase&#160;2 lifecycle management, with
          the ledger entry synced to the Central Ledger via event
          publish, not on the critical path.
        </t>
      </section>

      <section anchor="ledger-pull" numbered="true" toc="default">
        <name>On-Demand Pull</name>
        <t>
          The Central Ledger Management Application
          <bcp14>MAY</bcp14> trigger an on-demand pull from any
          service&#8217;s local ledger. This mechanism is used
          during incident investigation when the latest data is
          needed immediately, without waiting for the next
          scheduled batch sync.
        </t>
      </section>

      <section anchor="ledger-modes" numbered="true" toc="default">
        <name>Operational Modes</name>
        <table align="center">
          <thead>
            <tr>
              <th>Scenario</th>
              <th>Ledger Used</th>
              <th>Trigger</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>Normal operation</td>
              <td>Local Ledger only</td>
              <td>Per phase transition</td>
            </tr>
            <tr>
              <td>Routine audit</td>
              <td>Central Ledger</td>
              <td>Batch sync (scheduled)</td>
            </tr>
            <tr>
              <td>Incident investigation</td>
              <td>Central Ledger</td>
              <td>On-demand pull from Management App</td>
            </tr>
            <tr>
              <td>Compliance reporting</td>
              <td>Central Ledger</td>
              <td>Batch sync (scheduled)</td>
            </tr>
          </tbody>
        </table>
      </section>

      <section anchor="ledger-schema" numbered="true" toc="default">
        <name>Ledger Schema</name>
        <t>
          Each ledger entry <bcp14>MUST</bcp14> include the
          following fields:
        </t>
        <table align="center">
          <thead>
            <tr><th>Field</th><th>Description</th></tr>
          </thead>
          <tbody>
            <tr>
              <td><tt>client_correlation_id</tt></td>
              <td>Client-assigned request identifier.</td>
            </tr>
            <tr>
              <td><tt>server_correlation_id</tt></td>
              <td>Server-assigned intent identifier.</td>
            </tr>
            <tr>
              <td><tt>service_ledger_id</tt></td>
              <td>
                GUID issued to the service at ledger registration.
              </td>
            </tr>
            <tr>
              <td><tt>service_endpoint</tt></td>
              <td>Target service and resource path.</td>
            </tr>
            <tr>
              <td><tt>actor</tt></td>
              <td>
                Role of the entry writer: <tt>client</tt> or
                <tt>server</tt>.
              </td>
            </tr>
            <tr>
              <td><tt>source</tt></td>
              <td>
                Identity of the participant writing this entry
                (e.g., <tt>client</tt>, <tt>serviceA</tt>,
                <tt>serviceB1</tt>).
              </td>
            </tr>
            <tr>
              <td><tt>target</tt></td>
              <td>
                The downstream service being called. Populated
                only when <tt>actor = client</tt>.
                <bcp14>MUST</bcp14> be null for
                <tt>actor = server</tt> entries.
              </td>
            </tr>
            <tr>
              <td><tt>parent_reference_id</tt></td>
              <td>
                The correlation ID that the immediate upstream
                caller provided when calling this service. Used
                to reconstruct the full distributed call tree.
              </td>
            </tr>
            <tr>
              <td><tt>phase</tt></td>
              <td>Current or terminal phase state.</td>
            </tr>
            <tr>
              <td><tt>phase_1_timestamp</tt></td>
              <td>When Phase&#160;1 was received (ISO&#160;8601).</td>
            </tr>
            <tr>
              <td><tt>phase_2_timestamp</tt></td>
              <td>
                When Phase&#160;2 confirmation was received, if
                applicable (ISO&#160;8601).
              </td>
            </tr>
            <tr>
              <td><tt>ttl_ms</tt></td>
              <td>
                Configured TTL for this request, in milliseconds.
              </td>
            </tr>
            <tr>
              <td><tt>outcome</tt></td>
              <td>
                Terminal outcome: <tt>COMMITTED</tt>,
                <tt>ABANDONED</tt>, or <tt>TTL_EXPIRED</tt>.
              </td>
            </tr>
            <tr>
              <td><tt>payload_ref</tt></td>
              <td>
                Reference to the persisted payload. Not the
                payload itself.
              </td>
            </tr>
            <tr>
              <td><tt>sync_timestamp</tt></td>
              <td>
                When this entry was synced to the Central Ledger
                (ISO&#160;8601).
              </td>
            </tr>
            <tr>
              <td><tt>transaction_reference</tt></td>
              <td>Reference to transaction ID (optional).</td>
            </tr>
          </tbody>
        </table>
      </section>
    </section>

    <!-- Section 10 -->
    <section anchor="correlation" numbered="true" toc="default">
      <name>Cross-Service Correlation</name>

      <section anchor="correlation-overview"
               numbered="true" toc="default">
        <name>Overview</name>
        <t>
          When a service fans out to multiple downstream services,
          the Intent Ledger captures every participant&#8217;s view
          of every call&#8212;both the caller
          (<tt>actor = client</tt>) and the callee
          (<tt>actor = server</tt>). This produces a unified,
          traversable ledger that can reconstruct the full
          distributed call tree without any distributed coordination
          at the protocol level.
        </t>
      </section>

      <section anchor="correlation-rules"
               numbered="true" toc="default">
        <name>Propagation Rules</name>
        <ul spacing="normal">
          <li>
            Every service <bcp14>MUST</bcp14> write its own ledger
            entry after each phase transition.
          </li>
          <li>
            The client <bcp14>MUST</bcp14> write its ledger entry
            after receiving the Phase&#160;1 response, so that both
            <tt>client_correlation_id</tt> and
            <tt>server_correlation_id</tt> are available at write
            time.
          </li>
          <li>
            <tt>parent_reference_id</tt> <bcp14>MUST</bcp14> always
            record what the immediate upstream caller provided as
            its correlation ID when calling this service&#8212;
            enabling upward tree traversal.
          </li>
          <li>
            Each call <bcp14>MUST</bcp14> produce exactly two
            matching ledger rows: one <tt>actor = client</tt> and
            one <tt>actor = server</tt>, linked by the same
            <tt>client_correlation_id</tt> +
            <tt>server_correlation_id</tt> pair. This confirms
            dual-side registration&#8212;the core guarantee of 2PHP.
          </li>
          <li>
            <tt>target</tt> <bcp14>MUST</bcp14> be populated only
            on <tt>actor = client</tt> rows. It
            <bcp14>MUST</bcp14> be null on
            <tt>actor = server</tt> rows.
          </li>
        </ul>
      </section>

      <section anchor="correlation-example"
               numbered="true" toc="default">
        <name>Example Call Chain</name>
        <figure>
          <name>Fan-Out Call Chain</name>
          <artwork align="left" type="ascii-art"><![CDATA[
Client --> Service A --> Service B1 --> Service C1
                     --> Service B2 --> Service D1
                     --> Service B3 --> Service E1
          ]]></artwork>
        </figure>
        <t>
          The complete ledger for this call chain contains 14
          entries&#8212;two rows (one <tt>actor = client</tt>, one
          <tt>actor = server</tt>) for each of the seven
          service-boundary interactions. The root client entry uses
          <tt>client-corr-id1</tt> as both its
          <tt>client_correlation_id</tt> and its
          <tt>parent_reference_id</tt>, establishing the tree
          anchor. Each subsequent tier sets
          <tt>parent_reference_id</tt> to the correlation ID
          received from its immediate upstream caller.
        </t>
      </section>

      <section anchor="correlation-query"
               numbered="true" toc="default">
        <name>Tree Reconstruction Query Patterns</name>
        <ul spacing="normal">
          <li>
            <strong>Full tree from root:</strong> Query
            <tt>parent_reference_id = client-corr-id1</tt>
            recursively&#8212;walks the entire fan-out tree.
          </li>
          <li>
            <strong>Everything a service initiated:</strong> Query
            <tt>source = serviceA</tt> AND
            <tt>actor = client</tt>.
          </li>
          <li>
            <strong>Confirm dual registration:</strong> Match rows
            by <tt>client_correlation_id</tt> +
            <tt>server_correlation_id</tt>&#8212;one
            <tt>actor = client</tt>, one <tt>actor = server</tt>.
          </li>
          <li>
            <strong>Find stalled transactions:</strong> Query
            <tt>phase = WAITING_CONFIRM</tt> or
            <tt>phase = TTL_EXPIRED</tt> across any
            <tt>source</tt>.
          </li>
        </ul>
      </section>
    </section>

    <!-- Section 11 -->
    <section anchor="backward" numbered="true" toc="default">
      <name>Backward Compatibility</name>
      <t>2PHP is designed to be fully backward compatible:</t>
      <ul spacing="normal">
        <li>
          Clients that do not include
          <tt>DTT-2PHP-Enabled: true</tt>
          <bcp14>MUST</bcp14> experience no change in behavior.
        </li>
        <li>
          Servers that do not implement 2PHP
          <bcp14>MUST</bcp14> ignore the header and process
          normally.
        </li>
        <li>
          2PHP <bcp14>MAY</bcp14> be adopted
          incrementally&#8212;per endpoint, per service, or
          organization-wide.
        </li>
        <li>
          2PHP makes no changes to existing HTTP methods (POST,
          PUT, PATCH, DELETE). It is purely additive.
        </li>
        <li>
          No new transport layer or connection upgrade is required.
        </li>
      </ul>
    </section>

    <!-- Section 12 -->
    <section anchor="implementation" numbered="true" toc="default">
      <name>Implementation Considerations</name>

      <section anchor="impl-targets" numbered="true" toc="default">
        <name>Reference Implementation Targets</name>
        <ul spacing="normal">
          <li>
            **Spring Boot** &#8212;
            <tt>OncePerRequestFilter</tt> interceptor
          </li>
          <li>**Express.js** &#8212; middleware</li>
          <li>
            **Azure API Management** &#8212; inbound policy
          </li>
          <li>
            **MuleSoft** &#8212; HTTP request/response
            interceptor policy
          </li>
        </ul>
      </section>

      <section anchor="impl-storage" numbered="true" toc="default">
        <name>Durable Storage for Phase 1 Persistence</name>
        <t>
          Implementations <bcp14>SHOULD</bcp14> use one of the
          following for durable storage of intent payload and
          ledger records:
        </t>
        <ul spacing="normal">
          <li>
            Relational database with TTL-aware cleanup job.
          </li>
          <li>
            Redis with TTL-based key expiry (plus &#948; buffer).
          </li>
          <li>
            Distributed queue (e.g., Azure Service Bus, AWS SQS)
            with visibility timeout.
          </li>
        </ul>
      </section>

      <section anchor="impl-ttl" numbered="true" toc="default">
        <name>TTL Defaults</name>
        <t>
          The following are recommended starting values.
          Implementations <bcp14>MAY</bcp14> adjust these per
          endpoint.
        </t>
        <table align="center">
          <thead>
            <tr>
              <th>Scenario</th>
              <th>TTL</th>
              <th>&#948; (server-internal)</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>Low-latency APIs</td>
              <td>5,000 ms</td>
              <td>1,000 ms</td>
            </tr>
            <tr>
              <td>Standard APIs</td>
              <td>30,000 ms</td>
              <td>5,000 ms</td>
            </tr>
            <tr>
              <td>Long-running operations</td>
              <td>120,000 ms</td>
              <td>10,000 ms</td>
            </tr>
          </tbody>
        </table>
        <t>
          &#948; values are server implementation defaults and
          <bcp14>MUST NOT</bcp14> be exposed in protocol headers.
        </t>
      </section>

      <section anchor="impl-ledger-reg"
               numbered="true" toc="default">
        <name>Service Ledger Registration</name>
        <t>
          Each service <bcp14>SHOULD</bcp14> register with the
          Intent Ledger at onboarding and be issued a GUID
          (<tt>service_ledger_id</tt>) that is included in all
          ledger entries for that service. This enables
          cross-service querying while maintaining service-level
          isolation.
        </t>
      </section>
    </section>

    <!-- Section 13 -->
    <section anchor="security" numbered="true" toc="default">
      <name>Security Considerations</name>

      <section anchor="sec-corrids" numbered="true" toc="default">
        <name>Correlation ID Confidentiality</name>
        <t>
          <tt>DTT-2PHP-Client-Correlation-ID</tt> and
          <tt>DTT-2PHP-Server-Correlation-ID</tt> values
          <bcp14>MUST</bcp14> be treated as opaque identifiers.
          Implementations <bcp14>SHOULD</bcp14> use UUID&#160;v4
          or equivalent cryptographically random values to prevent
          enumeration or prediction of correlation IDs by
          unauthorized parties.
        </t>
      </section>

      <section anchor="sec-spoof" numbered="true" toc="default">
        <name>Phase 2 Spoofing</name>
        <t>
          A malicious actor in possession of both
          <tt>DTT-2PHP-Client-Correlation-ID</tt> and
          <tt>DTT-2PHP-Server-Correlation-ID</tt> could attempt to
          submit a spoofed Phase&#160;2 confirmation.
          Implementations <bcp14>MUST</bcp14> authenticate
          Phase&#160;2 confirmation requests using the same
          authentication mechanism applied to Phase&#160;1
          requests. The server <bcp14>MUST</bcp14> verify that
          the authenticated identity of the Phase&#160;2 requester
          matches that of the Phase&#160;1 requester.
        </t>
      </section>

      <section anchor="sec-replay" numbered="true" toc="default">
        <name>Replay Attacks</name>
        <t>
          The <tt>REUSE</tt> replay policy
          (<xref target="idempotency-after"/>) introduces a window
          during which a replayed Phase&#160;2 confirmation may be
          accepted. Implementations using <tt>REUSE</tt>
          <bcp14>SHOULD</bcp14> apply additional controls, such as
          request signing or short-lived tokens, to mitigate replay
          risk within the &#948; grace window.
        </t>
      </section>

      <section anchor="sec-payload" numbered="true" toc="default">
        <name>Intent Payload Confidentiality</name>
        <t>
          The intent payload persisted during Phase&#160;1 may
          contain sensitive data. Implementations
          <bcp14>MUST</bcp14> ensure that persisted payloads are
          protected at rest using appropriate encryption mechanisms
          consistent with the sensitivity of the data.
        </t>
      </section>

      <section anchor="sec-ledger" numbered="true" toc="default">
        <name>Ledger Access Control</name>
        <t>
          The Central Ledger aggregates intent records across all
          services. Access to the Central Ledger
          <bcp14>MUST</bcp14> be restricted to authorized audit,
          operations, and compliance consumers. The on-demand pull
          mechanism (<xref target="ledger-pull"/>)
          <bcp14>MUST</bcp14> be protected by appropriate
          authentication and authorization controls.
        </t>
      </section>

      <section anchor="sec-tls" numbered="true" toc="default">
        <name>Transport Security</name>
        <t>
          All 2PHP exchanges <bcp14>MUST</bcp14> be conducted over
          TLS (HTTPS). Use of 2PHP over unencrypted HTTP is
          <bcp14>NOT RECOMMENDED</bcp14>.
        </t>
      </section>
    </section>

  

    <!-- Section 14: IANA Considerations -->
    <section anchor="iana">
  <name>IANA Considerations</name>

  <section anchor="iana-headers">
    <name>HTTP Header Field Registrations</name>
    <t>
      This document requests registration of the following HTTP
      header fields in the "Permanent Message Header Field Names"
      registry defined in <xref target="RFC3864"/> and updated
      by <xref target="RFC9110"/>:
    </t>
    <table>
      <thead>
        <tr>
          <th>Header Field Name</th>
          <th>Applicable Protocol</th>
          <th>Status</th>
          <th>Reference</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td><tt>DTT-2PHP-Enabled</tt></td>
          <td>HTTP</td>
          <td>Experimental</td>
          <td>This document</td>
        </tr>
        <tr>
          <td><tt>DTT-2PHP-Client-Correlation-ID</tt></td>
          <td>HTTP</td>
          <td>Experimental</td>
          <td>This document</td>
        </tr>
        <tr>
          <td><tt>DTT-2PHP-Auto-Confirm</tt></td>
          <td>HTTP</td>
          <td>Experimental</td>
          <td>This document</td>
        </tr>
        <tr>
          <td><tt>DTT-2PHP-Callback</tt></td>
          <td>HTTP</td>
          <td>Experimental</td>
          <td>This document</td>
        </tr>
        <tr>
          <td><tt>DTT-2PHP-Requested-TTL</tt></td>
          <td>HTTP</td>
          <td>Experimental</td>
          <td>This document</td>
        </tr>
        <tr>
          <td><tt>DTT-2PHP-Server-Correlation-ID</tt></td>
          <td>HTTP</td>
          <td>Experimental</td>
          <td>This document</td>
        </tr>
        <tr>
          <td><tt>DTT-2PHP-Phase-State</tt></td>
          <td>HTTP</td>
          <td>Experimental</td>
          <td>This document</td>
        </tr>
        <tr>
          <td><tt>DTT-2PHP-TTL</tt></td>
          <td>HTTP</td>
          <td>Experimental</td>
          <td>This document</td>
        </tr>
        <tr>
          <td><tt>DTT-2PHP-PONR-Deadline</tt></td>
          <td>HTTP</td>
          <td>Experimental</td>
          <td>This document</td>
        </tr>
        <tr>
          <td><tt>DTT-2PHP-Resource-ID</tt></td>
          <td>HTTP</td>
          <td>Experimental</td>
          <td>This document</td>
        </tr>
        <tr>
          <td><tt>DTT-2PHP-Replay-Policy</tt></td>
          <td>HTTP</td>
          <td>Experimental</td>
          <td>This document</td>
        </tr>
        <tr>
          <td><tt>DTT-2PHP-Message</tt></td>
          <td>HTTP</td>
          <td>Experimental</td>
          <td>This document</td>
        </tr>
      </tbody>
    </table>
  </section>
</section>
<section anchor="requirements">
  <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>
</section>
</middle>

  <!-- ============================================================ -->
  <!-- BACK MATTER                                                   -->
  <!-- ============================================================ -->
  <back>
    <!-- Normative References -->
    <references anchor="normative-refs">
      <name>Normative References</name>

      <reference anchor="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">
        <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="RFC9110">
        <front>
          <title>HTTP Semantics</title>
          <author initials="R." surname="Fielding"
                  fullname="R. Fielding"/>
          <author initials="M." surname="Nottingham"
                  fullname="M. Nottingham"/>
          <author initials="J." surname="Reschke"
                  fullname="J. Reschke"/>
          <date year="2022" month="June"/>
        </front>
        <seriesInfo name="RFC" value="9110"/>
      </reference>

      <reference anchor="RFC9112">
        <front>
          <title>HTTP/1.1</title>
          <author initials="R." surname="Fielding"
                  fullname="R. Fielding"/>
          <author initials="J." surname="Reschke"
                  fullname="J. Reschke"/>
          <date year="2022" month="June"/>
        </front>
        <seriesInfo name="RFC" value="9112"/>
      </reference>

      <reference anchor="RFC3864">
        <front>
          <title>
            Registration Procedures for Message Header Fields
          </title>
          <author initials="G." surname="Klyne"
                  fullname="G. Klyne"/>
          <author initials="M." surname="Nottingham"
                  fullname="M. Nottingham"/>
          <author initials="J." surname="Mogul"
                  fullname="J. Mogul"/>
          <date year="2004" month="September"/>
        </front>
        <seriesInfo name="BCP" value="90"/>
        <seriesInfo name="RFC" value="3864"/>
      </reference>

    </references>

    <!-- Informative References -->
    <references anchor="informative-refs">
      <name>Informative References</name>

      <reference anchor="OTEL">
        <front>
          <title>OpenTelemetry Specification</title>
          <author>
            <organization>OpenTelemetry Authors</organization>
          </author>
          <date year="2024"/>
        </front>
        <refcontent>
          https://opentelemetry.io/docs/specs/otel/
        </refcontent>
      </reference>

      <reference anchor="SAGA">
        <front>
          <title>Sagas</title>
          <author initials="H." surname="Garcia-Molina"
                  fullname="H. Garcia-Molina"/>
          <author initials="K." surname="Salem"
                  fullname="K. Salem"/>
          <date year="1987"/>
        </front>
        <refcontent>
          ACM SIGMOD Record, Vol. 16, Issue 3
        </refcontent>
      </reference>

      <reference anchor="OUTBOX">
        <front>
          <title>Pattern: Transactional outbox</title>
          <author initials="C." surname="Richardson"
                  fullname="C. Richardson"/>
          <date year="2018"/>
        </front>
      </reference>

    </references>

  </back>

</rfc>