<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE rfc [
  <!ENTITY nbsp    "">
  <!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-01"
     ipr="trust200902"
     submissionType="independent"
     xml:lang="en"
     version="3">

  <!-- ============================================================ -->
  <!-- FRONT MATTER                                                  -->
  <!-- ============================================================ -->
  <front>
    <title abbrev="2PHP-DTT">
      2-Phase HTTP Protocol for Distributed Transaction Tracking (2PHP-DTT)
    </title>

    <seriesInfo name="Internet-Draft"
                value="draft-venkateswaran-jamwal-twophp-01"/>

    <!-- ascii attribute required per RFC 7997 Section 5 when
         non-Latin scripts may appear; kept here for completeness -->
    <author fullname="Barathan Venkateswaran"
            initials="B."
            surname="Venkateswaran">
      <organization>Salesforce</organization>
      <address>
        <email>bvenkateswaran@salesforce.com</email>
      </address>
    </author>

    <author fullname="Aditya Jamwal"
            initials="A."
            surname="Jamwal">
      <organization>Salesforce</organization>
      <address>
        <email>ajamwal@salesforce.com</email>
      </address>
    </author>

    <date year="2026" month="July" day="21"/>

    <area>Applications and Real-Time</area>

    <!-- 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>distributed transaction tracking</keyword>
    <keyword>intent registration</keyword>
    <keyword>ledger</keyword>
    <keyword>idempotency</keyword>
    <keyword>microservices</keyword>
    <keyword>correlation</keyword>
    <keyword>request reliability</keyword>
    <keyword>MCP</keyword>
    <keyword>agent-to-agent</keyword>

    <abstract>
      <t>
        This document specifies the 2-Phase HTTP Protocol for
        Distributed Transaction Tracking (2PHP-DTT), 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>
        The acronym DTT expands to Distributed Transaction Tracking.
        2PHP is not Two-Phase Commit (2PC). Microservices architectures
        deliberately avoid the cross-service locking and blocking that
        2PC requires. 2PHP standardizes the intent-tracking pattern that
        implementations currently build ad hoc, providing a common wire
        contract and a queryable ledger of registered intent without
        introducing a distributed transaction coordinator.
      </t>
      <t>
        A primary motivator for this work is the growing use of
        agent-driven HTTP invocations, including Model Context Protocol
        (MCP) tool calls and agent-to-agent (A2A) integrations. When an
        agent does not receive a response, its default behavior is to
        retry. Absent a queryable record of prior intent, retries pile
        onto services that may already be overwhelmed, causing
        deadlocks, resource exhaustion, and full outages. 2PHP's Phase 1
        ledger record enables the client to inspect whether the prior
        intent was registered before issuing a retry, breaking the
        retry-storm feedback loop at the protocol layer.
      </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 does not claim novelty over existing
        intent-tracking, idempotency, or distributed-tracing
        patterns. Its goal is to define a common HTTP-layer wire
        contract and a common ledger schema so that a single
        reference implementation--packaged as a library or
        framework module--can serve multiple deployments,
        replacing the per-team ad hoc implementations that
        currently reinvent this pattern.
      </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. 2PHP-DTT
        targets the synchronous request-response boundary, where no
        standard acknowledgement primitive exists to confirm bilateral
        intent registration before processing. Asynchronous HTTP
        patterns -- 202 Accepted with Location-based polling in
        HTTP Semantics, Prefer: respond-async negotiation, webhooks,
        and message-broker delivery -- already provide durable
        acknowledgement of intent by construction, and are therefore
        out of scope for this specification.
      </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--those using HTTP methods POST, PUT, PATCH, and
        DELETE--are treated as single atomic operations from the
        client'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--including the Saga pattern
        <xref target="SAGA"/>, Transactional Outbox
        <xref target="OUTBOX"/>, and application-layer idempotency
        keys--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 for
        Distributed Transaction Tracking (2PHP-DTT), 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>
        The acronym DTT expands to Distributed Transaction Tracking.
        2PHP is not Two-Phase Commit (2PC). 2PC requires cross-service
        locking and blocking coordination that microservices
        architectures deliberately avoid. 2PHP introduces no distributed
        transaction coordinator, no cross-service locks, and no
        thread-blocking. Each service boundary owns its own handshake
        independently; the Intent Ledger is a passive, queryable record
        of that handshake, not a coordinator. What is standardized here
        is the tracking pattern that implementations currently build
        ad hoc: a common wire contract and a common ledger contract,
        rather than a per-team reinvention.
      </t>
      <t>
        A primary motivator for this work is the growing use of
        agent-driven HTTP invocations, including Model Context Protocol
        (MCP) tool calls and agent-to-agent (A2A) integrations. Agents
        default to retrying when a response is not received within an
        expected window. Without a queryable record of prior intent,
        successive retries--often from multiple relays or peer
        agents--pile onto services that may already be overwhelmed.
        The result is server-side deadlocks, resource exhaustion, and
        cascading outages. 2PHP's Phase1 ledger record enables
        the client, upstream service, or orchestrating agent to inspect
        whether the prior intent was durably registered before issuing
        a retry, breaking the retry-storm feedback loop at the protocol
        layer. This inspection step is the first line of defense in
        agent-driven integration topologies where retry pressure is
        otherwise unbounded.
      </t>
      <t>
        This gap is specific to synchronous HTTP invocations.
        Asynchronous integration patterns--<tt>202 Accepted</tt>
        responses with <tt>Location</tt>-based status polling
        (<xref target="RFC9110"/>), <tt>Prefer: respond-async</tt>
        negotiation (<xref target="RFC7240"/>), webhooks, and
        message-broker delivery--already provide a durable
        acknowledgement of intent as part of their wire contract: the
        client receives a handle or ack the moment the server accepts
        the request, and inspecting prior state before retrying is
        built in. 2PHP-DTT does not attempt to duplicate those
        mechanisms. It addresses the boundary they leave open:
        synchronous request-response calls, which remain the dominant
        integration style for microservices and agent-driven tool
        invocations.
      </t>
      <t>
        The value proposition of this specification is
        standardization, not novel mechanism. Intent tracking,
        idempotent retries, and correlation-based inspection are all
        patterns implemented today across microservices deployments,
        typically ad hoc and per-team. This document does not claim
        to be the only solution to any of these problems, nor does
        it claim novelty over existing patterns. Its aim is a
        consistent, standardized wire contract for intent
        registration and Distributed Transaction Tracking:
        client-supplied correlation identifiers, an explicit phase
        state contract carried in HTTP header fields, and a queryable
        Intent Ledger schema, specified together so that a common
        implementation--packaged as a library in Spring Boot,
        Express.js, an API gateway policy, or an agent framework
        middleware--can serve heterogeneous deployments without
        per-vendor reinvention. Where deployments already use
        client-supplied identifiers for retry safety, the
        correlation identifier defined here <bcp14>MAY</bcp14>
        carry the same value; nothing in this document precludes
        composing 2PHP-DTT with existing idempotency, retry, or
        tracing conventions.
      </t>
      <t>2PHP is designed to be:</t>
      <ul spacing="normal">
        <li>
          <strong>Backward compatible</strong>--servers and clients
          that do not implement 2PHP are unaffected.
        </li>
        <li>
          <strong>Lightweight</strong>--two HTTP round trips; no
          new transport layer.
        </li>
        <li>
          <strong>Composable</strong>--each service boundary
          operates independently; no distributed coordination is
          required.
        </li>
        <li>
          <strong>Standardized</strong>--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 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 Phase1 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 Phase2 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 Phase2
          confirmation.
        </dd>
        <dt>Delta (delta):</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'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 Phase1 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'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
        Phase1 response. The resource is created only after
        Phase2 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 -- 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. The
            server <bcp14>MUST NOT</bcp14> substitute a
            server-generated proxy for this identifier: without a
            client-supplied identifier the client cannot
            deterministically query the ledger for prior intent,
            which is the retry-inspection guarantee that motivates
            this specification.
          </t>
          <t>
            If a request carries <tt>DTT-2PHP-Enabled: true</tt> but
            omits <tt>DTT-2PHP-Client-Correlation-ID</tt>, the server
            <bcp14>MUST</bcp14> reject the request with
            <tt>400 Bad Request</tt> and populate
            <tt>DTT-2PHP-Message</tt> to identify the missing header.
            The server <bcp14>MUST NOT</bcp14> synthesize a
            correlation identifier on the client's behalf. This
            requirement applies to both Standard 2PHP
            (<xref target="spec-phase2"/>) and Auto-Confirm mode
            (<xref target="spec-autoconfirm"/>).
          </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 Phase2 confirmation.
            </dd>
            <dt>DTT-2PHP-TTL:</dt>
            <dd>
              Milliseconds the server will wait for Phase2
              confirmation. Server-defined. The client
              <bcp14>MUST</bcp14> send Phase2 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 ISO8601
              format. The client <bcp14>MUST</bcp14> send
              Phase2 before this timestamp.
            </dd>
            <dt>DTT-2PHP-Phase-State:</dt>
            <dd>
              Current phase state. Value is
              <tt>WAITING_CONFIRM</tt> on Phase1 response.
            </dd>
          </dl>
          <t>
            No <tt>DTT-2PHP-Resource-ID</tt> is returned in
            Phase1. The resource does not yet exist; it is
            created only after Phase2 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 Phase1 response, the client
            <bcp14>MUST</bcp14> write a ledger entry to its local
            ledger. The client writes after the Phase1 response
            so that both <tt>client_correlation_id</tt> and
            <tt>server_correlation_id</tt> are available at write
            time. The client'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 -- Client Confirmation</name>

        <section anchor="spec-phase2-request"
                 numbered="true" toc="default">
          <name>Client Request</name>
          <t>
            The client confirms receipt of the Phase1
            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 Phase2 before
            the <tt>DTT-2PHP-PONR-Deadline</tt> timestamp received
            in the Phase1 response.
          </t>
          <t>
            A 2PHP-capable client
            <bcp14>MUST NOT</bcp14> send a Phase2 confirmation if
            the Phase1 response omitted the
            <tt>DTT-2PHP-Server-Correlation-ID</tt> header. The absence
            of that header indicates the server did not process the
            request under 2PHP semantics--typically because the
            server does not implement this specification. In that case
            the response <bcp14>MUST</bcp14> be treated as a conventional
            terminal HTTP response and the client
            <bcp14>MUST NOT</bcp14> issue a Phase2 request against
            any derived path. This preserves backward compatibility with
            servers that ignore the <tt>DTT-2PHP-Enabled</tt> opt-in
            header (see <xref target="backward"/>).
          </t>
        </section>

        <section anchor="spec-phase2-sync"
                 numbered="true" toc="default">
          <name>Server Response</name>
          <t>
            This revision specifies synchronous operation only. On
            receipt of a valid Phase2 confirmation, 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--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 Phase2
            response--no polling is required.
          </t>
          <t>
            Asynchronous operation is out of scope for this
            specification by design, not deferred. Asynchronous HTTP
            already provides a durable acknowledgement primitive:
            <tt>202 Accepted</tt> with a <tt>Location</tt> handle for
            status polling as defined in <xref target="RFC9110"/>,
            optionally negotiated with <tt>Prefer: respond-async</tt>
            as defined in <xref target="RFC7240"/>. Webhook and
            message-broker delivery models similarly bake
            acknowledgement into their wire contract. In all of these,
            the client learns whether the server registered its intent
            the moment the request is accepted, which is precisely the
            gap 2PHP-DTT addresses for synchronous calls. Servers
            requiring asynchronous execution semantics
            <bcp14>SHOULD</bcp14> use those established mechanisms
            rather than extending 2PHP-DTT.
          </t>
        </section>
      </section>

      <section anchor="spec-ttl" numbered="true" toc="default">
        <name>TTL Expiry</name>
        <t>
          Any Phase2 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 (delta) 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 server-side mode that
            eliminates the explicit Phase2 client confirmation
            round trip. When Auto-Confirm is enabled, the server
            writes the ledger record and crosses the PONR immediately
            after Phase1, then executes business logic inline
            on the original request channel. The final result
            (<tt>200 OK</tt> + <tt>DTT-2PHP-Resource-ID</tt>) is
            returned synchronously on that same channel--
            behaviorally identical to a standard HTTP request from the
            client's perspective.
          </t>
          <t>
            Auto-Confirm mode does not include an out-of-band
            callback. Earlier revisions of this specification described
            an asynchronous server-to-client callback to deliver the
            intent acknowledgement independently of the original
            response channel. That mechanism has been removed because
            it introduced failure modes (callback delivery loss,
            client callback endpoint availability, and server-side
            SSRF exposure via a client-supplied URL) that outweighed
            its observability benefit. Clients that require an
            out-of-band record of registered intent SHOULD use Standard
            2PHP (<xref target="spec-phase2"/>), which records
            client-side intent by construction.
          </t>
          <t>
            The client <bcp14>MUST</bcp14> include
            <tt>DTT-2PHP-Client-Correlation-ID</tt> on the Phase1
            request in Auto-Confirm mode, as in Standard 2PHP
            (<xref target="spec-phase1-request"/>). The server
            <bcp14>MUST NOT</bcp14> substitute a server-generated
            proxy for this identifier: without a client-supplied
            correlation identifier the client cannot deterministically
            query the ledger for prior intent, which defeats the
            retry-inspection guarantee that motivates this
            specification. Requests missing this header
            <bcp14>MUST</bcp14> be rejected with
            <tt>400 Bad Request</tt> and
            <tt>DTT-2PHP-Message</tt> indicating the missing header.
            The server <bcp14>MUST</bcp14> return
            <tt>DTT-2PHP-Server-Correlation-ID</tt> in the response so
            the client can correlate the ledger record with its own
            request.
          </t>
          <t>Auto-Confirm mode is valuable for:</t>
          <ul spacing="normal">
            <li>
              <strong>Low-latency critical paths</strong>--the
              Phase2 round trip cost is eliminated while
              preserving bilateral ledger participation.
            </li>
            <li>
              <strong>Server-side opt-in</strong>--platform
              teams can enable ledger tracking without requiring
              clients to implement the Phase2 confirmation
              step, provided the client is willing to attach a
              correlation identifier.
            </li>
            <li>
              <strong>Idempotent retries by inspection</strong>--
              because the client supplies the correlation identifier,
              a client that does not receive the final response can
              query the ledger by
              <tt>DTT-2PHP-Client-Correlation-ID</tt> to determine
              whether the intent was registered and, if so, its
              terminal outcome, before issuing any retry.
            </li>
          </ul>
        </section>

        <section anchor="spec-autoconfirm-headers"
                 numbered="true" toc="default">
          <name>Activation Header</name>
          <artwork align="left" type="http-message"><![CDATA[
DTT-2PHP-Auto-Confirm: true
          ]]></artwork>
          <dl newline="false" spacing="normal">
            <dt>DTT-2PHP-Auto-Confirm:</dt>
            <dd>
              When <tt>true</tt>, the server does not wait for a
              Phase2 confirmation before crossing the PONR
              and executing business logic. The final outcome is
              returned synchronously on the original request channel.
            </dd>
          </dl>
        </section>

        <section anchor="spec-autoconfirm-flow"
                 numbered="true" toc="default">
          <name>Protocol Flow</name>
          <figure>
            <name>Auto-Confirm Mode</name>
            <artwork align="left" type="ascii-art"><![CDATA[
Client                                Server
  |                                     |
  |-- POST /orders ------------------>  |
  |   DTT-2PHP-Enabled: true            |
  |   DTT-2PHP-Auto-Confirm: true       |
  |   DTT-2PHP-Client-Corr-ID: C1       |
  |                                     |
  |            Ledger write             |
  |            (WAITING_CONFIRM ->      |
  |             PROCESSING, atomic)     |
  |            PONR crossed             |
  |                                     |
  |            Server processes inline  |
  |                                     |
  | <-- 200 OK + Resource-ID --------   |
  |     DTT-2PHP-Phase-State:           |
  |       COMMITTED                     |
  |     DTT-2PHP-Server-Corr-ID: S1     |
  |                                     |
            ]]></artwork>
          </figure>
        </section>

        <section anchor="spec-autoconfirm-statemachine"
                 numbered="true" toc="default">
          <name>State Machine -- 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 )
        |
        +-- 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>Latency</th>
                <th>Client 2PHP Support</th>
                <th>Ledger Participation</th>
              </tr>
            </thead>
            <tbody>
              <tr>
                <td>Standard 2PHP</td>
                <td>Yes</td>
                <td>2 round trips</td>
                <td>Full (correlation ID + Phase2)</td>
                <td>Both sides</td>
              </tr>
              <tr>
                <td>Auto-Confirm</td>
                <td>No</td>
                <td>1 round trip</td>
                <td>Correlation ID only</td>
                <td>Both sides</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 )  --  after PONR + delta grace  -->
            ( 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>
              Phase1 received and persisted. Awaiting client
              Phase2 confirmation.
            </td>
          </tr>
          <tr>
            <td><tt>PROCESSING</tt></td>
            <td>
              Phase2 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 + delta 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 Phase2 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>
          Any Phase2 confirmation received after TTL expiry
          <bcp14>MUST</bcp14> be rejected with
          <tt>408 Request Timeout</tt> and
          <tt>DTT-2PHP-Phase-State: TTL_EXPIRED</tt>, consistent with
          <xref target="spec-ttl"/>. The client
          <bcp14>MUST</bcp14> re-submit as a new Phase1 request
          with a new <tt>DTT-2PHP-Client-Correlation-ID</tt>. A late
          Phase2 confirmation carrying the same correlation IDs
          <bcp14>MUST NOT</bcp14> be reused to trigger execution of
          the persisted intent.
        </t>
        <t>
          This revision defines a single replay behavior. Earlier
          revisions described a per-endpoint <tt>REUSE</tt> policy
          that allowed acceptance of a late Phase2 within the
          server-internal delta grace window. That policy has been
          removed because it created an ambiguous boundary with
          <xref target="spec-ttl"/> and expanded the replay-attack
          surface without a durable operational benefit.
        </t>
      </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--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>Relationship to Distributed Tracing</name>
        <t>
          The Intent Ledger is complementary to distributed tracing
          systems such as OpenTelemetry <xref target="OTEL"/>, not a
          replacement. Distributed tracing records what executed:
          spans, timings, causal links, and error paths, typically
          aggregated in a downstream store with some ingestion lag.
          It is the appropriate tool for post-hoc analysis of
          execution behavior.
        </t>
        <t>
          The Intent Ledger records what was <em>registered as
          intent</em> before execution began, and makes that record
          queryable in real time at the point of registration. This
          serves a different question than tracing: not "what
          happened?" but "did the server acknowledge my intent, and
          should I retry?" A client that does not receive the
          expected response can query the ledger by
          <tt>DTT-2PHP-Client-Correlation-ID</tt> to determine
          whether the prior intent was durably registered before
          issuing a retry. This inspection step is only useful if
          the answer is available synchronously and locally to the
          participating service, which is why the ledger is
          normatively local-first
          (<xref target="impl-ledger-arch"/>) rather than a
          downstream aggregation.
        </t>
        <t>
          Deployments SHOULD run both. Tracing answers questions
          about execution across services; the Intent Ledger answers
          questions about registered intent at each service
          boundary. Neither subsumes the other.
        </t>
      </section>

      <section anchor="ledger-role" numbered="true" toc="default">
        <name>Normative Role</name>
        <t>
          At each service boundary, the Intent Ledger
          <bcp14>MUST</bcp14> record a durable entry for every
          Phase1 receipt and every subsequent phase transition,
          and that entry <bcp14>MUST</bcp14> be queryable by
          <tt>DTT-2PHP-Client-Correlation-ID</tt> and by
          <tt>DTT-2PHP-Server-Correlation-ID</tt> before Phase2
          is processed. The ledger write and the intent-payload
          persistence <bcp14>MUST</bcp14> be committed as a single
          unit of work (see <xref target="spec-phase1-response"/>).
        </t>
        <t>
          2PHP-DTT is not a Distributed Transaction Coordinator: no
          cross-service locking, thread blocking, or two-phase
          commit is defined or required. Each service boundary owns
          its own handshake and its own ledger authoritative record.
          How the ledger is stored, tiered, replicated, or exposed
          for aggregate query is an implementation matter and is
          discussed in <xref target="impl-ledger-arch"/>.
        </t>
      </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 Phase1 was received (ISO8601).</td>
            </tr>
            <tr>
              <td><tt>phase_2_timestamp</tt></td>
              <td>
                When Phase2 confirmation was received, if
                applicable (ISO8601).
              </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
                (ISO8601).
              </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's view
          of every call--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 Phase1 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--
            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--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--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--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>--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>
          A 2PHP-capable client
          <bcp14>MUST NOT</bcp14> send a Phase2 confirmation
          against a server that did not return
          <tt>DTT-2PHP-Server-Correlation-ID</tt> in the response to
          the initial mutation request (see
          <xref target="spec-phase2-request"/>). The absence of that
          header identifies the response as originating from a
          non-2PHP server; the response
          <bcp14>MUST</bcp14> be treated as a conventional terminal
          HTTP response. This prevents a 2PHP client from issuing a
          spurious Phase2 request to a legacy server that has
          already committed the mutation.
        </li>
        <li>
          2PHP <bcp14>MAY</bcp14> be adopted
          incrementally--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>
            <strong>Spring Boot</strong>--
            <tt>OncePerRequestFilter</tt> interceptor
          </li>
          <li>
            <strong>Express.js</strong>--middleware
          </li>
          <li>
            <strong>Azure API Management</strong>--inbound policy
          </li>
          <li>
            <strong>MuleSoft</strong>--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 delta 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>delta (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>
          delta 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 anchor="impl-ledger-arch"
               numbered="true" toc="default">
        <name>Recommended Ledger Architecture</name>
        <t>
          The normative requirement (<xref target="ledger-role"/>)
          is that each service maintains a durable, locally
          queryable Intent Ledger. This section describes a
          recommended local-first, batch-sync architecture that
          satisfies that requirement without placing the ledger on
          the critical path of request processing. Implementations
          <bcp14>MAY</bcp14> choose a different architecture
          provided the normative requirements are met.
        </t>
        <dl newline="false" spacing="normal">
          <dt>Tier1 -- Local Ledger:</dt>
          <dd>
            Per-service, in-process write. Synchronous but
            local--no network hop. Authoritative source of
            truth for that service's phase states.
          </dd>
          <dt>Tier2 -- 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>Tier3 -- Central Ledger:</dt>
          <dd>
            Read-optimized aggregate view across all services.
            Used for audit, incident investigation, and SLA
            reporting. Does not require the same availability or
            latency guarantees as the Local Ledger.
          </dd>
        </dl>
        <t>
          For highly critical services, the intent payload and the
          ledger entry <bcp14>SHOULD</bcp14> be co-located in the
          same durable store so the atomic write required by
          <xref target="ledger-role"/> can be satisfied without a
          distributed transaction. For less critical services, a
          temporary co-located copy is sufficient for Phase1
          and Phase2 lifecycle management, with the ledger
          entry propagated to the Central Ledger via event publish.
        </t>
        <figure>
          <name>Recommended 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>
      </section>

      <section anchor="impl-ledger-pull"
               numbered="true" toc="default">
        <name>On-Demand Pull from Central Ledger</name>
        <t>
          Where a Central Ledger is deployed, its management
          application <bcp14>MAY</bcp14> trigger an on-demand pull
          from any service's Local Ledger. This is intended for
          incident investigation, where the latest data is required
          immediately without waiting for the next scheduled batch
          sync. Access controls for on-demand pull are discussed in
          <xref target="sec-ledger"/>.
        </t>
      </section>

      <section anchor="impl-ledger-modes"
               numbered="true" toc="default">
        <name>Operational Modes</name>
        <t>
          The following table summarizes typical operational modes
          for the recommended architecture:
        </t>
        <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>Retry-inspection query</td>
              <td>Local Ledger</td>
              <td>Client query before retry</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>

    <!-- 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 UUIDv4
          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 Phase2 confirmation.
          Implementations <bcp14>MUST</bcp14> authenticate
          Phase2 confirmation requests using the same
          authentication mechanism applied to Phase1
          requests. The server <bcp14>MUST</bcp14> verify that
          the authenticated identity of the Phase2 requester
          matches that of the Phase1 requester.
        </t>
      </section>

      <section anchor="sec-replay" numbered="true" toc="default">
        <name>Replay Semantics</name>
        <t>
          Replay of a Phase2 confirmation is a normal,
          expected behavior in 2PHP-DTT and is not by itself an
          attack. Phase2 confirmations are idempotent within
          the TTL window (<xref target="idempotency-before"/>) and
          are rejected after TTL expiry
          (<xref target="idempotency-after"/>). This design allows
          legitimate clients to safely re-send Phase2 in the
          face of transient network loss without duplicating
          server-side effects, and bounds the window in which any
          captured Phase2 request remains useful to a party
          that possesses both correlation identifiers.
        </t>
        <t>
          Because a re-sent Phase2 within TTL is honored, an
          unauthenticated party in possession of both correlation
          identifiers could re-drive the server's idempotent
          response. This is a confidentiality-of-identifiers
          concern rather than a duplication concern. Implementations
          <bcp14>MUST</bcp14> authenticate Phase2 requests
          under the same authentication mechanism used for
          Phase1 (see <xref target="sec-spoof"/>) and
          <bcp14>SHOULD</bcp14> pair Phase2 requests with
          short-lived credentials or request signing where the
          deployment threat model warrants it.
        </t>
      </section>

      <section anchor="sec-payload" numbered="true" toc="default">
        <name>Intent Payload Confidentiality</name>
        <t>
          The intent payload persisted during Phase1 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="impl-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" numbered="true" toc="default">
      <name>IANA Considerations</name>

      <section anchor="iana-headers" numbered="true" toc="default">
        <name>HTTP Field Name Registrations</name>
        <t>
          This document requests registration of the following HTTP
          field names in the "Hypertext Transfer Protocol (HTTP) Field
          Name Registry" defined in <xref target="RFC9110"/>,
          Section16.3.1:
        </t>
        <table>
          <thead>
            <tr>
              <th>Field Name</th>
              <th>Status</th>
              <th>Reference</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td><tt>DTT-2PHP-Enabled</tt></td>
              <td>Experimental</td>
              <td>This document</td>
            </tr>
            <tr>
              <td><tt>DTT-2PHP-Client-Correlation-ID</tt></td>
              <td>Experimental</td>
              <td>This document</td>
            </tr>
            <tr>
              <td><tt>DTT-2PHP-Auto-Confirm</tt></td>
              <td>Experimental</td>
              <td>This document</td>
            </tr>
            <tr>
              <td><tt>DTT-2PHP-Requested-TTL</tt></td>
              <td>Experimental</td>
              <td>This document</td>
            </tr>
            <tr>
              <td><tt>DTT-2PHP-Server-Correlation-ID</tt></td>
              <td>Experimental</td>
              <td>This document</td>
            </tr>
            <tr>
              <td><tt>DTT-2PHP-Phase-State</tt></td>
              <td>Experimental</td>
              <td>This document</td>
            </tr>
            <tr>
              <td><tt>DTT-2PHP-TTL</tt></td>
              <td>Experimental</td>
              <td>This document</td>
            </tr>
            <tr>
              <td><tt>DTT-2PHP-PONR-Deadline</tt></td>
              <td>Experimental</td>
              <td>This document</td>
            </tr>
            <tr>
              <td><tt>DTT-2PHP-Resource-ID</tt></td>
              <td>Experimental</td>
              <td>This document</td>
            </tr>
            <tr>
              <td><tt>DTT-2PHP-Message</tt></td>
              <td>Experimental</td>
              <td>This document</td>
            </tr>
          </tbody>
        </table>
      </section>
    </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="RFC7240">
        <front>
          <title>Prefer Header for HTTP</title>
          <author initials="J." surname="Snell"
                  fullname="J. Snell"/>
          <date year="2014" month="June"/>
        </front>
        <seriesInfo name="RFC" value="7240"/>
      </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>