<?xml version='1.0' encoding='UTF-8'?>
<rfc xmlns:xi="http://www.w3.org/2001/XInclude" category="info" docName="draft-das-composite-execution-finality-01" ipr="trust200902" submissionType="IETF" consensus="false" xml:lang="en" tocInclude="true" tocDepth="3" symRefs="true" sortRefs="true" version="3">
  <front>
    <title abbrev="Composite Execution Finality">Partial Commit Is Not Finality: Composite Candidate Acts Across Multiple Finality Sinks</title>
    <seriesInfo name="Internet-Draft" value="draft-das-composite-execution-finality-01"/>
    <author fullname="Sangam Das" initials="S." surname="Das">
      <organization>Independent</organization>
      <address>
        <postal>
          <city>Balasore</city>
          <region>Odisha</region>
          <country>IN</country>
        </postal>
        <phone>+91-9861363532</phone>
        <email>info@sangamdas.com</email>
      </address>
    </author>
    <date year="2026" month="September" day="16"/>
    <area>Security</area>
    <workgroup>Individual Submission</workgroup>
    <keyword>execution finality</keyword>
    <keyword>composite act</keyword>
    <keyword>multi-sink</keyword>
    <keyword>atomic consume</keyword>
    <keyword>all-or-none</keyword>
    <keyword>execution handle</keyword>
    <keyword>finality receipt</keyword>

    <abstract>
      <t>
        An agent, a payment orchestrator, or a control-plane applicator often intends
        one Candidate Act that would become real only as several consequences: a
        settlement post, a ledger write, a tool invoke, a notification, a radio
        enable.  <xref target="DAS-HANDLE"/> specifies verify, consume, and receipt
        at one Finality Sink.  It does not say what "the act was permitted" means
        when sink S1 has consumed and posted and sink S2 has rejected, timed out,
        or committed a different digest.
      </t>
      <t>
        Existing distributed-transaction tools already address adjacent problems.
        Two-phase commit, XA, sagas, TCC, transactional outbox, and workflow
        engines coordinate steps.  OAuth, WIMSE, RATS, and SCITT still name
        identity, environment, and signed statements.  None of them, by themselves,
        bind N sink-local Execution Handles to one composite Act Digest and require
        that no child consequence become externally effective unless every required
        child reaches a defined terminal state.
      </t>
      <t>
        This document specifies Composite Candidate Acts, child handles, a
        coordinator that may only prepare, a two-phase consume rule, and the
        distinction between prevention (no child commits unless the composite
        closes) and mitigation (compensate after a partial post).  Compensation
        is itself a Candidate Act.  It is not a silent undo and not a license to
        skip verify on the original child.
      </t>
      <t>
        The join is closed by a durable, linearizable Composite Decision Log
        holding one value per composite: empty, COMMIT, or ABORT, written only by
        compare-and-swap so that the first writer wins.  The coordinator MUST
        win the log before sending a decision to any child.  A prepared child
        whose reservation timer expires MUST consult the log: it commits if
        COMMIT is recorded, releases if ABORT is recorded, attempts its own
        compare-and-swap of ABORT if the log is empty, and holds its reservation
        (HOLD) only if the log is unreachable.  No child acts on silence alone.
      </t>
      <t>
        This is not a new consensus protocol and not a claim that XA is obsolete.
        Profiles that cannot obtain prepare-from-every-sink, or cannot reach a
        durable linearizable decision log, MUST NOT claim all-or-none prevention.
      </t>
      <t>
        Three rejections are expected and are treated as design constraints.
        "Saga plus handle is enough" is accepted when each saga step already
        reconstructs the live child, consumes a child handle, and cannot log
        SUCCESS after a required child rejects; this draft then shrinks to
        join fields.  Prepare across N sinks is a latency tax only if
        consume-state is global; it is per child handle, and a no_prepare
        mail or MCP child must use the mitigation profile rather than pretend
        2PC.  Child sinks reconstruct only their own pending effect, not a
        mesh-wide CAD.  Reviewers who would reject on saga, performance, or
        reconstruction grounds are asked to read those sections before
        discarding the document.  Criticism, including "this should stay a
        note in the handle draft," remains invited.
      </t>
    </abstract>
  </front>

  <middle>
    <section anchor="this-version">
      <name>What This Version (-01) Contains</name>
      <t>
        This Internet-Draft is
        <tt>draft-das-composite-execution-finality-01</tt>.
        It specifies how several handle-using sinks join as one
        Composite Candidate Act: child handles, prepare/commit/abort,
        ALL_REQUIRED prevention, and compensation as a new act.
        Single-sink objects remain in <xref target="DAS-HANDLE"/>;
        code points remain in <xref target="DAS-REG"/>.
      </t>
      <section anchor="this-version-includes">
        <name>Contents of -01</name>
        <ul spacing="normal">
          <li>Problem of split reality (one child posts, another rejects) after a locally correct handle.</li>
          <li>Composite CAD, child handles bound to composite_id, coordinator rules.</li>
          <li>Prepare/commit/abort, join rules, and the mitigation profile for no_prepare sinks.</li>
          <li>2PC timeout incompatibility resolved by a compare-and-swap Composite Decision Log: child CAS ABORT on an empty log, HOLD only when the log is unreachable, EF-047 for a discovered split.</li>
          <li>Formal join identities and pointer to the handle-draft hash / TLA+ embedding.</li>
          <li>Anticipated criticisms (saga plus handle, latency, reconstruction) and abstract hints.</li>
          <li>Industrial disclaimer, community questions, and IETF venue mapping.</li>
        </ul>
      </section>
      <section anchor="changes-from-00">
        <name>Changes from -00</name>
        <ul spacing="normal">
          <li>Document name and series info updated to -01.</li>
          <li>Abstract names saga, performance, and reconstruction objections, and states the decision-log rule.</li>
          <li>Added the Composite Decision Log: durable, linearizable, written only by compare-and-swap (first writer wins).  The coordinator wins the log before sending any decision.</li>
          <li>A prepared child whose reservation timer expires consults the log: COMMIT commits, ABORT releases, an empty log allows the child to CAS ABORT, and an unreachable log leads to HOLD (EF-048).  This removes the -00 partial-commit path and prevents indefinite blocking when the coordinator dies before deciding.</li>
          <li>Coordinator abort that loses the CAS to COMMIT re-drives commit instead of aborting.</li>
          <li>Coordinator re-checks EH_C currentness immediately before writing COMMIT.</li>
          <li>After COMMIT is recorded, child commits are retried to a terminal state; only a child that cannot post yields COMPENSATING with EF-047.</li>
          <li>The same rule is applied consistently in the state machine, Phase 2, coordinator rules, timeouts, formal join, PreventionWellFormed, pseudocode, profiles, questions, venues, security, and IANA sections.</li>
          <li>ABORTED includes a deliberate coordinator abort, always via the log.</li>
          <li>Partial commit uses EF-047, not EF-044.</li>
          <li>Security Considerations adds decision-log integrity and availability as trusted computing base, and child-forced abort as a liveness (not safety) attack.</li>
          <li>Family references aligned to current titles, revisions, and Datatracker URLs; handle and registries cited as -01.</li>
          <li>Split(C,t) refined so that the normal in-progress window after COMMIT is Closing(C,t) and COMMIT_DECIDED, not COMPENSATING.</li>
          <li>Child handles bind composite_digest = d(C), a digest over the sorted child list, not only composite_id.</li>
          <li>Added Safety, Closure, and Termination: decision-log axioms, child action invariants, No-Split theorem with proof, Closure theorem with its separate assumptions, termination bounds, the finality point and currentness condition, and compensation identities.</li>
        </ul>
      </section>
    </section>

    <section anchor="intro">
      <name>Introduction</name>

      <section anchor="intro-vulnerability">
        <name>Vulnerability: One Intent, Split Reality</name>
        <t>
          A composite intent C is authorized as a unit: pay vendor X AND write
          the invoice AND notify finance.  If the settlement sink consumes its
          handle and posts, and the notification sink times out, operators
          still say "the agent paid."  The Candidate Act that was reviewed was
          not the world that occurred.  No child token was forged.  The failure
          is split effectuation of one intended act.
        </t>
        <figure anchor="fig-split">
          <name>Child S1 commits; child S2 never becomes effective</name>
          <artwork type="ascii-art">
