<?xml version="1.0" encoding="utf-8"?>
<rfc xmlns:xi="http://www.w3.org/2001/XInclude"
     docName="draft-schrock-ep-quorum-02"
     category="info" ipr="trust200902" submissionType="IETF"
     version="3" tocInclude="true" sortRefs="true" symRefs="true">
  <front>
    <title abbrev="EP Multi-Party Quorum">Multi-Party Quorum Authorization for High-Risk Agent Actions (EP-QUORUM)</title>
    <seriesInfo name="Internet-Draft" value="draft-schrock-ep-quorum-02"/>
    <author fullname="Iman Schrock">
      <organization>EMILIA Protocol, Inc.</organization>
      <address>
        <postal>
          <country>US</country>
        </postal>
        <email>team@emiliaprotocol.ai</email>
      </address>
    </author>
    <date year="2026" month="July" day="6"/>
    <area>sec</area>
    <keyword>AI agents</keyword>
    <keyword>authorization</keyword>
    <keyword>multi-party</keyword>
    <keyword>quorum</keyword>
    <keyword>two-person rule</keyword>
    <keyword>separation of duties</keyword>
    <keyword>WebAuthn</keyword>
    <abstract>
      <t>This document defines EP-QUORUM, a multi-party authorization
      profile for the EMILIA Protocol (EP) authorization receipt
      (draft-schrock-ep-authorization-receipts). Where the base receipt
      binds a single accountable human to one exact high-risk action,
      EP-QUORUM binds a set of distinct accountable humans -- the
      "two-person rule," generalized to M-of-N and to ordered approval
      trails -- to one exact action, such that no action is authorized
      until the full quorum holds. The profile is purely additive: each
      quorum member is an unmodified EP signoff over the same action hash,
      and a single-approver policy is the degenerate one-member case.
      EP-QUORUM specifies a fail-closed quorum predicate (all member
      signatures valid, every member bound to the exact action, approvers
      pairwise distinct, every approver admitted by role, the threshold
      met, the declared order respected, and all signatures within a
      bounded time window), an incremental server-side admission rule that
      rejects a non-conforming signer before it enters the trail, and a set
      of adversarial conformance vectors. The predicate is
      offline-verifiable under the base draft's verification model and is
      maintained as cross-language conformance vectors that three reference
      verifiers (JavaScript, Python, Go) are required to agree on. Those
      verifiers live in one repository -- a cross-language consistency check,
      not clean-room independent implementations; independent implementations
      remain future interoperability evidence. The document also describes,
      informatively, the multi-handshake composition process: how individual
      signing ceremonies -- fresh one-time challenge, exact-action binding,
      user-verified device signature -- compose into one ordered,
      offline-verifiable, once-consumable multi-party decision.</t>
    </abstract>
  </front>
  <middle>
    <section anchor="introduction">
      <name>Introduction</name>
      <t>The base EP authorization receipt closes the gap between "is this
      actor authorized in general?" and "should this exact action happen,
      and which accountable human said yes?" by binding one named
      approver's device-held signature to one exact action (see
      <xref target="EP-RECEIPTS"/>, Section 1). For the
      highest-consequence actions, one approver is not the right control.
      The discipline that governs nuclear release, large-value treasury
      movement, and production-credential change is the
      <em>two-person rule</em>: no single human -- however
      well-authenticated, however senior -- can unilaterally cause the
      action. Two or more distinct, accountable humans must each
      independently authorize, and the action proceeds only when all of
      them have.</t>
      <t>As autonomous agents acquire credentials sufficient for
      irreversible operations, the two-person rule is exactly the control
      that lets an organization grant an agent real authority without
      creating a single point of failure: a compromised, misaligned, or
      prompt-injected agent cannot act alone, and neither can a single
      compromised or coerced approver. EP-QUORUM specifies how to express
      that control as a cryptographic predicate over EP signoffs and how to
      enforce it both at the moment each approver signs and at the moment
      the action would execute.</t>
      <t>The base draft already contemplates multi-approver policies
      (<xref target="EP-RECEIPTS"/>, Section 7): each approver signs an
      individual Authorization Context sharing the same action hash, and
      commitment occurs only when k valid, distinct signoffs exist before
      expiry. This document makes that sketch normative and testable. It
      adds: ordered approval trails (<xref target="approval-modes"/>); an
      explicit role roster and admission semantics
      (<xref target="quorum-policy"/>); a bounded approval window with a
      monotonic-time constraint for ordered mode
      (<xref target="approval-modes"/>); an incremental server-side
      admission rule that keeps a non-conforming signer out of the trail in
      the first place (<xref target="incremental-admission"/>); the
      consolidated fail-closed quorum predicate
      (<xref target="quorum-gate"/>); and an adversarial conformance suite
      (<xref target="conformance"/>).</t>
      <section anchor="design-goals">
        <name>Design Goals</name>
        <t>EP-QUORUM inherits design goals G1-G7 of the base draft and
        adds:</t>
        <ul>
          <li><strong>Q1 -- Additivity.</strong> A quorum is a set of
          unmodified EP signoffs over the same action hash. No new
          signature type, no new signing ceremony, and no change to the
          receipt verifier are introduced. A single-approver policy is the
          one-member quorum.</li>
          <li><strong>Q2 -- Fail-closed.</strong> Authorization is denied
          unless <em>every</em> element of the quorum predicate holds.
          Absence of evidence, an unparseable member, a malformed policy,
          or any single failed check yields "not satisfied," never
          "satisfied."</li>
          <li><strong>Q3 -- Distinctness (separation of duties at the human
          level).</strong> A quorum of size k requires k pairwise-distinct
          human approvers, each distinct from the initiator. One human
          <bcp14>MUST NOT</bcp14> fill two slots.</li>
          <li><strong>Q4 -- Incremental enforcement.</strong> The protocol
          enforces conformance as each approver signs, not only at consume
          time, so that a wrong-action, wrong-role, duplicate,
          out-of-order, stale, or invalid signature never becomes part of
          the trail.</li>
          <li><strong>Q5 -- Offline-verifiable quorum.</strong> The
          satisfied/not-satisfied judgment is computable from the receipt's
          members and the policy alone, under the same offline verification
          model as the base draft (<xref target="EP-RECEIPTS"/>,
          Section 6.3).</li>
        </ul>
      </section>
    </section>

    <section anchor="terminology">
      <name>Terminology</name>
      <t>In addition to the terminology of <xref target="EP-RECEIPTS"/>:</t>
      <t>The key words "<bcp14>MUST</bcp14>", "<bcp14>MUST NOT</bcp14>",
      "<bcp14>REQUIRED</bcp14>", "<bcp14>SHALL</bcp14>",
      "<bcp14>SHOULD</bcp14>", "<bcp14>MAY</bcp14>" 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>
      <dl>
        <dt>Quorum.</dt>
        <dd>The set of distinct approver signoffs required to authorize one
        action under a Quorum Policy.</dd>
        <dt>Quorum Policy.</dt>
        <dd>A named, versioned rule set declaring the approval mode, the
        required count, the roster of eligible (role, approver) slots, the
        distinct-humans rule, and the approval window. Carried in the
        policy that governs the action; see
        <xref target="quorum-policy"/>.</dd>
        <dt>Member.</dt>
        <dd>One element of a candidate quorum: a (role, approver public
        key, signoff) triple, where the signoff is an unmodified EP signoff
        (<xref target="EP-RECEIPTS"/>, Section 5.3) over the Authorization
        Context the approver signed.</dd>
        <dt>Trail.</dt>
        <dd>The ordered sequence of members admitted so far for one action
        -- the partial quorum under construction.</dd>
        <dt>Quorum Gate.</dt>
        <dd>The fail-closed predicate (<xref target="quorum-gate"/>) that
        decides whether a trail is a satisfied quorum. The Verifying
        Executor (<xref target="EP-RECEIPTS"/>, Section 9)
        <bcp14>MUST</bcp14> consult it before performing the action.</dd>
      </dl>
    </section>

    <section anchor="quorum-policy">
      <name>The Quorum Policy</name>
      <t>A Quorum Policy is a JSON object:</t>
      <sourcecode type="json"><![CDATA[
{
  "mode": "ordered",
  "required": 3,
  "approvers": [
    {
      "role": "program_officer",
      "approver": "ep:approver:po_rivera"
    },
    {
      "role": "authorizing_official",
      "approver": "ep:approver:ao_chen"
    },
    {
      "role": "inspector_general",
      "approver": "ep:approver:ig_okafor"
    }
  ],
  "distinct_humans": true,
  "window_sec": 900
}
]]></sourcecode>
      <t>Members:</t>
      <table>
        <thead>
          <tr><th>Field</th><th>Required</th><th>Type</th><th>Meaning</th></tr>
        </thead>
        <tbody>
          <tr>
            <td><tt>mode</tt></td>
            <td><bcp14>REQUIRED</bcp14></td>
            <td>string (enum)</td>
            <td><tt>threshold</tt> or <tt>ordered</tt>
            (<xref target="approval-modes"/>).</td>
          </tr>
          <tr>
            <td><tt>required</tt></td>
            <td><bcp14>REQUIRED</bcp14></td>
            <td>integer &gt;= 1</td>
            <td>The quorum size k.</td>
          </tr>
          <tr>
            <td><tt>approvers</tt></td>
            <td><bcp14>REQUIRED</bcp14></td>
            <td>array</td>
            <td>The roster of eligible {role, approver} slots. <bcp14>MUST</bcp14>
            be non-empty.</td>
          </tr>
          <tr>
            <td><tt>distinct_humans</tt></td>
            <td><bcp14>OPTIONAL</bcp14> (default true)</td>
            <td>boolean</td>
            <td>When true, no approver identifier may fill more than one
            slot, AND no single device key may fill more than one slot
            (<xref target="quorum-gate"/>, checks 5 and 5a). Implementations
            <bcp14>MUST</bcp14> treat a missing value as true.</td>
          </tr>
          <tr>
            <td><tt>window_sec</tt></td>
            <td><bcp14>OPTIONAL</bcp14> (default 900)</td>
            <td>integer &gt; 0</td>
            <td>Maximum span, in seconds, between the first and any later
            admitted signature.</td>
          </tr>
          <tr>
            <td><tt>ordered_chain</tt></td>
            <td><bcp14>OPTIONAL</bcp14> (default false)</td>
            <td>boolean</td>
            <td>Strong ordered mode (<xref target="approval-modes"/>). When
            true (ordered mode only), each signoff after the first
            <bcp14>MUST</bcp14> commit, inside its own signed context, to the
            hash of its predecessor's context (<tt>prev_context_hash</tt>), so
            approval order is proven by the signatures rather than by
            <tt>issued_at</tt> timestamps.</td>
          </tr>
        </tbody>
      </table>
      <t>Rules:</t>
      <ul>
        <li>An <tt>approver</tt> slot identifies an eligible approver in the
        Approver Directory (<xref target="EP-RECEIPTS"/>, Section 5.2). The
        <tt>role</tt> is the organizational role under which that approver
        is admitted to this quorum; it is the unit of role eligibility
        (<xref target="quorum-gate"/>, check 4).</li>
        <li>A member is admitted only if its (role, approver) pair is
        present in the roster. A correct signature by a real, enrolled
        approver who is not on the roster for this action
        <bcp14>MUST</bcp14> be rejected (<tt>wrong_role</tt>).</li>
        <li><tt>required</tt> <bcp14>MUST NOT</bcp14> exceed the number of
        distinct human approvers the roster can supply under
        <tt>distinct_humans</tt>. A policy that cannot be satisfied is a
        misconfiguration; verifiers treat an unsatisfiable trail as not
        satisfied, as always.</li>
        <li>The Quorum Policy is part of the action's governing policy and
        is therefore committed by the <tt>policy_hash</tt> of every
        member's Authorization Context (<xref target="EP-RECEIPTS"/>,
        Section 4). A signature collected under one Quorum Policy version
        <bcp14>MUST NOT</bcp14> satisfy a requirement evaluated under
        another.</li>
      </ul>
    </section>

    <section anchor="approval-modes">
      <name>Approval Modes</name>
      <t><strong>Threshold mode (<tt>"threshold"</tt>).</strong> Any
      <tt>required</tt> distinct approvers drawn from the roster, in any
      order, satisfy the quorum. This is the classic M-of-N rule.</t>
      <t><strong>Ordered mode (<tt>"ordered"</tt>).</strong> Approvals
      <bcp14>MUST</bcp14> occur in the roster's listed order: the i-th
      admitted member <bcp14>MUST</bcp14> match <tt>approvers[i-1]</tt> in
      both role and approver. In addition, signature times
      <bcp14>MUST</bcp14> be strictly increasing -- each member's
      <tt>issued_at</tt> <bcp14>MUST</bcp14> be later than the previous
      admitted member's <tt>issued_at</tt> (<tt>non_increasing_time</tt>).
      Ordered mode expresses escalation chains in which a later authority
      signs <em>after, and in knowledge of,</em> an earlier one (e.g.,
      Program Officer, then Authorizing Official, then Inspector
      General).</t>
      <t>In both modes, every admitted member's <tt>issued_at</tt>
      <bcp14>MUST</bcp14> fall within <tt>window_sec</tt> of the first
      admitted member's <tt>issued_at</tt> (<tt>window_exceeded</tt>). The
      window bounds the lifetime of a partial quorum so that a long-dormant
      partial trail cannot be completed much later by an attacker who has
      since compromised a remaining approver.</t>
      <t><strong>Strong ordered mode (<tt>"ordered_chain": true</tt>).</strong>
      Ordering by <tt>issued_at</tt> alone is asserted by whoever stamps the
      time. To prove order by the signatures themselves, a policy
      <bcp14>MAY</bcp14> require a cryptographic chain: each signoff after the
      first carries, inside its signed Authorization Context, a
      <tt>prev_context_hash</tt> equal to the SHA-256 of the canonical context
      of its predecessor in the trail; the first signoff carries none. Because
      each approver's device signs over its own context, the i-th approver
      thereby attests to having signed <em>after</em> the exact (i-1)-th
      approval, and no party -- including the operator -- can reorder, insert,
      or backdate a signoff without invalidating a signature
      (<tt>broken_chain</tt>). This is <bcp14>RECOMMENDED</bcp14> for the
      highest-consequence ordered actions. Implementations that do not set
      <tt>ordered_chain</tt> retain timestamp ordering and remain
      conformant.</t>
    </section>

    <section anchor="quorum-gate">
      <name>The Quorum Gate (fail-closed predicate)</name>
      <t>A trail is a <strong>satisfied quorum</strong> for an action with
      hash H under policy P if and only if ALL of the following hold. A
      verifier <bcp14>MUST</bcp14> return "satisfied" only when every check
      passes, and <bcp14>MUST</bcp14> return "not satisfied" on the first
      failure, on a malformed policy or member, or on any unrecognized
      condition (Q2):</t>
      <ol>
        <li><strong>Well-formed policy.</strong> P has a recognized
        <tt>mode</tt>, an integer <tt>required &gt;= 1</tt>, and a non-empty
        <tt>approvers</tt> roster. Otherwise: not satisfied.</li>
        <li><strong>All signatures valid.</strong> For every member, the EP
        signoff verifies under <xref target="EP-RECEIPTS"/>,
        Section 5.3 / 6.3 -- the WebAuthn <xref target="WEBAUTHN"/>
        assertion (Class A) verifies against the member's
        <tt>approver_public_key</tt>, with the
        assertion challenge equal to the member's context hash and user
        verification asserted. One invalid signature
        (<tt>one_bad_signature</tt>) fails the whole quorum.</li>
        <li><strong>Action binding.</strong> Every member's Authorization
        Context carries <tt>action_hash == H</tt>
        (<tt>action_mismatch</tt>). A member bound to any other action does
        not count.</li>
        <li><strong>Role admission.</strong> Every member's (role,
        approver) pair is present in the roster
        (<tt>wrong_role</tt>).</li>
        <li><strong>Distinct humans.</strong> When <tt>distinct_humans</tt>
        is true (the default), approvers are pairwise distinct and (per the
        base draft's SelfApprovalImpossible) distinct from the initiator
        (<tt>duplicate_human</tt>).</li>
        <li><strong>Distinct keys (check 5a).</strong> When
        <tt>distinct_humans</tt> is true, the admitted members'
        <tt>approver_public_key</tt> values are pairwise distinct
        (<tt>duplicate_key</tt>). This defends against a single device key
        enrolled under two approver identifiers, which would otherwise pass
        the distinct-humans check by name while being one signer.</li>
        <li><strong>Threshold.</strong> At least <tt>required</tt> admitted
        members exist (<tt>under_threshold</tt>).</li>
        <li><strong>Order (ordered mode only).</strong> The i-th admitted
        member matches <tt>approvers[i-1]</tt>; signature times are strictly
        increasing (<tt>out_of_order</tt>,
        <tt>non_increasing_time</tt>).</li>
        <li><strong>Ordering chain (strong ordered mode only, check 7a).</strong>
        When <tt>ordered_chain</tt> is true, the first admitted member carries
        no <tt>prev_context_hash</tt> and every later member's
        <tt>prev_context_hash</tt> equals the SHA-256 of the canonical context
        of its predecessor (<tt>broken_chain</tt>). When <tt>ordered_chain</tt>
        is false this check is not applicable.</li>
        <li><strong>Window.</strong> Every admitted member's
        <tt>issued_at</tt> is within <tt>window_sec</tt> of the first
        member's <tt>issued_at</tt> (<tt>window_exceeded</tt>).</li>
      </ol>
      <t>The predicate is the same whether computed by the orchestrating
      operator before consumption or by an independent Verifying Executor or
      auditor offline (Q5): it is a pure function of (P, H, members).
      Because each member is an unmodified EP signoff, check 2 is exactly
      the base draft's verifier invoked per member; EP-QUORUM adds the
      set-level checks 1, 3-10 on top.</t>
    </section>

    <section anchor="incremental-admission">
      <name>Incremental Admission (canAccept)</name>
      <t>To keep a non-conforming signer out of the trail rather than
      discovering it only at consume time (Q4), an orchestrator
      <bcp14>MUST</bcp14> evaluate an incremental admission rule before
      recording each new signoff. Given the policy P, the action hash H, the
      already-admitted trail, and one incoming candidate member, the rule
      ADMITS the candidate only if all of the following hold, and otherwise
      REJECTS it with the named reason:</t>
      <ol>
        <li>P is well-formed and its roster is non-empty (else
        <tt>no_policy</tt> / <tt>no_eligible_approvers</tt>).</li>
        <li>The candidate's context carries <tt>action_hash == H</tt> (else
        <tt>action_mismatch</tt>).</li>
        <li>The candidate's (role, approver) is on the roster (else
        <tt>ineligible_role</tt>).</li>
        <li>When <tt>distinct_humans</tt> is true, no already-admitted
        member shares the candidate's approver (else
        <tt>duplicate_human</tt>).</li>
        <li>In ordered mode, the candidate matches the next unfilled roster
        slot (<tt>approvers[len(trail)]</tt>) in both role and approver
        (else <tt>out_of_order</tt>).</li>
        <li>If the trail is non-empty, the candidate's <tt>issued_at</tt> is
        within <tt>window_sec</tt> of the first member's <tt>issued_at</tt>
        (else <tt>window_exceeded</tt>); and in ordered mode it is strictly
        greater than the last admitted member's <tt>issued_at</tt> (else
        <tt>non_increasing_time</tt>).</li>
        <li>The candidate's signature verifies (else
        <tt>invalid_signature</tt>).</li>
      </ol>
      <t>A rejected candidate <bcp14>MUST NOT</bcp14> be written into the
      trail. Incremental admission is an enforcement convenience and an
      early-rejection UX; it is not a substitute for the Quorum Gate. A
      conforming Verifying Executor <bcp14>MUST</bcp14> re-evaluate the full
      Quorum Gate (<xref target="quorum-gate"/>) over the assembled trail
      before performing the action, regardless of incremental admission,
      because the executor does not trust the orchestrator to have applied
      admission honestly (this mirrors the base draft's execution-side
      enforcement, <xref target="EP-RECEIPTS"/>, Section 9).</t>
    </section>

    <section anchor="composition">
      <name>Multi-Handshake Composition</name>
      <t>This section is informative. It describes, end to end, the process
      by which the normative machinery of <xref target="quorum-policy"/>
      through <xref target="incremental-admission"/> composes individual
      signing ceremonies into one multi-party decision. Every property named
      here is required elsewhere in this document or in the base draft; this
      section introduces no new requirements.</t>
      <t><strong>The handshake is the unit of approval.</strong> Each quorum
      member is one complete run of the base draft's single-approver signing
      ceremony (<xref target="EP-RECEIPTS"/>, Section 5.3): a fresh, one-time
      challenge is issued for that approver against a verified handshake,
      bound to the exact action bytes through the action hash and binding
      hash it commits to, and bounded by an explicit validity window; the
      approver answers it with a user-verified, device-held signature whose
      WebAuthn assertion challenge is the member's context hash (the
      per-member requirement is check 2 of the Quorum Gate,
      <xref target="quorum-gate"/>). Nothing about the ceremony is weakened
      or amortized when the action needs several approvers: there is no
      batch signature and no shared signing session; k approvals are k
      separate ceremonies, each independently verifiable and each
      independently refusable.</t>
      <t><strong>A malformed trail never exists.</strong> Composition is
      incremental and server-side. Each candidate member is evaluated
      against the admission rule of <xref target="incremental-admission"/>
      before it is recorded, so a wrong-action, off-roster, duplicate,
      out-of-order, stale, or invalidly signed member handshake is refused
      at the moment it is presented and never enters the trail. The stored
      trail is therefore, at every instant, a prefix of a potentially
      satisfiable quorum; there is no later cleaning pass that removes bad
      members, because bad members are never written. The Verifying
      Executor still re-evaluates the full Quorum Gate over the assembled
      trail, as <xref target="incremental-admission"/> requires, precisely
      because it does not trust the orchestrator to have admitted
      honestly.</t>
      <t><strong>Order is a signed commitment, not a claim.</strong> In
      strong ordered mode (<xref target="approval-modes"/>), each member's
      signed context after the first commits to the hash of its
      predecessor's context. Because that commitment sits inside the bytes
      the approver's device signed, and each predecessor in turn committed
      to its own predecessor, the i-th signature transitively covers the
      entire prior trail: the order of approvals is non-repudiable, and no
      party -- including the operator -- can reorder, insert, or backdate a
      member without invalidating a signature (check 7a,
      <xref target="quorum-gate"/>). Plain ordered mode retains the weaker,
      timestamp-based ordering described in
      <xref target="approval-modes"/>.</t>
      <t><strong>Denial is a first-class terminal outcome.</strong> Any
      approver in the trail can refuse, and a refusal is not the mere
      absence of an approval. Under the base draft a denial is signed over
      the same context hash with a decision of "denied" and is terminal: a
      denied authorization can never later become approved
      (<xref target="EP-RECEIPTS"/>, Sections 5.3 and 6.1). At the
      composition level, one dissenting approver leaves durable,
      attributable evidence of the dissent, which a relying party can weigh
      as adverse evidence rather than as a gap in the trail.</t>
      <t><strong>The composed decision is consumed once.</strong> A
      satisfied quorum authorizes exactly one execution of exactly one
      action. Each member's signoff individually retains the base draft's
      one-time-consumption rule (<xref target="sec-window"/>), and
      consumption of the composed decision is refused until the Quorum Gate
      is satisfied; a partial trail confers no partial authority
      (<xref target="sec-partial"/>).</t>
      <t><strong>The whole composition is verifiable offline.</strong>
      Because the satisfied/not-satisfied judgment is a pure function of
      the policy, the action hash, and the members
      (<xref target="quorum-gate"/>, Q5), an auditor holding only the
      quorum receipt can recompute every check above -- per-member
      signatures, action binding, distinctness of humans and keys, roster
      admission, threshold, order and ordering chain, and window -- without
      contacting the operator. As in the base draft, such offline
      verification establishes the integrity and authenticity of the
      recorded composition as of signing time; it does not by itself
      establish current validity, such as the present enrollment or
      revocation status of the approvers involved.</t>
    </section>

    <section anchor="member-representation">
      <name>Member Representation in the Receipt</name>
      <t>A quorum receipt is an ordinary EP Trust Receipt
      (<xref target="EP-RECEIPTS"/>, Section 6.2) whose <tt>contexts</tt>
      and <tt>signoffs</tt> arrays carry one entry per admitted member, plus
      the Quorum Policy (by reference via <tt>policy_hash</tt>, and
      optionally inline for convenience). For the offline
      quorum computation, each member is the triple:</t>
      <sourcecode type="json"><![CDATA[
{
  "role": "program_officer",
  "approver_public_key": "<SPKI of the approver's enrolled key>",
  "signoff": {
    "@type": "ep.signoff",
    "context": {
      "context_type": "ep.signoff.v1",
      "action_hash": "...",
      "approver": "...",
      "issued_at": "...",
      "...": "..."
    },
    "webauthn": {
      "authenticator_data": "...",
      "client_data_json": "...",
      "signature": "..."
    }
  }
}
]]></sourcecode>
      <t>The <tt>context</tt> and <tt>webauthn</tt> members are exactly as
      defined by the base draft; EP-QUORUM does not alter their
      canonicalization, hashing, or signature verification. The
      <tt>role</tt> and <tt>approver_public_key</tt> are the join keys
      against the Quorum Policy roster and the Approver Directory.</t>
    </section>

    <section anchor="conformance">
      <name>Conformance</name>
      <t>An implementation conforms to EP-QUORUM if, for the published
      adversarial conformance vectors, it returns the expected
      satisfied/not-satisfied verdict for every vector and rejects every
      non-conforming candidate at incremental admission with the expected
      reason. The reference suite (EP-QUORUM-v1) comprises the following
      vectors, each carrying real Class-A WebAuthn assertions:</t>
      <table>
        <thead>
          <tr><th>Vector</th><th>Expect</th><th>Exercises</th></tr>
        </thead>
        <tbody>
          <tr><td><tt>accept_ordered_3of3</tt></td><td>satisfied</td><td>Ordered PO, AO, IG; distinct; increasing time; all action-bound</td></tr>
          <tr><td><tt>accept_threshold_2of3</tt></td><td>satisfied</td><td>Any 2 distinct approvers from a 3-slot roster</td></tr>
          <tr><td><tt>reject_under_threshold</tt></td><td>not satisfied</td><td>Fewer than <tt>required</tt> valid members</td></tr>
          <tr><td><tt>reject_duplicate_human</tt></td><td>not satisfied</td><td>One human filling two slots</td></tr>
          <tr><td><tt>reject_out_of_order</tt></td><td>not satisfied</td><td>Ordered mode, members out of roster order</td></tr>
          <tr><td><tt>reject_action_mismatch</tt></td><td>not satisfied</td><td>A member bound to a different action hash</td></tr>
          <tr><td><tt>reject_expired_window</tt></td><td>not satisfied</td><td>A member outside <tt>window_sec</tt></td></tr>
          <tr><td><tt>reject_one_bad_signature</tt></td><td>not satisfied</td><td>One invalid member signature</td></tr>
          <tr><td><tt>reject_wrong_role</tt></td><td>not satisfied</td><td>A correct signature by an off-roster approver</td></tr>
          <tr><td><tt>reject_duplicate_key</tt></td><td>not satisfied</td><td>One device key signing under two approver identifiers (check 5a)</td></tr>
          <tr><td><tt>reject_broken_chain</tt></td><td>not satisfied</td><td>Strong ordered mode, a member committing to the wrong predecessor context hash (check 7a)</td></tr>
        </tbody>
      </table>
      <t>The reference suite is maintained such that three cross-language
      reference verifiers (JavaScript, Python, Go), which share one
      repository and are therefore a consistency check rather than
      clean-room independent implementations, <bcp14>MUST</bcp14> agree on
      every vector; divergence is a conformance defect in at least one
      verifier. Independent implementations remain future interoperability
      evidence. The "accept" vectors guard against a verifier that is
      too strict (denying valid quorums); the "reject" vectors guard against
      a verifier that is too lenient (the security-critical direction).</t>
    </section>

    <section anchor="security-considerations">
      <name>Security Considerations</name>
      <t>EP-QUORUM inherits all Security Considerations of
      <xref target="EP-RECEIPTS"/> and adds the following. Several restate,
      honestly, what a quorum does <em>not</em> buy.</t>
      <section anchor="sec-prevent">
        <name>What multi-party authorization does and does not prevent</name>
        <t>A satisfied quorum proves that k pairwise-distinct enrolled
        approvers each produced a valid, action-bound, in-window, in-order
        (where required) signature with their own device-held keys, and that
        the orchestrator could not have forged any of them
        (<xref target="EP-RECEIPTS"/>, Section 11.1). It raises the cost of
        unilateral action: a single compromised agent, a single stolen key,
        a single coerced or malicious approver is insufficient. It does
        <em>not</em> defeat collusion among the required number of
        distinct humans, nor one human who controls multiple enrolled
        identities (an enrollment control -- base draft Section 5.2), nor
        simultaneous coercion of a full quorum. As in the base draft,
        EP-QUORUM makes such events <em>attributable</em> -- named, signed,
        and evidenced for every member -- which is a deterrent and an audit
        primitive, not an impossibility proof. Implementations
        <bcp14>MUST NOT</bcp14> claim a quorum is collusion-proof.</t>
      </section>
      <section anchor="sec-failclosed">
        <name>Fail-closed is the only safe default</name>
        <t>The dangerous error in a multi-party gate is to treat ambiguity
        as approval. EP-QUORUM is specified so that a malformed policy, a
        missing or unparseable member, a partial trail, or any single failed
        check yields "not satisfied." A verifier <bcp14>MUST NOT</bcp14>
        default to satisfied on any unrecognized condition. The "reject"
        conformance vectors exist to catch a regression in this
        direction.</t>
      </section>
      <section anchor="sec-partial">
        <name>Partial trails confer no authority</name>
        <t>A trail short of the threshold, or one in which incremental
        admission has accepted some but not all required slots, authorizes
        nothing. A Verifying Executor presented with a partial trail
        <bcp14>MUST</bcp14> refuse, exactly as it refuses a missing single
        signoff. This is the multi-party form of the base draft's
        NoBypassWrite invariant.</t>
      </section>
      <section anchor="sec-window">
        <name>Window and replay</name>
        <t>The approval window (<tt>window_sec</tt>) bounds how long a
        partial quorum remains completable, limiting the value to an
        attacker of compromising a <em>remaining</em> approver after some
        approvals already exist. Each member's signoff retains the base
        draft's one-time-consumption nonce (G3); the window is an
        additional, quorum-level constraint, not a substitute for
        per-signoff replay protection. The monotonic-time rule in ordered
        mode additionally prevents back-dating a later authority's signature
        to appear to precede an earlier one.</t>
      </section>
      <section anchor="sec-misinform">
        <name>Divide-and-misinform across members</name>
        <t>Because each approver signs their own Authorization Context, a
        malicious orchestrator can attempt to show different approvers
        different renderings or different initiator attestations
        (<xref target="EP-RECEIPTS"/>, Section 11.9) while each individual
        signature remains valid. EP-QUORUM does not change the base draft's
        cross-context consistency requirement; verifiers
        <bcp14>SHOULD</bcp14> surface per-member context differences, and
        high-value ordered policies <bcp14>SHOULD</bcp14> render the prior
        approvers' decisions to each subsequent approver so that the trail is
        a chain of informed approvals rather than parallel ones. The
        presentation-attack mitigations of base draft Section 11.3 apply per
        member.</t>
      </section>
      <section anchor="sec-fatigue">
        <name>Approver fatigue, at quorum scale</name>
        <t>Requiring more humans does not help if each rubber-stamps; it can
        hurt, by diffusing responsibility across a group in which no member
        feels decisive (base draft Section 11.8). Quorum policies
        <bcp14>MUST</bcp14> be scoped to genuinely high-consequence,
        low-frequency actions, and deployments <bcp14>SHOULD</bcp14> monitor
        per-role time-to-sign and deny rates rather than assume that more
        signers means more scrutiny.</t>
      </section>
      <section anchor="sec-rubberstamp">
        <name>Rubber-stamping and review-latency evidence</name>
        <t>Signing-ceremony telemetry -- the times at which each member's
        challenge was issued, first viewed, and approved --
        <bcp14>SHOULD</bcp14> be retained alongside the trail as evidence of
        how much review each approval actually received. A relying party
        <bcp14>MAY</bcp14> apply a minimum-review-latency policy when
        weighing an approval: an approval recorded a very short interval
        after the challenge was first viewed is evidence of rubber-stamping
        rather than review, and a relying party may discount it or treat the
        bundle as containing conflicting evidence accordingly. This raises
        the probability that rubber-stamping is detected after the fact and
        makes it attributable to a named approver; it does not prevent
        rubber-stamping, and it does not defeat collusion or coercion, for
        which <xref target="sec-prevent"/> applies. Ceremony telemetry
        originates with the orchestrator, so a relying party that depends on
        it should weigh unsigned telemetry with the same skepticism as any
        other operator-supplied metadata; deployments that need it to serve
        as evidence rather than log data can have the operator sign the
        ceremony record so that its integrity is independently
        checkable.</t>
      </section>
    </section>

    <section anchor="iana">
      <name>IANA Considerations</name>
      <t>This document has no IANA actions.</t>
    </section>
  </middle>
  <back>
    <references>
      <name>Normative References</name>
      <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.2119.xml"/>
      <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.8174.xml"/>
      <reference anchor="EP-RECEIPTS" target="https://datatracker.ietf.org/doc/draft-schrock-ep-authorization-receipts/">
        <front>
          <title>Authorization Receipts for High-Risk Agent Actions (EP)</title>
          <author fullname="Iman Schrock">
            <organization>EMILIA Protocol, Inc.</organization>
          </author>
          <date year="2026" month="June"/>
        </front>
        <seriesInfo name="Internet-Draft" value="draft-schrock-ep-authorization-receipts"/>
      </reference>
      <reference anchor="WEBAUTHN" target="https://www.w3.org/TR/webauthn-2/">
        <front>
          <title>Web Authentication: An API for accessing Public Key Credentials, Level 2</title>
          <author><organization>W3C</organization></author>
          <date year="2021" month="April"/>
        </front>
      </reference>
    </references>
  </back>
</rfc>
