Internet-Draft 2PHP-DTT July 2026
Venkateswaran & Jamwal Expires 22 January 2027 [Page]
Workgroup:
Network Working Group
Internet-Draft:
draft-venkateswaran-jamwal-twophp-01
Published:
Intended Status:
Experimental
Expires:
Authors:
B. Venkateswaran
Salesforce
A. Jamwal
Salesforce

2-Phase HTTP Protocol for Distributed Transaction Tracking (2PHP-DTT)

Abstract

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.

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.

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.

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.

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.

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.

Status of This Memo

This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.

Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.

Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."

This Internet-Draft will expire on 22 January 2027.

Table of Contents

1. Introduction

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.

Existing approaches--including the Saga pattern [SAGA], Transactional Outbox [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 [OTEL] provide execution telemetry but cannot answer whether both parties registered intent before processing commenced.

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.

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.

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.

This gap is specific to synchronous HTTP invocations. Asynchronous integration patterns--202 Accepted responses with Location-based status polling ([RFC9110]), Prefer: respond-async negotiation ([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.

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 MAY carry the same value; nothing in this document precludes composing 2PHP-DTT with existing idempotency, retry, or tracing conventions.

2PHP is designed to be:

2. Conventions and Definitions

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.

2PHP:
The 2-Phase HTTP Protocol, as defined in this document.
Phase 1:
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.
Phase 2:
The Client Confirmation phase, in which the client confirms receipt of the Phase1 acknowledgement, triggering server-side business logic execution.
PONR (Point of No Return):
The moment at which the server begins executing business logic after receiving Phase2 confirmation. Processing cannot be rolled back after this point.
TTL (Time to Live):
The server-defined duration, in milliseconds, within which the client MUST send Phase2 confirmation.
Delta (delta):
A server-internal grace window applied after TTL expiry to handle race conditions at the PONR boundary. Not exposed to the client.
Intent Ledger:
A durable, queryable log of 2PHP transactions, distinct from execution telemetry.
Local Ledger:
A per-service, in-process ledger authoritative for that service's phase states.
Central Ledger:
A read-optimized aggregate view of all local ledgers, populated via background batch sync.
Client Correlation ID:
A client-assigned identifier for a specific request, included in all protocol headers and ledger entries for that interaction.
Server Correlation ID:
A server-assigned identifier for a specific intent registration, returned in the Phase1 response.
Parent Reference ID:
The correlation ID that the immediate upstream caller provided when initiating the call to a downstream service. Used to reconstruct distributed call trees.

3. Problem Statement

3.1. The Request Boundary Gap

Current HTTP REST semantics treat a mutation request as a single atomic operation from the client's perspective. In reality, a server may:

  • Receive and process the request without the client ever confirming receipt of the response.
  • Fail mid-processing with no durable record of the original intent.
  • Return a success response that is lost in transit.

No standard HTTP mechanism exists to confirm that both the client and the server have registered the intent of a request before processing commences.

3.2. Limitations of Existing Telemetry

Modern distributed tracing systems provide visibility into what executed across services, how long operations took, and where failures occurred. However, they cannot answer:

  • Did both client and server register the intent before processing?
  • Was a request abandoned or confirmed?
  • At what phase did a distributed transaction stall?

3.3. Limitations of Existing Patterns

Table 1
Pattern Scope Client Involved? Protocol Level? Intent Ledger?
Two-Phase Commit (2PC) Database No No No
Saga Pattern Application No No No
Transactional Outbox Server-side No No No
Idempotency Keys Application Partial No No
HTTP 100 Continue Protocol Yes Yes No
2PHP (this document) Protocol Yes Yes Yes

4. Protocol Overview

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.

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 ---------- |
  |                                     |
Figure 1: 2PHP Protocol Flow (Synchronous Mode)

The server MUST 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.

5. Protocol Specification

5.1. Opt-In Header

Clients signal 2PHP intent by including the following header on any mutation request:

DTT-2PHP-Enabled: true

Servers that do not recognize this header MUST ignore it and process the request normally, preserving backward compatibility.

5.2. Phase 1 -- Intent Registration

5.2.1. Client Request

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... }

The client MUST include DTT-2PHP-Client-Correlation-ID with a unique identifier for this request. This identifier MUST be used in all subsequent protocol exchanges and ledger entries for this interaction. The server MUST NOT 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.

If a request carries DTT-2PHP-Enabled: true but omits DTT-2PHP-Client-Correlation-ID, the server MUST reject the request with 400 Bad Request and populate DTT-2PHP-Message to identify the missing header. The server MUST NOT synthesize a correlation identifier on the client's behalf. This requirement applies to both Standard 2PHP (Section 5.3) and Auto-Confirm mode (Section 5.5).

The client MAY include DTT-2PHP-Requested-TTL (in milliseconds) to request that the server override its default TTL setting.

5.2.2. Server Response

The server MUST atomically persist the intent payload and an initial ledger record with phase state WAITING_CONFIRM before returning the response.

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>

Response header semantics:

DTT-2PHP-Server-Correlation-ID:
Server-assigned identifier for this intent registration. The client MUST echo this value in the Phase2 confirmation.
DTT-2PHP-TTL:
Milliseconds the server will wait for Phase2 confirmation. Server-defined. The client MUST send Phase2 before this duration elapses. If the client includes DTT-2PHP-Requested-TTL, the server MAY accommodate by increasing the TTL.
DTT-2PHP-PONR-Deadline:
Server-computed absolute deadline in ISO8601 format. The client MUST send Phase2 before this timestamp.
DTT-2PHP-Phase-State:
Current phase state. Value is WAITING_CONFIRM on Phase1 response.

No DTT-2PHP-Resource-ID 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.

5.2.3. Client Ledger Write

After receiving the Phase1 response, the client MUST write a ledger entry to its local ledger. The client writes after the Phase1 response so that both client_correlation_id and server_correlation_id are available at write time. The client's parent_reference_id MUST be set to its own client_correlation_id as the root anchor.

5.3. Phase 2 -- Client Confirmation

5.3.1. Client Request

The client confirms receipt of the Phase1 acknowledgement:

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>

The client MUST send Phase2 before the DTT-2PHP-PONR-Deadline timestamp received in the Phase1 response.

A 2PHP-capable client MUST NOT send a Phase2 confirmation if the Phase1 response omitted the DTT-2PHP-Server-Correlation-ID 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 MUST be treated as a conventional terminal HTTP response and the client MUST NOT issue a Phase2 request against any derived path. This preserves backward compatibility with servers that ignore the DTT-2PHP-Enabled opt-in header (see Section 11).

5.3.2. Server Response

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:

HTTP/1.1 200 OK
DTT-2PHP-Phase-State: COMMITTED
DTT-2PHP-Resource-ID: <resource-identifier>

The server MUST return 200 OK only after business logic has completed and the resource is fully created. The DTT-2PHP-Resource-ID is authoritative--the resource exists at the point this response is returned.

If business logic fails after PONR is crossed, the server MUST return 500 Internal Server Error. If the request is invalid, the server MUST return the appropriate 4xx response. In both cases the client receives a definitive outcome in the Phase2 response--no polling is required.

Asynchronous operation is out of scope for this specification by design, not deferred. Asynchronous HTTP already provides a durable acknowledgement primitive: 202 Accepted with a Location handle for status polling as defined in [RFC9110], optionally negotiated with Prefer: respond-async as defined in [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 SHOULD use those established mechanisms rather than extending 2PHP-DTT.

5.4. TTL Expiry

Any Phase2 confirmation received after TTL expiry MUST be rejected with:

HTTP/1.1 408 Request Timeout
DTT-2PHP-Phase-State: TTL_EXPIRED
DTT-2PHP-Message: Request TTL exceeded. Re-submit as new request.

The server MAY apply an internal grace window (delta) after TTL expiry to handle race conditions at the PONR boundary. This grace window is server-internal and MUST NOT be exposed in protocol headers or communicated to the client.

5.5. Auto-Confirm Mode

5.5.1. Overview

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 (200 OK + DTT-2PHP-Resource-ID) is returned synchronously on that same channel-- behaviorally identical to a standard HTTP request from the client's perspective.

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 (Section 5.3), which records client-side intent by construction.

The client MUST include DTT-2PHP-Client-Correlation-ID on the Phase1 request in Auto-Confirm mode, as in Standard 2PHP (Section 5.2.1). The server MUST NOT 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 MUST be rejected with 400 Bad Request and DTT-2PHP-Message indicating the missing header. The server MUST return DTT-2PHP-Server-Correlation-ID in the response so the client can correlate the ledger record with its own request.

Auto-Confirm mode is valuable for:

  • Low-latency critical paths--the Phase2 round trip cost is eliminated while preserving bilateral ledger participation.
  • Server-side opt-in--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.
  • Idempotent retries by inspection-- because the client supplies the correlation identifier, a client that does not receive the final response can query the ledger by DTT-2PHP-Client-Correlation-ID to determine whether the intent was registered and, if so, its terminal outcome, before issuing any retry.

5.5.2. Activation Header

DTT-2PHP-Auto-Confirm: true
DTT-2PHP-Auto-Confirm:
When true, 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.

5.5.3. Protocol Flow

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     |
  |                                     |
Figure 2: Auto-Confirm Mode

5.5.4. State Machine -- Auto-Confirm Mode

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
Figure 3: Auto-Confirm Phase State Machine

In Auto-Confirm mode, WAITING_CONFIRM is a transient internal state written and immediately superseded by PROCESSING within the same atomic ledger operation. It is never visible to the client on the original channel.

5.5.5. Mode Comparison

Table 2
Mode Phase 2 Required Latency Client 2PHP Support Ledger Participation
Standard 2PHP Yes 2 round trips Full (correlation ID + Phase2) Both sides
Auto-Confirm No 1 round trip Correlation ID only Both sides

6. State Machine

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 )
Figure 4: 2PHP Phase State Machine

7. Phase States

Table 3
State Description
WAITING_CONFIRM Phase1 received and persisted. Awaiting client Phase2 confirmation.
PROCESSING Phase2 confirmed. PONR crossed. Business logic executing (synchronous mode).
COMMITTED Processing complete. Resource created. Terminal success state.
FAILED Processing failed after PONR. Server returned 500 or 4xx. Terminal failure state.
TTL_EXPIRED Client did not confirm within TTL. Pending cleanup.
ABANDONED Cleaned up after TTL + delta grace window. Terminal state.

8. Idempotency and Replay Behavior

8.1. Replay Before TTL

If a client replays Phase2 before TTL expiry, the server MUST return the same response associated with the original DTT-2PHP-Server-Correlation-ID. No duplicate processing occurs.

8.2. Replay After TTL

Any Phase2 confirmation received after TTL expiry MUST be rejected with 408 Request Timeout and DTT-2PHP-Phase-State: TTL_EXPIRED, consistent with Section 5.4. The client MUST re-submit as a new Phase1 request with a new DTT-2PHP-Client-Correlation-ID. A late Phase2 confirmation carrying the same correlation IDs MUST NOT be reused to trigger execution of the persisted intent.

This revision defines a single replay behavior. Earlier revisions described a per-endpoint REUSE 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 Section 5.4 and expanded the replay-attack surface without a durable operational benefit.

9. Intent Ledger

9.1. Overview

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.

9.2. Relationship to Distributed Tracing

The Intent Ledger is complementary to distributed tracing systems such as OpenTelemetry [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.

The Intent Ledger records what was registered as intent 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 DTT-2PHP-Client-Correlation-ID 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 (Section 12.5) rather than a downstream aggregation.

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.

9.3. Normative Role

At each service boundary, the Intent Ledger MUST record a durable entry for every Phase1 receipt and every subsequent phase transition, and that entry MUST be queryable by DTT-2PHP-Client-Correlation-ID and by DTT-2PHP-Server-Correlation-ID before Phase2 is processed. The ledger write and the intent-payload persistence MUST be committed as a single unit of work (see Section 5.2.2).

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 Section 12.5.

9.4. Ledger Schema

Each ledger entry MUST include the following fields:

Table 4
Field Description
client_correlation_id Client-assigned request identifier.
server_correlation_id Server-assigned intent identifier.
service_ledger_id GUID issued to the service at ledger registration.
service_endpoint Target service and resource path.
actor Role of the entry writer: client or server.
source Identity of the participant writing this entry (e.g., client, serviceA, serviceB1).
target The downstream service being called. Populated only when actor = client. MUST be null for actor = server entries.
parent_reference_id The correlation ID that the immediate upstream caller provided when calling this service. Used to reconstruct the full distributed call tree.
phase Current or terminal phase state.
phase_1_timestamp When Phase1 was received (ISO8601).
phase_2_timestamp When Phase2 confirmation was received, if applicable (ISO8601).
ttl_ms Configured TTL for this request, in milliseconds.
outcome Terminal outcome: COMMITTED, ABANDONED, or TTL_EXPIRED.
payload_ref Reference to the persisted payload. Not the payload itself.
sync_timestamp When this entry was synced to the Central Ledger (ISO8601).
transaction_reference Reference to transaction ID (optional).

10. Cross-Service Correlation

10.1. Overview

When a service fans out to multiple downstream services, the Intent Ledger captures every participant's view of every call--both the caller (actor = client) and the callee (actor = server). This produces a unified, traversable ledger that can reconstruct the full distributed call tree without any distributed coordination at the protocol level.

10.2. Propagation Rules

  • Every service MUST write its own ledger entry after each phase transition.
  • The client MUST write its ledger entry after receiving the Phase1 response, so that both client_correlation_id and server_correlation_id are available at write time.
  • parent_reference_id MUST always record what the immediate upstream caller provided as its correlation ID when calling this service-- enabling upward tree traversal.
  • Each call MUST produce exactly two matching ledger rows: one actor = client and one actor = server, linked by the same client_correlation_id + server_correlation_id pair. This confirms dual-side registration--the core guarantee of 2PHP.
  • target MUST be populated only on actor = client rows. It MUST be null on actor = server rows.

10.3. Example Call Chain

Client --> Service A --> Service B1 --> Service C1
                     --> Service B2 --> Service D1
                     --> Service B3 --> Service E1
Figure 5: Fan-Out Call Chain

The complete ledger for this call chain contains 14 entries--two rows (one actor = client, one actor = server) for each of the seven service-boundary interactions. The root client entry uses client-corr-id1 as both its client_correlation_id and its parent_reference_id, establishing the tree anchor. Each subsequent tier sets parent_reference_id to the correlation ID received from its immediate upstream caller.

10.4. Tree Reconstruction Query Patterns

  • Full tree from root: Query parent_reference_id = client-corr-id1 recursively--walks the entire fan-out tree.
  • Everything a service initiated: Query source = serviceA AND actor = client.
  • Confirm dual registration: Match rows by client_correlation_id + server_correlation_id--one actor = client, one actor = server.
  • Find stalled transactions: Query phase = WAITING_CONFIRM or phase = TTL_EXPIRED across any source.

11. Backward Compatibility

2PHP is designed to be fully backward compatible:

12. Implementation Considerations

12.1. Reference Implementation Targets

  • Spring Boot-- OncePerRequestFilter interceptor
  • Express.js--middleware
  • Azure API Management--inbound policy
  • MuleSoft--HTTP request/response interceptor policy

12.2. Durable Storage for Phase 1 Persistence

Implementations SHOULD use one of the following for durable storage of intent payload and ledger records:

  • Relational database with TTL-aware cleanup job.
  • Redis with TTL-based key expiry (plus delta buffer).
  • Distributed queue (e.g., Azure Service Bus, AWS SQS) with visibility timeout.

12.3. TTL Defaults

The following are recommended starting values. Implementations MAY adjust these per endpoint.

Table 5
Scenario TTL delta (server-internal)
Low-latency APIs 5,000 ms 1,000 ms
Standard APIs 30,000 ms 5,000 ms
Long-running operations 120,000 ms 10,000 ms

delta values are server implementation defaults and MUST NOT be exposed in protocol headers.

12.4. Service Ledger Registration

Each service SHOULD register with the Intent Ledger at onboarding and be issued a GUID (service_ledger_id) that is included in all ledger entries for that service. This enables cross-service querying while maintaining service-level isolation.

The normative requirement (Section 9.3) 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 MAY choose a different architecture provided the normative requirements are met.

Tier1 -- Local Ledger:
Per-service, in-process write. Synchronous but local--no network hop. Authoritative source of truth for that service's phase states.
Tier2 -- Batch Sync:
Scheduled background job syncing local ledger entries to the Central Ledger. Frequency configurable per service. Not on the critical path.
Tier3 -- Central Ledger:
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.

For highly critical services, the intent payload and the ledger entry SHOULD be co-located in the same durable store so the atomic write required by Section 9.3 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.

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)
Figure 6: Recommended Ledger Write Flow

12.6. On-Demand Pull from Central Ledger

Where a Central Ledger is deployed, its management application MAY 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 Section 13.5.

12.7. Operational Modes

The following table summarizes typical operational modes for the recommended architecture:

Table 6
Scenario Ledger Used Trigger
Normal operation Local Ledger only Per phase transition
Retry-inspection query Local Ledger Client query before retry
Routine audit Central Ledger Batch sync (scheduled)
Incident investigation Central Ledger On-demand pull from management app
Compliance reporting Central Ledger Batch sync (scheduled)

13. Security Considerations

13.1. Correlation ID Confidentiality

DTT-2PHP-Client-Correlation-ID and DTT-2PHP-Server-Correlation-ID values MUST be treated as opaque identifiers. Implementations SHOULD use UUIDv4 or equivalent cryptographically random values to prevent enumeration or prediction of correlation IDs by unauthorized parties.

13.2. Phase 2 Spoofing

A malicious actor in possession of both DTT-2PHP-Client-Correlation-ID and DTT-2PHP-Server-Correlation-ID could attempt to submit a spoofed Phase2 confirmation. Implementations MUST authenticate Phase2 confirmation requests using the same authentication mechanism applied to Phase1 requests. The server MUST verify that the authenticated identity of the Phase2 requester matches that of the Phase1 requester.

13.3. Replay Semantics

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 (Section 8.1) and are rejected after TTL expiry (Section 8.2). 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.

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 MUST authenticate Phase2 requests under the same authentication mechanism used for Phase1 (see Section 13.2) and SHOULD pair Phase2 requests with short-lived credentials or request signing where the deployment threat model warrants it.

13.4. Intent Payload Confidentiality

The intent payload persisted during Phase1 may contain sensitive data. Implementations MUST ensure that persisted payloads are protected at rest using appropriate encryption mechanisms consistent with the sensitivity of the data.

13.5. Ledger Access Control

The Central Ledger aggregates intent records across all services. Access to the Central Ledger MUST be restricted to authorized audit, operations, and compliance consumers. The on-demand pull mechanism (Section 12.6) MUST be protected by appropriate authentication and authorization controls.

13.6. Transport Security

All 2PHP exchanges MUST be conducted over TLS (HTTPS). Use of 2PHP over unencrypted HTTP is NOT RECOMMENDED.

14. IANA Considerations

14.1. HTTP Field Name Registrations

This document requests registration of the following HTTP field names in the "Hypertext Transfer Protocol (HTTP) Field Name Registry" defined in [RFC9110], Section16.3.1:

Table 7
Field Name Status Reference
DTT-2PHP-Enabled Experimental This document
DTT-2PHP-Client-Correlation-ID Experimental This document
DTT-2PHP-Auto-Confirm Experimental This document
DTT-2PHP-Requested-TTL Experimental This document
DTT-2PHP-Server-Correlation-ID Experimental This document
DTT-2PHP-Phase-State Experimental This document
DTT-2PHP-TTL Experimental This document
DTT-2PHP-PONR-Deadline Experimental This document
DTT-2PHP-Resource-ID Experimental This document
DTT-2PHP-Message Experimental This document

15. Normative References

[RFC2119]
Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, , <https://www.rfc-editor.org/rfc/rfc2119>.
[RFC8174]
Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words", BCP 14, RFC 8174, , <https://www.rfc-editor.org/rfc/rfc8174>.
[RFC9110]
Fielding, R., Nottingham, M., and J. Reschke, "HTTP Semantics", RFC 9110, , <https://www.rfc-editor.org/rfc/rfc9110>.
[RFC7240]
Snell, J., "Prefer Header for HTTP", RFC 7240, , <https://www.rfc-editor.org/rfc/rfc7240>.

16. Informative References

[OTEL]
OpenTelemetry Authors, "OpenTelemetry Specification", https://opentelemetry.io/docs/specs/otel/ , .
[SAGA]
Garcia-Molina, H. and K. Salem, "Sagas", ACM SIGMOD Record, Vol. 16, Issue 3 , .
[OUTBOX]
Richardson, C., "Pattern: Transactional outbox", .

Authors' Addresses

Barathan Venkateswaran
Salesforce
Aditya Jamwal
Salesforce