Intended C = { pay X, book invoice, notify finance }

        +-- S1 SETTLEMENT   consume + post     EFFECTUATED
   C ---+-- S2 CONTROL      timeout            NON_EFFECTIVE
        +-- S3 TOOL         never invoked      NON_EFFECTIVE

Reviewed act:   C
Occurred world: pay X only

Partial commit
      !=
composite finality
          </artwork>
        </figure>
      </section>

      <section anchor="intro-existing">
        <name>Existing Mechanisms Already Coordinate Steps</name>
        <t>
          Two-phase commit and XA serialize prepare/commit across resource
          managers.  Sagas and TCC specify forward actions plus compensations.
          Outbox and workflow engines order side effects.  Those tools should
          be used.  They do not define an Execution Handle per child sink, do
          not reconstruct each live child act, and do not make "compensation"
          a second handled Candidate Act.
        </t>
      </section>

      <section anchor="intro-gap">
        <name>Residual Gap After a Single-Sink Handle</name>
        <t>
          <xref target="DAS-HANDLE"/> closes possession-versus-authority at one
          boundary.  After that draft, each child can be locally correct and
          the composite still wrong.  Registries reserve <tt>COMPOSITE</tt>
          as a class and a sink type <xref target="DAS-REG"/>.  They do not
          specify the protocol.
        </t>
      </section>

      <section anchor="intro-delta">
        <name>What This Document Introduces</name>
        <ul spacing="normal">
          <li>Composite CAD with an ordered, explicit child list and a composite Act Digest.</li>
          <li>One child Execution Handle per required sink, all bound to the same composite id.</li>
          <li>Prepare / commit / abort on consume-state, with fail-closed defaults.</li>
          <li>A coordinator role that cannot substitute for child sinks.</li>
          <li>Compensation as a new Candidate Act, not a hidden reverse of the original handle.</li>
        </ul>
      </section>
    </section>

    <section anchor="conventions">
      <name>Conventions and Requirements Language</name>
      <t>
        The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
        "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and
        "OPTIONAL" in this document are to be interpreted as described in
        BCP 14 <xref target="RFC2119"/> <xref target="RFC8174"/> when, and
        only when, they appear in all capitals.
      </t>
    </section>

    <section anchor="direct-question">
      <name>Direct Question</name>
      <t>
        If C is the act that was authorized, and only a proper subset of C's
        required children have become externally effective, was C effectuated?
      </t>
      <artwork type="ascii-art">
Prevention profile:
    C is EFFECTUATED
        iff every required child is EFFECTUATED
        under handles bound to C
        and no extra child effect is attributed to C.

Otherwise C remains NON_EFFECTIVE
    even if some child already moved money or iron.
      </artwork>
      <t>
        That last sentence is the operational cost.  If a profile cannot
        prevent the child post, it must not advertise all-or-none prevention.
      </t>
    </section>

    <section anchor="motivating-scenario">
      <name>Motivating Scenario: Pay, Book, Notify</name>
      <t>
        An enterprise agent is allowed to settle a vendor invoice only as a
        unit with the ERP booking and an audit mail.  Settlement posts in
        80 ms.  ERP is in a change window.  Mail is a third-party API with
        no prepare.  A coordinator that "retries notify later" has not
        effectuated C.  It has effectuated a different act: pay-without-book.
      </t>
      <figure anchor="fig-pay-book">
        <name>Why retry-later is a different Candidate Act</name>
        <artwork type="ascii-art">
Authorized C17 = pay + book + notify   [all required]

After S1 post, S2 reject:
    world = paid, not booked
    C17   = still NON_EFFECTIVE

C18 = "notify later about a payment already posted"
    is a new CAD
    and needs its own handle
        </artwork>
      </figure>
    </section>

    <section anchor="threat-model">
      <name>Threat Model</name>
      <t>The attacker need not forge handles.  Relevant failures:</t>
      <ul spacing="normal">
        <li>Child reordering so an optional notify is treated as required, or the reverse.</li>
        <li>Coordinator crash after S1 prepare and before S2 prepare.</li>
        <li>S2 commit of a mutated child CAD under the same composite id.</li>
        <li>Calling compensation on S1 without a new handle.</li>
        <li>Declaring success from the coordinator receipt while a required child is still PENDING.</li>
        <li>Using a saga log as proof that no child effect exists.</li>
      </ul>
    </section>

    <section anchor="goals">
      <name>Goals and Non-Goals</name>
      <t>
        Goal: define when a multi-sink Candidate Act may be called EFFECTUATED,
        PREPARED, ABORTED, or COMPENSATING, using the objects in
        <xref target="DAS-HANDLE"/>.
      </t>
      <t>
        Non-goals: a new Paxos/Raft; replacing XA or workflow engines; requiring
        every SaaS to implement prepare; claiming exactly-once delivery across
        the open Internet; automatic legal netting of a posted payment.
      </t>
    </section>

    <section anchor="terminology">
      <name>Terminology</name>
      <t><strong>Composite Candidate Act (C):</strong> A Candidate Act whose intended consequence is the conjunction of two or more child consequences.</t>
      <t><strong>Child Act C_i:</strong> The component that one sink would effectuate.</t>
      <t><strong>Required child:</strong> A child that MUST reach EFFECTUATED for C to reach EFFECTUATED.</t>
      <t><strong>Optional child:</strong> A child that MUST NOT block C if it fails, and MUST NOT be advertised as part of C's authorized unit if it is skipped.  Optional children SHOULD be separate acts.</t>
      <t><strong>Coordinator:</strong> The component that records composite state.  It is not a Finality Sink for a child effect unless it is also that child's sink.</t>
      <t><strong>Prepare:</strong> Child sink has verified the live child act, reserved consume-state, and promises not to post until the Composite Decision Log records COMMIT, and not to release until it records ABORT.</t>
      <t><strong>Composite Decision Log (CDL):</strong> A durable, linearizable register holding one value per composite identifier: empty, <tt>COMMIT</tt>, or <tt>ABORT</tt>.  Its only write operation is <tt>CAS(C, empty -&gt; v)</tt>.  The first successful write is final; every later write fails and returns the recorded value.  The coordinator and every prepared required child can read it and attempt the write.  "Queryable" is not sufficient: an eventually consistent store cannot provide first-writer-wins.</t>
      <t><strong>HOLD:</strong> The child-local state of a prepared child that cannot read or write the CDL.  A child in HOLD produces no external effect and keeps its reservation.</t>
      <t><strong>Compensation act:</strong> A new Candidate Act that would reverse or contain a child effect that already escaped.  It uses a new handle.</t>
    </section>

    <section anchor="objects">
      <name>Composite Objects</name>

      <section anchor="ccad">
        <name>Composite CAD</name>
        <t>
          A composite CAD is a CAD whose <tt>consequence_class</tt> and
          coordinator <tt>sink_type</tt> are <tt>COMPOSITE</tt>
          <xref target="DAS-REG"/>.  It MUST list children explicitly.
        </t>
        <artwork type="ascii-art">
composite_cad = {
  candidate_act_id,
  composite_act_digest,     // d(C), see below
  consequence_class: "COMPOSITE",
  children: [
    { child_id, cad_i, sink_i, required: true|false },
    ...
  ],
  join_rule: "ALL_REQUIRED",
  expires_at,
  generations
}

ALL_REQUIRED:
    every child with required=true must EFFECTUATE
    for C to EFFECTUATE.

QUORUM / BEST_EFFORT:
    MUST NOT be used in a prevention profile
    for FINANCIAL, PHYSICAL, RF, CLINICAL, MODEL_RELEASE.
        </artwork>
        <t>
          The composite Act Digest binds the whole child list, not only an
          identifier.  Children are sorted by <tt>child_id</tt> before
          canonicalization so that the digest does not depend on
          presentation order.
        </t>
        <artwork type="ascii-art">
d(C) = Hash( Canon( join_rule,
         sort_by_id[ (id_i, d(C_i), S_i, required_i) ]
         for all children i ) )
        </artwork>
        <t>
          Hash and Canon are those of <xref target="DAS-HANDLE"/>.  Adding,
          removing, reordering-as-required, or substituting a child changes
          d(C).
        </t>
      </section>

      <section anchor="child-handle">
        <name>Child Handles</name>
        <t>
          Each required child has its own Execution Handle EH_i issued by the
          PED.  EH_i MUST bind:
        </t>
        <ul spacing="normal">
          <li><tt>act_digest</tt> of child CAD_i</li>
          <li><tt>sink_id</tt> of S_i</li>
          <li><tt>composite_id</tt> = C.candidate_act_id</li>
          <li><tt>composite_digest</tt> = d(C), the composite Act Digest</li>
          <li><tt>reuse_policy</tt> compatible with the child class</li>
        </ul>
        <t>
          A child handle MUST NOT be accepted at a sink whose consume would
          not serve that composite_id and composite_digest.  Binding only
          composite_id is insufficient: an attacker who can alter the child
          list while keeping the identifier would otherwise obtain child
          handles that still match.  Presenting EH_i for a standalone
          payment that is not C is EF-023 or EF-040
          <xref target="DAS-REG"/>.
        </t>
      </section>

      <section anchor="composite-handle">
        <name>Composite Handle</name>
        <t>
          The coordinator MAY hold a composite handle EH_C whose digest is
          the composite Act Digest.  EH_C authorizes the coordinator to
          drive prepare/commit/abort.  It does not authorize S_i to post.
        </t>
      </section>
    </section>

    <section anchor="state-machine">
      <name>Composite State Machine</name>
      <figure anchor="fig-states">
        <name>States of C, distinct from child sink states</name>
        <artwork type="ascii-art">
                 issue
                   |
                   v
              NON_EFFECTIVE
                   |
              children verify
                   v
                PREPARED --- CDL CAS ABORT wins ---&gt; ABORTED
                   |       (coordinator or child)
              CDL CAS COMMIT wins
                   v
              COMMIT_DECIDED
                   |
              every required child driven to commit
                   v
              EFFECTUATED

If any required child already posted
and others cannot:
              EFFECTUATED is forbidden
                   |
                   v
              COMPENSATING -- compensation C' --&gt; CONTAINED
                   |
                   +--&gt; UNKNOWN (compensation also split)

Prevention profile forbids entering COMPENSATING
because a required child posted before join.

Child-local, on reservation timer:
  CDL = COMMIT        --&gt; commit        (EFFECTUATED)
  CDL = ABORT         --&gt; release       (RELEASED)
  CDL = empty         --&gt; CAS ABORT
                           won  --&gt; release
                           lost --&gt; commit (COMMIT won)
  CDL unreachable     --&gt; HOLD, no post, EF-048
  HOLD, CDL readable  --&gt; re-evaluate as above
        </artwork>
      </figure>
      <t>
        Child sinks use the handle draft states locally: verify, consume
        (here: reserve / commit / abort), receipt.  C's state is a join of
        those receipts, not a vote by the model that proposed C.
      </t>
    </section>

    <section anchor="protocol">
      <name>Prepare, Commit, Abort</name>

      <section anchor="phase-verify">
        <name>Phase 0: Reconstruct Each Child</name>
        <t>
          Each S_i MUST reconstruct live child CAD_i from the pending local
          effect, not from a coordinator-supplied blob alone
          <xref target="DAS-HANDLE"/>.  Coordinator-supplied CAD_i is a
          hint.  Digest mismatch is EF-023.
        </t>
      </section>

      <section anchor="phase-prepare">
        <name>Phase 1: Prepare</name>
        <t>
          Prepare is consume-state reservation without external effect.
        </t>
        <artwork type="ascii-art">
S_i.prepare(EH_i, live_cad_i):
    verify handle and reconstruct digest
    if fail: receipt REJECTED, do not reserve
    if consume-store down: EF-081, fail closed
    reserve EH_i  (not yet posted)
    return PREPARED

Coordinator:
    if any required child not PREPARED
        before deadline:
            Coordinator.abort(C)   // via the CDL
        </artwork>
        <t>
          A sink that cannot reserve without posting (typical third-party
          mail or card API) MUST be declared <tt>no_prepare=true</tt>.  A
          composite that includes a required no_prepare sink MUST NOT use
          the prevention profile.  It MAY use mitigation: post last, or
          accept COMPENSATING.
        </t>
      </section>

      <section anchor="phase-commit">
        <name>Phase 2: Commit or Abort</name>
        <artwork type="ascii-art">
Coordinator.commit(C):
    require all required children PREPARED
    require EH_C still current          // checked immediately
                                        // before the CAS
    v = CDL.CAS(C, empty -&gt; COMMIT)
    if v == ABORT:                      // a child TTL or an
        release_all_prepared(C)         // earlier abort won
        C = ABORTED
        return
    C = COMMIT_DECIDED                  // point of no return
    for each required S_i:
        r = retry_until_terminal(S_i.commit, EH_i)
        // a PREPARED child promised to post; transient
        // failure is retried, never treated as abort
    if all required EFFECTUATED:
        C = EFFECTUATED
        emit composite receipt
    else:
        // reachable only if a prepared sink cannot post
        // (broken promise, sink destroyed, legal stop)
        C = COMPENSATING                // EF-047

Coordinator.abort(C):
    v = CDL.CAS(C, empty -&gt; ABORT)
    if v == COMMIT:                     // lost to an earlier
        Coordinator.commit_redrive(C)   // COMMIT: do not abort
        return
    for each PREPARED S_i:
        S_i.abort(EH_i)                 // RELEASED, no post
    C = ABORTED
        </artwork>
        <t>
          The composite finality point is the successful <tt>CAS</tt> of
          <tt>COMMIT</tt>, not the first child post.  Child currentness
          (generations, revocation) is evaluated inside each child's
          serialized prepare section <xref target="DAS-HANDLE"/>, and
          <tt>EH_C</tt> currentness is re-checked immediately before the
          CAS.  A revocation that arrives after <tt>COMMIT</tt> is recorded
          applies to an act that is already final; it is handled by a new
          compensation act (<xref target="compensation"/>), not by releasing
          a child that the log says must commit.
        </t>
      </section>
    </section>

    <section anchor="join-rules">
      <name>Join Rules</name>
      <table>
        <name>Join rules and whether prevention may be claimed</name>
        <thead>
          <tr>
            <th>Rule</th>
            <th>C EFFECTUATED when</th>
            <th>Prevention claim</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td>ALL_REQUIRED</td>
            <td>every required child EFFECTUATED</td>
            <td>Yes, if every required sink supports prepare</td>
          </tr>
          <tr>
            <td>ALL_THEN_OPTIONAL</td>
            <td>required set EFFECTUATED; optional attempted</td>
            <td>Only over the required set</td>
          </tr>
          <tr>
            <td>QUORUM(k)</td>
            <td>any k children</td>
            <td>No for high-consequence classes</td>
          </tr>
          <tr>
            <td>BEST_EFFORT</td>
            <td>coordinator tried</td>
            <td>Never</td>
          </tr>
        </tbody>
      </table>
    </section>

    <section anchor="compensation">
      <name>Compensation Is a New Act</name>
      <t>
        If a required child has already posted, C cannot become EFFECTUATED
        and cannot become cleanly ABORTED.  A compensation Candidate Act C'
        MAY be issued: reverse the post, hold the funds, disable the radio,
        retract the tool side effect.
      </t>
      <figure anchor="fig-comp">
        <name>Compensation does not reuse the original child handle</name>
        <artwork type="ascii-art">
EH_pay  CONSUMED + posted
EH_book REJECTED

Forbidden:
    S_pay.undo(EH_pay)     // handle already consumed

Required:
    C' = "return funds for composite C17"
    new CAD', new EH'
    S_pay.verify/consume(EH')
    C  = COMPENSATING then CONTAINED
         only if C' EFFECTUATED
        </artwork>
      </figure>
      <t>
        Success of C' is not success of C.  Receipts MUST keep the two
        identifiers distinct.  Failure of C' leaves C in UNKNOWN containment
        and MUST be visible.
      </t>
    </section>

    <section anchor="coordinator">
      <name>Coordinator Rules</name>
      <ul spacing="normal">
        <li>The coordinator MUST NOT post a child effect itself unless it is that child's sink.</li>
        <li>The coordinator MUST win <tt>CDL.CAS(C, empty -&gt; COMMIT)</tt> or <tt>CDL.CAS(C, empty -&gt; ABORT)</tt> before sending that decision to any child.  Prepare messages are sent before any decision exists.</li>
        <li>After crash, the coordinator MUST read the CDL first, then query child receipts; it MUST NOT assume commit or abort.  If the CDL records <tt>COMMIT</tt>, it MUST re-drive commit to every required child not yet EFFECTUATED.  If the CDL is empty, it MAY write <tt>ABORT</tt>.</li>
        <li>A coordinator whose abort CAS loses to <tt>COMMIT</tt> MUST re-drive commit and MUST NOT send abort.</li>
        <li>A coordinator receipt without child receipts is not evidence that C effectuated.</li>
        <li>The coordinator SHOULD be identifiable as sink type COMPOSITE in the registry sense only: it joins, it does not replace SETTLEMENT or ACTUATION.</li>
      </ul>
    </section>

    <section anchor="timeouts">
      <name>Timeouts, Crash, and Child CAS Abort</name>
      <t>
        Classic two-phase commit is blocking.  The following two rules
        cannot both hold in a prevention profile:
      </t>
      <ol spacing="normal">
        <li>A PREPARED child whose reserve TTL expires aborts locally without asking anyone.</li>
        <li>After the coordinator has decided COMMIT, no required child may abort.</li>
      </ol>
      <t>
        If the coordinator writes COMMIT, S1 posts, and then dies before
        S2 hears COMMIT, S2's local TTL abort produces the partial
        commit this draft called unreachable.  That path is reachable
        unless children consult a durable decision before acting on a timer.
      </t>
      <t>
        A log that children can only query is not enough on its own.  If the
        coordinator dies before deciding, every child reads an empty log and
        cannot safely release, because the coordinator might still write
        COMMIT.  Every child would then block.  The CDL removes that case:
        a child may itself write ABORT into an empty log, and because the
        write is compare-and-swap, the coordinator can no longer write COMMIT
        afterwards.  HOLD is reserved for the case where the log itself is
        unreachable.
      </t>
      <artwork type="ascii-art">
Forbidden pair (prevention profile):
    coordinator.decision == COMMIT
        AND
    some required child locally ABORTS on TTL

Required instead:
    coordinator wins CDL.CAS before sending
        COMMIT or ABORT to any child
    child reservation timer fires
        -&gt; read CDL[C]
        -&gt; COMMIT:      commit locally
        -&gt; ABORT:       release (RELEASED)
        -&gt; empty:       CAS(C, empty -&gt; ABORT)
                          won:  release
                          lost: commit
        -&gt; unreachable: HOLD, no post, EF-048
                        retry the log; alert operator

Dead coordinator, no decision:
    first child timer writes ABORT
    composite ABORTED, no child posts

Dead coordinator, COMMIT recorded:
    every child timer reads COMMIT
    composite closes

If the deployment cannot provide a durable,
linearizable CDL:
    it MUST NOT claim PreventionWellFormed
    it MAY use hold-until-operator
    it MUST treat a discovered split as
        C = COMPENSATING, reason EF-047
      </artwork>
      <t>
        Automatic commit on coordinator silence remains forbidden.
        Automatic release on a child timer is forbidden unless the child's
        own CAS of ABORT succeeds or the log already records ABORT.  Timers
        trigger a decision attempt; they never substitute for a decision.
        An operator resolution procedure, if a profile defines one, MUST
        itself write through <tt>CDL.CAS</tt> so that a manual decision
        cannot contradict an automatic one.  A child in HOLD SHOULD raise an
        operator alert; for sinks where a long-held reservation is itself
        harmful (for example a held payment authorization), the profile
        SHOULD document the maximum acceptable HOLD duration and the
        escalation path.
      </t>
    </section>

    <section anchor="formal">
      <name>Formal Join</name>
      <t>
        Let C have required index set R.  Child i has handle H_i, sink S_i,
        live act C_i(t), and local state q_i(t).  The identities below are
        the same join as the prepare/commit pseudocode.  Child digests use
        the hash function of <xref target="DAS-HANDLE"/>.  A TLA+ model of
        this join is well-formed only if COMMIT followed by child TTL
        cannot reach Split under PreventionWellFormed.
      </t>
      <artwork type="ascii-art">
Posted(C,t)   = { i in R | q_i(t) = EFFECTUATED }

Effect(C,t)   iff  Posted(C,t) = R
              and  forall i in R. d(C_i(t)) = H_i.d
              and  forall i in R. H_i.composite_id = C.id
              and  forall i in R. H_i.composite_digest = d(C)

Abort(C,t)    iff  Posted(C,t) = empty
              and  CDL(C) = ABORT

Split(C,t)    iff  Posted(C,t) nonempty and Posted(C,t) != R
              and  ( CDL(C) != COMMIT
                     or exists i in R. CannotPost(i,t) )
Split(C,t)    =&gt;   COMPENSATING with EF-047

Closing(C,t)  iff  CDL(C) = COMMIT
              and  Posted(C,t) nonempty and Posted(C,t) != R
              and  forall i in R. not CannotPost(i,t)
Closing(C,t)  =&gt;   COMMIT_DECIDED (transient, not a split)

reject, deadline, operator abort, or child timer
    =&gt; attempt CDL.CAS(C, empty -&gt; ABORT)
       (never a direct transition to Abort)

CDL(C) = COMMIT =&gt; no PREPARED child is released
Timer:  COMMIT -&gt; commit
        ABORT  -&gt; release
        empty  -&gt; CAS ABORT (won: release; lost: commit)
        unreachable -&gt; HOLD
      </artwork>
      <artwork type="ascii-art">
Required(C)     = { i | child_i.required }
Prepared(C)     = { i in Required(C) | S_i.state == PREPARED }
Posted(C)       = { i in Required(C) | S_i.state == EFFECTUATED }
Rejected(C)     = { i in Required(C) | S_i.state == REJECTED }

C.EFFECTUATED   iff Posted(C) == Required(C)
                 AND no child digest diverged
                 AND join_rule == ALL_REQUIRED
                     or ALL_THEN_OPTIONAL

C.ABORTED       iff Posted(C) empty
                 AND CDL(C) == ABORT

C.COMMIT_DECIDED iff CDL(C) == COMMIT
                 AND Posted(C) != Required(C)
                 // transient: every required child
                 // is being driven to EFFECTUATED

C.COMPENSATING  iff Posted(C) nonempty
                 AND Posted(C) != Required(C)
                 AND some required child can no longer post

PreventionWellFormed(C) iff
    join_rule == ALL_REQUIRED
AND every required S_i supports prepare
AND no required S_i has no_prepare
AND a durable, linearizable CDL is reachable
    by the coordinator and every required child
AND no child releases or posts on a timer
    except as the CDL dictates

PreventionWellFormed(C) == false
    ==&gt; MUST NOT claim all-or-none prevention
      </artwork>
    </section>

    <section anchor="safety-closure">
      <name>Safety, Closure, and Termination</name>
      <t>
        This section states the properties the join claims and the
        assumptions each property needs.  Safety (no split) depends only on
        the Composite Decision Log and child behaviour.  Closure (the
        composite eventually ends) additionally depends on sinks keeping the
        promise made at prepare and on fair re-drive.  The two claims are
        kept separate so that a deployment can state which one it provides.
      </t>

      <section anchor="cdl-axioms">
        <name>Decision-Log Axioms</name>
        <t>
          Notation: bot is the empty value; CDL(C,t) is the value recorded
          for C at time t; Read_x(C,t) is a read by party x.
        </t>
        <artwork type="ascii-art">
A1 Stability:
     CDL(C,t) != bot
       =&gt;  forall t' &gt;= t.  CDL(C,t') = CDL(C,t)

A2 Single value (linearizability):
     Read_x(C,t) = v  and  Read_y(C,t') = v'
     and  v != bot  and  v' != bot
       =&gt;  v = v'

A3 First writer wins:
     CAS_x(C, bot -&gt; v) succeeds at t
       =&gt;  CDL(C,t) = v  and  every later CAS fails
           and returns v
        </artwork>
      </section>

      <section anchor="child-invariants">
        <name>Child Action Invariants</name>
        <artwork type="ascii-art">
I1  Post_i(t)
      =&gt;  exists t0 &lt;= t.  Read_i(C,t0) = COMMIT

I2  Release_i(t)
      =&gt;  exists t0 &lt;= t.  Read_i(C,t0) = ABORT
                        or WonCAS_i(C, ABORT, t0)

I3  Post_i(t)
      =&gt;  Prepared_i(t_p)  with  t_p &lt; t_dec
    where t_dec is the time of the successful
    CAS(C, bot -&gt; COMMIT)

I4  Hold_i(t)
      =&gt;  not Post_i(t)  and  not Release_i(t)
        </artwork>
      </section>

      <section anchor="no-split">
        <name>No-Split Theorem</name>
        <artwork type="ascii-art">
Theorem 1 (Safety).  Under A1, A2, A3, I1, I2:

    not exists i, j in R.
        Posted_i  and  Released_j

Proof.
    Posted_i    =&gt;  some party read COMMIT      (I1)
    Released_j  =&gt;  some party read ABORT
                    or won CAS ABORT            (I2, A3)
    A1 and A2 forbid the log holding both
    COMMIT and ABORT for C.
    Hence no execution has both.               QED
        </artwork>
        <t>
          Theorem 1 does not require the coordinator to be correct, alive,
          or honest.  It requires only that the log satisfies A1 to A3 and
          that every required child obeys I1 and I2.
        </t>
      </section>

      <section anchor="closure">
        <name>Closure Theorem</name>
        <artwork type="ascii-art">
A4 Promise kept:
     CDL(C) = COMMIT  =&gt;  forall i in R.  not CannotPost(i)

A5 Weak fairness:
     a party that can re-drive commit, or retry a
     reachable log, eventually does so

A6 Log eventually reachable:
     &lt;&gt;[] Reachable(CDL)

Theorem 2 (Closure).  Under Theorem 1, A4, A5, A6:

    &lt;&gt;( Effect(C)  or  Abort(C) )

Sketch.
    If CDL(C) stays bot, the first expiring child
    timer writes ABORT once the log is reachable
    (A5, A6), so Abort(C).
    If CDL(C) = COMMIT, every required child is
    driven to Post (A4, A5), so Effect(C).
    By A1 exactly one branch applies.           QED
        </artwork>
        <t>
          If A4 fails for some child (a prepared sink is destroyed, or a
          legal stop prevents posting), Theorem 1 still holds for all other
          children but the composite enters COMPENSATING with EF-047.  That
          case is a broken prepare promise, not a protocol split.
        </t>
      </section>

      <section anchor="termination-bounds">
        <name>Termination Bounds</name>
        <artwork type="ascii-art">
Coordinator dead before deciding, log reachable:
    T_abort  &lt;=  min_i TTL_i  +  delta_cas

HOLD duration for child i:
    T_hold(i)  &lt;=  T_unreach(CDL)  +  TTL_i  +  delta_cas

Commit completion after t_dec, with at most k
retries per child and per-attempt time delta_post:
    T_close  &lt;=  t_dec  +  |R| * k * delta_post
             (sequential; parallel re-drive gives
              max_i instead of the sum)
        </artwork>
        <t>
          T_unreach(CDL) is outside the protocol.  It is why log
          availability belongs to the trusted computing base and why a
          profile SHOULD state a maximum acceptable HOLD duration.
        </t>
      </section>

      <section anchor="finality-point">
        <name>Finality Point and Currentness</name>
        <artwork type="ascii-art">
t_prep_i  = time child i entered PREPARED
t_dec     = time of successful CAS(C, bot -&gt; COMMIT)

Effect(C)
    =&gt;  forall i in R.  Current(H_i.g, t_prep_i)
    and Current(EH_C.g, t_dec)
    and t_dec &gt;= max_i t_prep_i

Revoked(H_i, t)  with  t &gt; t_dec
    =&gt;  handled by a compensation act C'
        not by releasing child i
        </artwork>
      </section>

      <section anchor="compensation-math">
        <name>Compensation Is Distinct from C</name>
        <artwork type="ascii-art">
Effect(C')  does not imply  Effect(C)

Contained(C)  iff  Split(C)
              and  Effect(C')
              and  C'.parent = C.id
              and  C'.handle != any H_i

Failed containment:
    Split(C) and not Effect(C')  =&gt;  UNKNOWN
        </artwork>
      </section>
    </section>

    <section anchor="pseudocode">
      <name>Illustrative Pseudocode</name>
      <artwork type="ascii-art">
function effectuate_composite(C, handles, cdl):
    if not PreventionWellFormed(C):
        return mitigation_mode(C, handles)   # different claim

    prepared = []
    for child in required(C):
        live = child.sink.reconstruct()
        r = child.sink.prepare(handles[child], live, C.id)
        if r.decision != "PREPARED":
            return abort_via_log(C, prepared, cdl, r.reason_code)
        prepared.append(child)

    if not ehc_current(C):                   # re-check before CAS
        return abort_via_log(C, prepared, cdl, "EF-041")

    if cdl.cas(C.id, EMPTY, COMMIT) == ABORT:
        release_all(prepared)                # a child timer won
        return receipt(C, "ABORTED", "EF-048")

    for child in required(C):                # point of no return
        r = retry_until_terminal(child.sink.commit, handles[child])
        if r.decision != "EFFECTUATED":
            return receipt(C, "COMPENSATING", "EF-047")

    return receipt(C, "EFFECTUATED", consume_outcome="CONSUMED")


function abort_via_log(C, prepared, cdl, code):
    if cdl.cas(C.id, EMPTY, ABORT) == COMMIT:
        return redrive_commit(C)             # too late to abort
    release_all(prepared)
    return receipt(C, "ABORTED", code)


function child_on_reservation_timer(child, C_id, cdl):
    try:
        v = cdl.read(C_id)
        if v == EMPTY:
            v = cdl.cas(C_id, EMPTY, ABORT)  # returns final value
    except Unreachable:
        child.state = HOLD                   # no post, keep reserve
        alert_operator(C_id)
        return report("EF-048")
    if v == ABORT:
        child.release()                      # RELEASED
    else:
        child.commit()                       # COMMIT is final


function compensate(C, child_posted):
    C2 = new_cad(reverse_of(child_posted), parent=C.id)
    eh2 = PED.issue(C2)
    return child_posted.sink.verify_consume(eh2)
      </artwork>
    </section>

    <section anchor="composition">
      <name>Composition with Existing Tools</name>
      <table>
        <name>What existing coordinators supply versus what is still required</name>
        <thead>
          <tr>
            <th>Existing tool</th>
            <th>Supplies</th>
            <th>Still required</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td>XA / 2PC</td>
            <td>Prepare/commit wire to RMs</td>
            <td>Child Act Digest + handle bind</td>
          </tr>
          <tr>
            <td>Saga / TCC</td>
            <td>Forward + compensate graph</td>
            <td>Compensation as new handle; no silent undo</td>
          </tr>
          <tr>
            <td>Outbox / workflow</td>
            <td>Ordered jobs, retries</td>
            <td>Retries are new attempts, not C success</td>
          </tr>
          <tr>
            <td>SCITT</td>
            <td>Logged composite receipt</td>
            <td>Log is not child commit</td>
          </tr>
          <tr>
            <td>Single-sink handle</td>
            <td>Local authority</td>
            <td>This join</td>
          </tr>
        </tbody>
      </table>
    </section>

    <section anchor="profiles">
      <name>Profiles</name>
      <t><tt>ef-composite-strict</tt>: ALL_REQUIRED, every required sink supports prepare, a durable linearizable Composite Decision Log written only by compare-and-swap, child timers act only through that log (CAS ABORT if empty, HOLD if unreachable), compensation is a new act, coordinator receipt insufficient.</t>
      <t><tt>ef-composite-mitigation</tt>: at least one required sink is no_prepare, or no durable linearizable decision log is available; document the escaped-effect window; MUST NOT claim prevention.</t>
    </section>

    <section anchor="anticipated-criticisms">
      <name>Anticipated Criticisms</name>
      <t>
        The invitation in the abstract to treat "saga plus handle is
        enough" as a successful review outcome is deliberate.  The same
        is true of the performance and reconstruction objections.  This
        section states where those objections are accepted.
      </t>

      <section anchor="crit-saga-enough">
        <name>Objection: Saga Plus Handle Is Enough</name>
        <t>
          If each saga step is already a handle-using sink — reconstruct
          live child, prepare or consume, fail closed — then this draft
          is a join profile on top of that saga, not a replacement for
          it.  XA resource managers that expose prepare, and PSP
          authorize/capture pairs, are the same pattern under other
          names.
        </t>
        <t>
          The residual cases this draft still names:
        </t>
        <ul spacing="normal">
          <li>the saga logs SUCCESS after a required child posts and another required child rejects;</li>
          <li>compensation reuses the original child handle instead of a new reverse act;</li>
          <li>a no_prepare child (mail, many MCP tools) is treated as if 2PC applied;</li>
          <li>the coordinator receipt is taken as proof that settlement occurred.</li>
        </ul>
        <artwork type="ascii-art">
Accepted:
    saga / XA / workflow
        AS coordinator
    child Execution Handle
        AS per-sink authority

Not accepted as prevention:
    saga success
        without child PREPARED/EFFECTUATED receipts
    compensate(original_token)
        after the original handle is CONSUMED
    BEST_EFFORT notify
        advertised as C EFFECTUATED
        </artwork>
        <t>
          If reviewers produce a saga profile that already forbids those
          four cases, this document should shrink to that profile plus
          the composite CAD fields.  That outcome is welcome.
        </t>
      </section>

      <section anchor="crit-perf-join">
        <name>Objection: Prepare Across N Sinks Is a Latency Tax</name>
        <t>
          ALL_REQUIRED prepare adds one reservation round to every
          required child before any child posts.  On a payment rail
          that is already authorize-then-capture, that round already
          exists.  On a three-sink agent path where mail cannot
          prepare, the honest profile is mitigation: do not claim
          all-or-none, or split notify into a later act.
        </t>
        <t>
          Consume-state remains per child handle, not one lock for the
          whole composite.  The coordinator persists join state; it
          does not serialize unrelated composites.  Automatic commit
          on coordinator silence is still forbidden in the strict
          profile, because that is how partial posts happen.
        </t>
      </section>

      <section anchor="crit-reconstruct-child">
        <name>Objection: Each Child Cannot Reconstruct a Unified CAD</name>
        <t>
          Children do not reconstruct the composite CAD.  Each sink
          reconstructs only its child: the payment it would post, the
          tool it would invoke, the row it would write.  The
          coordinator compares child receipts and the composite digest
          over the declared child list.  A mesh hop that is not a
          child sink does not reconstruct anything.
        </t>
        <t>
          If a child sink cannot observe the fields that change its
          own effect, that child cannot be in a prevention-profile
          composite.  It can be an optional later act.  Hiding that
          limitation inside a caller-supplied composite blob is how
          split reality occurs.
        </t>
      </section>

      <section anchor="crit-sdk-composite">
        <name>What Would Settle the Argument</name>
        <t>
          A minimal demonstration is more useful than another join
          diagram: one MCP (or payment) middleware that consumes a
          child handle, one second sink that can prepare, and a
          coordinator that aborts when the second child rejects —
          including the case where a prompt-injected destination
          changes only one child's digest.  This document does not
          ship that code.  An implementation that does is invited.
        </t>
      </section>
    </section>

    <section anchor="industry-relevance">
      <name>Industrial Relevance and Public-Roadmap Alignment</name>
      <t>
        Agent products already chain tools: pay, ticket, mail, CRM write.
        Cloud workflow services already run sagas.  PSPs already split
        authorize and capture.  Those roadmaps are complementary.  This
        document does not assert that any named orchestrator is unsafe,
        incomplete, or required to implement prepare.
      </t>
      <artwork type="ascii-art">
PSP authorize/capture   ~  prepare/commit at SETTLEMENT
Cloud workflow / saga   ~  coordinator + compensation graph
MCP tool chain          ~  children; often no_prepare
Agent runtime           ~  proposer of C, not the join
      </artwork>
      <t>
        A PSP capture API that already holds an authorization and voids it
        on timeout is already a prepare-capable SETTLEMENT sink under
        another name.  An MCP server that cannot reserve a Gmail send is a
        no_prepare TOOL_DISPATCH sink.  Putting both in one "strict"
        composite would be a mis-profile, not a vendor defect.
      </t>
      <t>
        Named companies are not claimed as reviewers, implementers, or
        endorsers.  Corrections and requests to remove a framing are
        invited.  Silence is not agreement.
      </t>
    </section>

    <section anchor="questions-community">
      <name>Questions to the IETF Community</name>
      <ol spacing="normal">
        <li>Is composite finality a separate document, or a section of the handle draft?</li>
        <li>Is ALL_REQUIRED the only join rule worth specifying?</li>
        <li>Must every prevention-profile child support prepare, or is "post last no_prepare sink" an acceptable prevention trick?</li>
        <li>Is child compare-and-swap of ABORT into an empty decision log the right reservation-timer default, with HOLD only when the log is unreachable?</li>
        <li>For sinks where a held reservation is itself harmful, should the profile define a maximum HOLD duration that ends in an operator decision written through the log?</li>
        <li>Should the Composite Decision Log be a named wire object with a media type, or remain an abstract linearizable register?</li>
        <li>Is compensation in scope here or a later draft?</li>
        <li>How should composite receipts nest child receipts without becoming a new transparency protocol?</li>
        <li>What prior art (XA, saga, TCC, TN, workflow) already closes this if child handles are added as resources?</li>
        <li>Should optional children be forbidden in v1 so that notify-later is always a new act?</li>
        <li>Which venue: DISPATCH, then none until handle lands?</li>
        <li>If a saga profile already forbids success-without-child-receipts and silent reuse of a consumed handle, should this draft shrink to CAD fields only?</li>
        <li>Is authorize/capture on existing rails accepted as prepare/commit, or must the wire names change?</li>
      </ol>
    </section>

    <section anchor="ietf-venues">
      <name>Potential IETF Discussion Venues</name>
      <t>This document does not claim that any named group should adopt the work.</t>
      <section anchor="venue-dispatch">
        <name>DISPATCH</name>
        <t>Natural first stop: the work spans payments, HTTP APIs, and agents.</t>
      </section>
      <section anchor="venue-oauth">
        <name>OAuth</name>
        <t>Relevant only if child handles are token profiles.  OAuth should not own actuation join.</t>
      </section>
      <section anchor="venue-wimse">
        <name>WIMSE</name>
        <t>Relevant if child actors are workloads on different hops.  Workload identity is not composite join.</t>
      </section>
      <section anchor="venue-rats">
        <name>RATS</name>
        <t>Relevant if a child CAD includes environment currentness.  Attestation is not prepare.</t>
      </section>
      <section anchor="venue-httpapi">
        <name>HTTPAPI</name>
        <t>Relevant to coordinator HTTP resources and problem+json mapping of EF-047 / COMPENSATING and EF-048 / HOLD.</t>
      </section>
      <section anchor="venue-scitt">
        <name>SCITT</name>
        <t>Relevant to logging composite receipts.  Not the commit protocol.</t>
      </section>
      <section anchor="venue-saag">
        <name>SAAG</name>
        <t>Useful for whether all-or-none across administrative domains is in scope for the IETF at all.</t>
      </section>
      <section anchor="venue-no-home">
        <name>No Presumed Home</name>
        <t>Individual -00.  No IANA request in this revision beyond codes already reserved in <xref target="DAS-REG"/>.</t>
      </section>
    </section>

    <section anchor="related-drafts">
      <name>Relationship to Other Execution-Finality Internet-Drafts</name>
      <t>
        This draft sits above the handle and the registries.  It does not
        replace predicate or domain drafts.
      </t>
      <artwork type="ascii-art">
DAS-PROTOCOL      architecture
DAS-HANDLE        authority at ONE sink
DAS-REG           COMPOSITE class/type, EF-047
                  PARTIAL_COMMIT, EF-048
                  DECISION_LOG_UNREACHABLE
this document     join of N handle-using sinks
DAS-PATH          every child path must still be covered
DAS-STATE         generations still current at commit
DAS-REVOCATION    a child handle can be withdrawn before commit
DAS-JURISDICTION  each child JEC may differ; join must see that
DAS-PURPOSE       live purpose of C and of each child
DAS-AGENTIC       tool chain is often the composite proposer

Still not this draft:
    delegation warrant
    break-glass override
      </artwork>
      <t>
        See <xref target="DAS-HANDLE"/>, <xref target="DAS-REG"/>,
        <xref target="DAS-PROTOCOL"/>, <xref target="DAS-PATH"/>,
        <xref target="DAS-STATE"/>, <xref target="DAS-REVOCATION"/>,
        <xref target="DAS-JURISDICTION"/>, <xref target="DAS-PURPOSE"/>,
        and <xref target="DAS-AGENTIC"/>.
      </t>
    </section>

    <section anchor="security">
      <name>Security Considerations</name>
      <t>
        Coordinator compromise is not child-sink compromise.  A malicious
        or failed coordinator can withhold a decision, but it cannot make a
        child post without a recorded <tt>COMMIT</tt>, and it cannot hold
        reservations indefinitely while the log is reachable, because any
        prepared child can write <tt>ABORT</tt> into an empty log.
      </t>
      <t>
        Child sinks MUST NOT release a PREPARED reservation on a timer when
        <tt>COMMIT</tt> may already be recorded.  They MUST consult the log:
        <tt>COMMIT</tt> commits, <tt>ABORT</tt> releases, an empty log allows
        a child CAS of <tt>ABORT</tt>, and an unreachable log leads to HOLD.
      </t>
      <t>
        The integrity and availability of the Composite Decision Log are part
        of the trusted computing base for the prevention claim.  An attacker
        who can rewrite a recorded value, or who can make two readers observe
        different values, can split the composite.  An attacker who can make
        the log unreachable can keep reservations in HOLD.  Profiles SHOULD
        replicate the log with a consensus protocol and SHOULD authenticate
        writers.
      </t>
      <t>
        Child CAS of <tt>ABORT</tt> means a compromised or misconfigured
        required child can force a composite to abort by writing
        <tt>ABORT</tt> early.  That is a liveness attack, not a safety break:
        no child posts, and no split occurs.  Profiles MAY restrict early
        child writes to children whose reservation timer has actually expired
        and SHOULD log the writer identity of every CAS.
      </t>
      <t>
        Treating a coordinator signature as a settlement receipt recreates
        bearer authority at the wrong layer.  Quorum join on financial or
        physical children is a safety bug, not an availability feature.
      </t>
    </section>

    <section anchor="privacy">
      <name>Privacy Considerations</name>
      <t>
        A composite CAD lists every child destination.  That list can reveal
        a payment, a patient-adjacent system, and a mail target together.
        Coordinators SHOULD store child digests and sink ids, and SHOULD
        omit raw arguments from composite receipts sent off-path.
      </t>
    </section>

    <section anchor="iana">
      <name>IANA Considerations</name>
      <t>
        This version does not request IANA action.  It uses
        <tt>COMPOSITE</tt>, <tt>EF-047</tt> PARTIAL_COMMIT, and the
        composite states proposed in <xref target="DAS-REG"/>.  It also
        uses <tt>EF-048</tt> DECISION_LOG_UNREACHABLE, the
        <tt>COMMIT_DECIDED</tt> composite state, the <tt>HOLD</tt> child
        decision, and the <tt>ef-composite-strict</tt> and
        <tt>ef-composite-mitigation</tt> profile identifiers, which are
        expected to be added to <xref target="DAS-REG"/> in its next
        revision.
      </t>
    </section>

    <section anchor="review-request">
      <name>Criticism, Corrections, and Review Invited</name>
      <t>
        The most useful criticism is that XA plus per-resource handles
        already yields this join, or that mitigation sagas are the only
        honest Internet profile.  Either result should shrink the draft.
        Named-product corrections are welcome.  Silence is not endorsement.
      </t>
    </section>

    <section anchor="conclusion">
      <name>Conclusion</name>
      <t>
        Local handle correctness at each sink does not make a multi-sink
        intent final.  C is effectuated only when every required child is
        effectuated under handles bound to C.  If a required child cannot
        prepare, the honest claim is mitigation plus a later compensation
        act — not all-or-none prevention.
      </t>
      <artwork type="ascii-art">
CHILD POST
      !=
COMPOSITE FINALITY

SAGA COMPENSATION
      !=
THE ORIGINAL ACT SUCCEEDED

PREPARE ALL REQUIRED
  or
DO NOT CLAIM PREVENTION
      </artwork>
    </section>
  </middle>

  <back>
    <references>
      <name>Normative References</name>
      <reference anchor="RFC2119" target="https://www.rfc-editor.org/rfc/rfc2119">
        <front>
          <title>Key words for use in RFCs to Indicate Requirement Levels</title>
          <author fullname="Scott Bradner" initials="S." surname="Bradner"/>
          <date year="1997" month="March"/>
        </front>
        <seriesInfo name="BCP" value="14"/>
      </reference>
      <reference anchor="RFC8174" target="https://www.rfc-editor.org/rfc/rfc8174">
        <front>
          <title>Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words</title>
          <author fullname="Barry Leiba" initials="B." surname="Leiba"/>
          <date year="2017" month="May"/>
        </front>
        <seriesInfo name="BCP" value="14"/>
      </reference>
    </references>
    <references>
      <name>Informative References</name>
      <reference anchor="DAS-HANDLE" target="https://datatracker.ietf.org/doc/draft-das-execution-handle/">
        <front>
          <title>Possession Is Not Authority: Execution Handle, Sink Verification, Atomic Consumption, and Finality Receipt</title>
          <author fullname="Sangam Das" initials="S." surname="Das"/>
          <date year="2026" month="September"/>
        </front>
        <seriesInfo name="Internet-Draft" value="draft-das-execution-handle-01"/>
      </reference>
      <reference anchor="DAS-REG" target="https://datatracker.ietf.org/doc/draft-das-ef-registries/">
        <front>
          <title>Illustrative Codes Are Not a Namespace: Registries for Execution-Finality Objects</title>
          <author fullname="Sangam Das" initials="S." surname="Das"/>
          <date year="2026" month="September"/>
        </front>
        <seriesInfo name="Internet-Draft" value="draft-das-ef-registries-01"/>
      </reference>

      <reference anchor="DAS-PROTOCOL" target="https://datatracker.ietf.org/doc/draft-das-execution-finality-protocol-layer/">
        <front>
          <title>The Missing Protocol Layer for the Agentic Internet: Computation Is Not Authority</title>
          <author fullname="Sangam Das" initials="S." surname="Das"/>
          <date year="2026" month="September"/>
        </front>
        <seriesInfo name="Internet-Draft" value="draft-das-execution-finality-protocol-layer-01"/>
      </reference>

      <reference anchor="DAS-PATH" target="https://datatracker.ietf.org/doc/draft-das-consequence-path-completeness/">
        <front>
          <title>When the Gate Can Be Bypassed: Consequence-Path Completeness for Execution Finality</title>
          <author fullname="Sangam Das" initials="S." surname="Das"/>
          <date year="2026" month="September"/>
        </front>
        <seriesInfo name="Internet-Draft" value="draft-das-consequence-path-completeness-00"/>
      </reference>

      <reference anchor="DAS-STATE" target="https://datatracker.ietf.org/doc/draft-das-state-policy-continuity-finality/">
        <front>
          <title>When Valid Authorization Becomes Stale: State and Policy Continuity at the Execution-Finality Boundary</title>
          <author fullname="Sangam Das" initials="S." surname="Das"/>
          <date year="2026" month="September"/>
        </front>
        <seriesInfo name="Internet-Draft" value="draft-das-state-policy-continuity-finality-00"/>
      </reference>

      <reference anchor="DAS-REVOCATION" target="https://datatracker.ietf.org/doc/draft-das-finality-bound-revocation/">
        <front>
          <title>Revoked but Still Executable: Closing the Authorization-to-Effect Gap with Finality-Bound Revocation</title>
          <author fullname="Sangam Das" initials="S." surname="Das"/>
          <date year="2026" month="September"/>
        </front>
        <seriesInfo name="Internet-Draft" value="draft-das-finality-bound-revocation-00"/>
      </reference>

      <reference anchor="DAS-JURISDICTION" target="https://datatracker.ietf.org/doc/draft-das-jurisdiction-bound-execution-finality/">
        <front>
          <title>Authorized Here, Not Authorized There: Jurisdiction-Bound Execution Finality for Cross-Border and Sovereign Systems</title>
          <author fullname="Sangam Das" initials="S." surname="Das"/>
          <date year="2026" month="September"/>
        </front>
        <seriesInfo name="Internet-Draft" value="draft-das-jurisdiction-bound-execution-finality-00"/>
      </reference>

      <reference anchor="DAS-PURPOSE" target="https://datatracker.ietf.org/doc/draft-das-purpose-execution-finality/">
        <front>
          <title>Data-Purpose Laundering Prevention: Execution-Finality for Preventing Cross-Domain Data Reuse</title>
          <author fullname="Sangam Das" initials="S." surname="Das"/>
          <date year="2026" month="September"/>
        </front>
        <seriesInfo name="Internet-Draft" value="draft-das-purpose-execution-finality-03"/>
      </reference>

      <reference anchor="DAS-AGENTIC" target="https://datatracker.ietf.org/doc/draft-das-agentic-execution-finality/">
        <front>
          <title>Tool Selection Is Not Execution: Finality for Agentic Tool Dispatch in High-Risk AI Systems</title>
          <author fullname="Sangam Das" initials="S." surname="Das"/>
          <date year="2026" month="September"/>
        </front>
        <seriesInfo name="Internet-Draft" value="draft-das-agentic-execution-finality-02"/>
      </reference>
    </references>
  </back>
</rfc>
