<?xml version="1.0" encoding="UTF-8"?>
<rfc xmlns:xi="http://www.w3.org/2001/XInclude" category="std" docName="draft-hamr-oauth-agent-delegation-02" ipr="trust200902" consensus="true" version="3">
  <front>
    <title abbrev="Agent Delegation Profile">An Attenuated Delegation Profile for Automated Agents</title>
    <seriesInfo name="Internet-Draft" value="draft-hamr-oauth-agent-delegation-02"/>
    <author initials="A." surname="Hassan" fullname="Amr Hassan">
      <organization>Independent</organization>
      <address>
        <postal>
          <city>San Francisco</city>
          <region>California</region>
          <country>United States of America</country>
        </postal>
        <email>avoidaccess@msn.com</email>
      </address>
    </author>
    <date year="2026" month="September" day="19"/>
    <area>Security</area>
    <workgroup>Web Authorization Protocol</workgroup>
    <keyword>OAuth</keyword>
    <keyword>agent</keyword>
    <keyword>delegation</keyword>
    <keyword>attestation</keyword>
    <keyword>attenuation</keyword>
    <keyword>authorization</keyword>
    <abstract>
      <t>
        This document builds on HTTP Message Signatures (RFC 9421) for
        requests that automated agents send on behalf of people. RFC
        9421 shows who signed a request. It does not show who the agent
        acts for, or what the agent may do. This document defines an
        HTTP header field, Agent-Delegation, that carries a chain of
        delegation links. Each link can only narrow the scope, floors,
        and expiry of the link before it, and a verifier checks every
        link. Floors cover two things. The first is the person or
        account behind the agent: an Attestation Issuer, such as a
        mobile network operator or an identity-document check, answers
        with a signed yes or no, bound to the verifier's nonce and to an
        expiry, and never returns the value behind the answer. The
        second is what the agent may do: each call has a class, r, w, or
        x, with an optional count per class, and a signed menu gives the
        class of each call. This document does not define a credential
        format, an identity system, or revocation.
      </t>
    </abstract>
  </front>
  <middle>

    <section anchor="introduction" numbered="true" toc="default">
      <name>Introduction</name>
      <t>
        Automated agents now act on the web for people: they search,
        fill in forms, and call APIs. An API Provider that receives such
        a request has two problems. It cannot tell whether an
        accountable person stands behind the software, so today it
        mostly tries to detect and block automation, or it asks a person
        to finish the last step, for example with a CAPTCHA. And it
        cannot rely on the agent to stay within what the person asked
        for, because a language model is probabilistic: an instruction
        such as "only search" is a request to the model, not a limit on
        it.
      </t>
      <t>
        A person asks an agent to search for a red-eye flight from SFO
        to LAX under USD 400. The agent finds the airline's API,
        searches, and then books the only seat at that price, reasoning
        that the fare can be cancelled within 24 hours. The airline says
        the fare cannot be refunded: the booking was made from the
        person's own verified account. A model-based check was in place,
        but it is probabilistic too, and it did not stop the booking.
        The person asked for a search, a call that changes nothing. The
        agent made a booking, a call that spends money.
      </t>
      <t>
        <xref target="RFC9421"/> lets a receiver check who signed a
        request. It does not carry who delegated the authority, whether
        an accountable person or account is behind it, or what the agent
        may do. When one agent passes work to another, often at another
        company, existing OAuth mechanisms do not, by themselves, ensure
        that each hop only narrows what it received. A limit that the
        model enforces on itself is not a limit: the check has to sit
        outside the model, at a point the model cannot change (<xref
        target="action-class-verifier-placement"/>).
      </t>
      <t>
        This document adds four things:
      </t>
      <ul spacing="normal">
        <li>
          <t>
            <strong>A chain.</strong> One HTTP header field carries a
            chain of delegation links (<xref target="header"/>, <xref
            target="rfc9421-profile"/>). Each link can only narrow the
            scope, floors, and expiry of the link before it (<xref
            target="attenuation"/>). Widening is a separate,
            consent-visible operation outside this profile. A verifier
            checks every link, with keys from its own trust source
            (<xref target="verification"/>).
          </t>
        </li>
        <li>
          <t>
            <strong>A floor on who is behind the agent.</strong> An
            Attestation Issuer answers each floor (<xref
            target="floors"/>) with a signed yes, a signed no, or a
            signed refusal, bound to the verifier's nonce and to an
            expiry, and never returns the value behind it (<xref
            target="attestation"/>). For example, a mobile network
            operator can attest a voice-and-data line held for at least
            two years with no SIM swap in the last 90 days. An
            identity-document check can attest that the holder is at
            least 18 (<xref target="appendix-a"/>). The expiry limits
            how long a yes can be used.
          </t>
        </li>
        <li>
          <t>
            <strong>A floor on what the agent may do.</strong> Each call
            has a class: r changes nothing; w changes only the caller's
            own things; x reaches beyond the caller, or cannot be
            repeated (<xref target="action-class"/>). A link sets the
            highest class it allows and, optionally, a count per class
            (<xref target="write-budget"/>). For example, a link written
            "rwx" allows every class with no count. A link it delegates
            to can be "r+1w+1x": reads with no count, one w call, and
            one x call. It can never be more. In the flight example, a
            link that allows only r refuses the booking, whatever the
            model decides.
          </t>
        </li>
        <li>
          <t>
            <strong>Who says what class a call has.</strong> The API
            Provider signs a menu that gives each call its class (<xref
            target="declared-menu"/>). Until it does, the party that
            runs the agent can build its own menu, sign it, and be
            responsible for it. Without a trusted menu, the HTTP method
            decides, so a flight search sent as POST is x. A mechanical
            tool can draft a menu from API descriptions. One such tool,
            rwxmap <xref target="RWXMAP"/>, was run over 5465 labelled
            calls it was tuned on, with each vendor left out in turn. It
            gave the right class for 78.4% of calls, a tighter class
            than needed for 17.5%, and a looser class for 4.2% (<xref
            target="implementation-status"/>). The same menu can later
            give MCP tool annotations <xref target="MCP"/>, such as
            readOnlyHint and destructiveHint. This document does not
            define that mapping.
          </t>
        </li>
      </ul>
      <t>
        This document does not:
      </t>
      <ul spacing="normal">
        <li>
          <t>
            define a credential format, a signature scheme, or an agent
            identity system. It works with related work and does not
            replace it (<xref target="related-work"/>).
          </t>
        </li>
        <li>
          <t>
            prove that a human is present, or that one person backs one
            agent. A long-held line is costly to get, but it is not
            unique. A genuine identity document limits identities to the
            documents a holder has (<xref target="security"/>, <xref
            target="appendix-a"/>).
          </t>
        </li>
        <li>
          <t>
            define a unique identifier for the person. This is on
            purpose, for privacy. A deployment that needs one site to
            recognize a returning document can use a per-site tag, as
            zkagent <xref target="ZKAGENT"/> does, and no other site can
            link that tag. A deployment can also add its own limits on
            how often one line or one document backs a chain.
          </t>
        </li>
        <li>
          <t>
            revoke. A short expiry is the only limit on the life of a
            link.
          </t>
        </li>
        <li>
          <t>
            stop replay (<xref target="binding-not-replay"/>), share
            counts across verifiers (<xref
            target="write-budget-limits"/>), or hide queries from the
            Attestation Issuer (<xref target="privacy"/>).
          </t>
        </li>
      </ul>
    </section>

    <section anchor="conventions" numbered="true" toc="default">
      <name>Conventions and Terminology</name>
      <t>
        The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL
        NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED",
        "MAY", and "OPTIONAL" in this document are to be interpreted as
        described in BCP 14 <xref target="RFC2119"/> <xref target="RFC8174"/>
        when, and only when, they appear in all capitals, as shown here.
      </t>
      <t>
        This document defines the following terms:
      </t>
      <dl newline="false" spacing="normal">
        <dt>Attestation Issuer:</dt>
        <dd>
          An entity that produces a signed, boolean, expiring statement
          about whether a subject satisfies a floor condition, without
          disclosing the value behind the answer. This document treats
          the role abstractly: any party a deployment trusts to make
          such a statement.
        </dd>
        <dt>Delegator:</dt>
        <dd>
          The principal that holds a delegation and grants a narrower
          one to the next link. The first Delegator in a chain is the
          human or organizational principal that originates it.
        </dd>
        <dt>Delegate:</dt>
        <dd>
          The principal that receives a delegation link, whether an
          automated agent or a sub-agent.
        </dd>
        <dt>Relying Service:</dt>
        <dd>
          The service that receives a request bearing a delegation chain
          and decides whether to honor it.
        </dd>
        <dt>API Provider:</dt>
        <dd>
          The party that runs the API named by a request's target
          origin. It is commonly the same operator as the Relying
          Service, but this document does not require that; the two are
          distinct roles.
        </dd>
        <dt>Scope:</dt>
        <dd>
          The set of capabilities a link grants, expressed as a set of
          opaque strings. See <xref target="scope"/>.
        </dd>
        <dt>Floor:</dt>
        <dd>
          A named set of axis/value pairs a subject must satisfy before
          a Relying Service or an intermediate Delegate will act. See
          <xref target="floors"/>. Each pair is a constraint, and a
          floor carries at most one per axis.
        </dd>
        <dt>Link:</dt>
        <dd>
          One element of a delegation chain: a scope, a floor, an
          expiry, and the signature that binds them to their issuer and
          to their parent link.
        </dd>
      </dl>
    </section>

    <section anchor="layers" numbered="true" toc="default">
      <name>Position Among Delegation Layers</name>
      <t>
        Delegated agent authorization, read across the drafts active in
        this space, separates into three distinct layers:
      </t>
      <dl newline="false" spacing="normal">
        <dt>(a) Who may act on whose behalf:</dt>
        <dd>
          the delegation chain and the floor conditions that bound it,
          as it narrows from an originating principal to the agent
          making a request.
        </dd>
        <dt>(b) What exactly may be done, per invocation:</dt>
        <dd>
          binding a specific action, tool name, or argument set to a
          grant, as the action is about to occur.
        </dd>
        <dt>(c) What was approved and consumed, once:</dt>
        <dd>
          the receipts and audit evidence that record that a given
          action was authorized, presented, and consumed.
        </dd>
      </dl>
      <t>
        This document occupies layer (a), plus floors. The chain model of
        <xref target="attenuation"/> and the floor axes of
        <xref target="floors"/> bound the space of what an agent could
        invoke coarsely: an axis such as actionClass or xBudget
        constrains a category of action or a count of calls across an
        entire chain, not the arguments of any single call. A floor is
        evaluated once, when a link is issued or verified, and it does
        not change per invocation.
      </t>
      <t>
        This document does not perform per-call argument binding, and it
        does not produce a receipt. <xref target="RFC9396"/> defines Rich
        Authorization Requests, which bind authorization detail at grant
        time. <xref target="I-D.das-agentic-tool-binding"/> binds a tool
        name and its arguments per call, after a model has already chosen
        what to invoke.
        <xref target="I-D.schrock-ep-authorization-receipts"/> addresses
        layer (c) with a pre-execution approval bundle and terminal
        consumption evidence, without itself establishing that the
        action occurred.
        <xref target="I-D.schrock-action-evidence-boundary"/> complements
        it with an executor-side model for exact-action matching, local
        authorization, atomic consumption, and outcome reconciliation at
        invocation, including a requirement to enumerate every path that
        bypasses it. The ordering runs: authorization detail is bound at
        grant time; floors of the kind this document specifies bound the
        space in between as a chain is attenuated hop to hop; and a
        per-call digest is bound after a specific choice has been made.
      </t>
      <t>
        One structural problem is shared across this layering and is not
        solved by any one layer alone: a parent credential remains valid
        after it has been attenuated into a narrower child, so a holder
        of the parent retains the broader authority the child was only
        ever a narrowing of. This problem sits in the composition
        boundary between layers, not inside any single token profile.
        <xref target="I-D.asor-wimse-agent-delegation-chain"/> Section 9.1
        lists mitigations for it (a short token lifetime, holder binding,
        and status lists). This document shares the same problem and
        offers no token-profile fix for it either; a deployment that
        needs to close it needs a mechanism this document does not
        supply.
      </t>
    </section>

    <section anchor="header" numbered="true" toc="default">
      <name>The Agent-Delegation Header Field</name>
      <t>
        This document defines an HTTP field, Agent-Delegation. It carries a
        delegation chain from a Delegate to a Relying Service, or to the next
        Delegate in the chain. It is a Structured Field
        <xref target="RFC9651"/>: a List whose members are Byte Sequences,
        each member one opaque, encoded link, ordered from the root link
        (index zero) to the leaf link the immediate sender presents.
      </t>
      <artwork>
Agent-Delegation: :BASE64-ENCODED-L0:, :BASE64-ENCODED-L1:
      </artwork>
      <t>
        The credential format in use sets the encoding inside each link
        (<xref target="attestation"/>); this document does not mandate one. A
        verifier MUST be able to parse each Byte Sequence into the fields that
        <xref target="attenuation"/> and <xref target="attestation"/> require,
        before it evaluates the chain. The immediate sender covers
        Agent-Delegation with an RFC 9421 <xref target="RFC9421"/> signature,
        which binds the field to the request it accompanies. This document
        defines no new signature mechanism.
      </t>
      <t>
        Any intermediary that can read HTTP headers can read
        Agent-Delegation, and this profile does not encrypt it. A link MUST
        NOT carry personal data of the Delegator, the Delegate, or any subject
        the floor condition concerns. See <xref target="privacy"/>.
      </t>
    </section>

    <section anchor="rfc9421-profile" numbered="true" toc="default">
      <name>Profile of RFC 9421 Signature Parameters</name>
      <t>
        This section profiles the RFC 9421 <xref target="RFC9421"/> signature
        parameters of its Section 2.3 for a request that carries an
        Agent-Delegation field. It defines no new signature mechanism.
      </t>
      <t>
        A sender MUST include the <tt>keyid</tt>, <tt>alg</tt>,
        <tt>created</tt>, <tt>expires</tt>, and <tt>nonce</tt> parameters, and
        MUST include the Agent-Delegation field among the signature's covered
        components.
      </t>
      <t>
        A sender MUST set the <tt>tag</tt> parameter to the string
        "agent-delegation", so that a verifier can identify a signature made
        under this profile. Tag values are application-specific and need no
        registry. RFC 9421 Section 7.2.7 states the risk that unrelated
        applications pick the same tag; this profile adds nothing to it.
      </t>
      <t>
        RFC 9421 leaves enforcement of <tt>expires</tt> to the application.
        This profile requires it: a verifier MUST reject a request whose
        signature <tt>expires</tt> value is earlier than the time of
        verification. Three expiry values stay separate. A link carries its
        own expiry. A floor attestation carries its own expiry
        (<xref target="attestation"/>). The <tt>expires</tt> parameter covers
        the HTTP message alone.
      </t>
      <t>
        A verifier that needs a fresh signature bound to a value of its own
        choosing SHOULD send the Accept-Signature field with a <tt>nonce</tt>
        parameter, as RFC 9421 Section 7.2.2 describes. This profile defines
        no other way to deliver a nonce. The <tt>nonce</tt> parameter binds
        one HTTP message. The nonce inside a floor attestation
        (<xref target="attestation"/>) binds one attestation to one verifier's
        question. They are two nonces at two layers, and implementers MUST NOT
        treat them as one.
      </t>
      <t>
        RFC 9421 signs a message. It has no notion of delegated authority,
        sub-principals, or attenuation of scope. The chain model of
        <xref target="attenuation"/> therefore extends RFC 9421 rather than
        restating it.
      </t>
    </section>

    <section anchor="scope" numbered="true" toc="default">
      <name>Scope</name>
      <t>
        A scope is a set of zero or more capability strings. A capability
        string is OPAQUE to this profile: this document defines no
        syntax, no internal structure, and no namespace for it. Any
        structure a deployment gives it, for example a resource:action
        convention, is a deployment convention and MUST NOT be
        interpreted by a verifier implementing this profile.
      </t>
      <t>
        Comparison is by exact set containment. A scope S1 is contained
        in a scope S2 if and only if every member of S1 is
        octet-for-octet identical to some member of S2.
      </t>
      <t>
        A verifier MUST NOT apply any of the following when comparing
        scopes, because none is defined by this profile:
      </t>
      <ul spacing="normal">
        <li>wildcard or prefix matching;</li>
        <li>
          hierarchical or namespace containment: a verifier MUST NOT
          treat one capability string as containing another because of
          a shared prefix, separator, or apparent parent/child relation;
        </li>
        <li>case folding;</li>
        <li>Unicode normalisation;</li>
        <li>whitespace trimming; or</li>
        <li>any other canonicalisation.</li>
      </ul>
      <t>
        The presented bytes are compared as presented. A verifier that
        applies a matching rule this profile does not define can accept
        a chain another conforming verifier rejects, which makes
        attenuation unverifiable.
      </t>
      <t>
        The empty set is a valid scope, is contained in every scope, and
        is not by itself an error. Wildcard, hierarchical, and
        pattern-based scope semantics are out of scope for this version
        of the profile and are future work.
      </t>
    </section>

    <section anchor="attenuation" numbered="true" toc="default">
      <name>Attenuation Rules</name>
      <t>
        A delegation chain is an ordered sequence of links L(0), L(1), ...,
        L(n), where L(0) is the root delegation and L(n) is the link
        presented to the Relying Service. For every link L(i) with i &gt;
        0, relative to its parent L(i-1), all of the following MUST hold:
      </t>
      <ol spacing="normal" type="1">
        <li>
          <t>
            <strong>Scope containment.</strong> The scope granted by L(i) MUST be a
            subset of the scope granted by L(i-1), per <xref target="scope"/>.
            L(i) MUST NOT name a capability that L(i-1) did not itself hold.
          </t>
        </li>
        <li>
          <t>
            <strong>Floor non-relaxation.</strong> The floor carried by L(i) MUST be
            at least as tight as the floor carried by L(i-1) on every
            axis defined in <xref target="floors"/>. L(i) MUST NOT be
            looser than L(i-1) on any axis, whether by omitting an axis
            L(i-1) constrained, by widening an ordered axis's value, or
            by substituting a different value on an equality axis that
            L(i-1) did not itself permit.
          </t>
        </li>
        <li>
          <t>
            <strong>Expiry non-extension.</strong> The expiry carried by L(i) MUST
            NOT be later than the expiry carried by L(i-1).
          </t>
        </li>
      </ol>
      <t>
        These three rules apply to every link in the chain, not only to
        the leaf link. A verifier MUST validate rules 1 through 3 for
        every adjacent pair (L(i-1), L(i)) for i = 1 to n, in addition to
        validating L(0) against the root Attestation Issuer's published
        floor and the Delegator's own authority. A verifier MUST reject
        the entire chain if any single link violates any single rule; a
        partial acceptance of a chain up to the point of violation is not
        conforming.
      </t>
      <t>
        A verifier MUST support a configurable maximum chain depth and
        MUST reject a chain whose length exceeds that configured maximum,
        prior to evaluating any link's content. This bounds the
        verification cost of a chain and limits the depth to which
        authorization can be re-delegated across administrative domains
        in a single deployment.
      </t>
      <t>
        Widening a scope, a floor, or an expiry beyond what a chain
        currently carries is out of scope for this profile. Where a
        deployment needs to widen an existing grant, that MUST be
        performed as a distinct, explicit, consent-visible operation
        outside this delegation mechanism, producing a new root
        delegation rather than a modification of an existing chain.
      </t>
    </section>

    <section anchor="floors" numbered="true" toc="default">
      <name>Floor Axes</name>
      <t>
        An axis is one named condition a floor can constrain, for
        example how long a line has existed. A floor is a set of
        axis/value pairs. This document defines a closed set of axes
        for this version of the profile:
      </t>
      <table>
        <thead>
          <tr><th>Axis</th><th>Type</th><th>Comparator</th></tr>
        </thead>
        <tbody>
          <tr><td>subjectClass</td><td>enum</td><td>equals</td></tr>
          <tr><td>tenureMin</td><td>duration</td><td>min</td></tr>
          <tr><td>credentialAgeMin</td><td>duration</td><td>min</td></tr>
          <tr><td>ageMin</td><td>non-negative integer (years)</td><td>min</td></tr>
          <tr><td>actionClass</td><td>ordered list: r &lt; w &lt; x</td><td>rank; semantics in <xref target="action-class"/></td></tr>
          <tr><td>classSource</td><td>ordered list: method &lt; declared</td><td>rank; semantics in <xref target="action-class"/></td></tr>
          <tr><td>rBudget</td><td>non-negative integer</td><td>max; semantics in <xref target="write-budget"/></td></tr>
          <tr><td>wBudget</td><td>non-negative integer</td><td>max; semantics in <xref target="write-budget"/></td></tr>
          <tr><td>xBudget</td><td>non-negative integer</td><td>max; semantics in <xref target="write-budget"/></td></tr>
        </tbody>
      </table>
      <t>
        An axis name that is not in this table MUST cause a verifier to
        reject the floor, and therefore the link that carries it. An
        unknown or misspelled axis name is always a hard rejection; it
        MUST NOT be silently ignored or dropped from the comparison.
        This applies equally to a published floor and to a requested
        floor presented in a delegation link.
      </t>
      <t>
        subjectClass has two values. <tt>interactive</tt> means the
        subject is a service a person uses directly; for a telecom
        issuer, a line with voice and data service. <tt>machine</tt>
        means the subject is a device-only service; for a telecom
        issuer, a machine-to-machine line. A child link's subjectClass
        MUST equal its parent's.
      </t>
      <t>
        ageMin is an age threshold stated in whole years, for example
        18. It asks one question: is the subject at least that age. An
        Attestation Issuer answers only yes or no; it MUST NOT return
        the subject's age or date of birth. ageMin does not use the
        duration grammar of <xref target="duration-grammar"/>. An
        Attestation Issuer publishes the thresholds it supports; if a
        floor asks for a threshold that is not on that list, the
        issuer MUST refuse, per <xref target="refusal"/>.
      </t>
      <t>
        For a min-compared axis (tenureMin, credentialAgeMin, ageMin),
        a larger value is tighter than a smaller one. For a
        max-compared axis (rBudget, wBudget, xBudget), a smaller value
        is tighter than a larger one. For a
        rank-compared axis (actionClass, classSource), a lower position
        in the axis's ordered list is tighter than a higher one.
      </t>
      <t>
        min, max, and rank are the comparisons defined in
        <xref target="I-D.asor-wimse-agent-delegation-chain"/> Section
        4.2, with the same tightening rules as its Section 4.3. equals
        is that draft's one_of with a single value. This document names
        its own closed set of axes rather than a registry.
      </t>
      <t>
        A link can leave an axis out in two different ways. First,
        within a chain: if the parent link L(i-1) constrains an axis
        and the child link L(i) leaves it out, that is a loosening;
        the chain MUST be rejected, per rule 2 of
        <xref target="attenuation"/>. Second, if no link in the chain up
        to and including the link under evaluation constrains an axis,
        the issuer's published floor for that axis applies unchanged.
        This second case is inheritance, not loosening, and it is not
        the same as the first case.
      </t>
      <t>
        On every ordered axis, a requested floor value MUST be at least
        as tight, by that axis's comparator, as the matching published
        or parent floor value. If it is not, the request MUST be
        refused. A floor value MUST NOT be widened implicitly, as a
        side effect of anything this profile defines; as
        <xref target="attenuation"/> states, widening is always a
        separate, explicit, consent-visible operation, outside this
        profile's scope.
      </t>
      <section anchor="duration-grammar" numbered="true" toc="default">
        <name>Duration Grammar</name>
        <t>
          A duration-typed axis value MUST match exactly one of two
          forms, P&lt;n&gt;D or P&lt;n&gt;Y, where &lt;n&gt; is a
          non-negative integer. &lt;n&gt; MUST be between 0 and 2^53 - 1
          inclusive. No other ISO 8601 duration form is accepted. A
          duration given in months MUST be rejected, because a month's
          length in days depends on the calendar and is ambiguous;
          accepting it would make the tightening comparison above
          undefined. For comparison, 1Y equals exactly 365D. A value
          outside the permitted range, or matching neither form, MUST
          be rejected.
        </t>
      </section>
    </section>

    <section anchor="action-class" numbered="true" toc="default">
      <name>Action Class Floors</name>
      <t>
        This section defines actionClass, the highest class of call a
        link allows, and classSource, where a verifier gets a call's
        class from.
      </t>
      <section anchor="action-class-values" numbered="true" toc="default">
        <name>actionClass</name>
        <t>
          actionClass is an ordered list, r, w, and x, ranked
          r &lt; w &lt; x by the rank comparator of
          <xref target="floors"/>. A link's actionClass is a ceiling
          on the whole link, not a statement about one call. The
          values are defined by effect:
        </t>
        <dl newline="false" spacing="normal">
          <dt>r:</dt>
          <dd>the call changes nothing.</dd>
          <dt>w:</dt>
          <dd>
            the call changes only the caller's own things, such as
            its own records, settings, or files. Deleting the
            caller's own thing is w, even if it cannot be undone.
          </dd>
          <dt>x:</dt>
          <dd>
            Either of two things is enough. The call reaches beyond
            the caller: it affects another party or their resource,
            money, a live session or call, a network path or a
            device; it sends or publishes to others; or it changes
            who has access to something shared. Or the call is not
            repeatable: doing it twice is not the same as doing it
            once.
          </dd>
        </dl>
        <t>
          Whether a call can be undone is not the test. A child link's
          actionClass MUST NOT be higher than its parent's.
        </t>
      </section>
      <section anchor="class-source" numbered="true" toc="default">
        <name>classSource</name>
        <t>
          classSource is an ordered list, method and declared,
          ranked method &lt; declared by the rank comparator of
          <xref target="floors"/>. With method, the verifier uses
          the HTTP method alone (<xref target="classification"/>).
          With declared, the verifier also uses a signed menu
          (<xref target="declared-menu"/>); where the
          menu verifies and names the call, its value is used
          instead of the method default. method trusts no one but
          the verifier, so it is the tighter value.
        </t>
        <t>
          A child link's classSource MUST NOT go from method to
          declared; the only permitted move is declared down to
          method. Where no link in the chain sets classSource,
          method applies, the second omitted-axis case of
          <xref target="floors"/>; where a parent sets it and a
          child omits it, the chain MUST be rejected, the first
          case.
        </t>
      </section>
      <section anchor="classification" numbered="true" toc="default">
        <name>Classification</name>
        <t>
          Where classSource is method, the method gives a starting
          class, not the definition of the class: GET, HEAD, and
          OPTIONS default to r; PUT, DELETE, and PATCH default to w;
          POST, and any other method, default to x. This matches how
          most APIs use HTTP methods, and it can be wrong either way:
          some PUT, DELETE, or PATCH calls are really x, and some
          POST calls are really r or w. A GET that changes something
          breaks the safe-method rule of <xref target="RFC9110"/>.
          The API Provider's declared menu, below, corrects a
          wrong starting class.
        </t>
        <t>
          Where classSource is declared, the class is the value the
          menu declares, provided it verifies per
          <xref target="declared-menu"/> and names the call. If the
          menu is absent, its signature does not verify, the
          verifier has no trusted key for the menu's signer, or its iss does
          not match the request's target, the verifier MUST use the
          method default. A verifier MUST NOT combine or take the
          greater of the two;
          a valid, matching menu entry replaces the method default,
          it is not compared against it. No menu at all is handled
          the same way as a menu that fails to verify. A verifier
          MUST NOT build or change a menu while it admits a
          request; a menu is created and signed before it is used.
        </t>
      </section>
      <section anchor="declared-menu" numbered="true" toc="default">
        <name>The Declared Menu</name>
        <t>
          A declared menu is a JSON Web Signature (JWS)
          <xref target="RFC7515"/> whose payload has two members:
          iss, the resource's origin, serialized per
          <xref target="RFC6454"/>; and menu, a JSON object mapping
          an operation key to r, w, or x. An operation key has the
          form "METHOD path-template", where a brace-delimited
          segment such as {callId} stands for exactly one non-empty
          path segment.
        </t>
        <t>
          A verifier matches a request against a menu's keys
          deterministically: the method matches exactly, and the
          path matches only where both have the same number of
          segments and each segment either equals the template's
          literal segment or fills a parameter. Where more than one
          key matches, the one with the most literal segments wins.
          A tie MUST be treated as a miss, and MUST NOT be broken by
          the keys' order in the menu. If no key matches, or the
          only matches are tied, the method default applies.
        </t>
        <t>
          A menu applies only to the API it describes: its iss
          value is compared octet-for-octet against the request
          target's origin, and a mismatch fails the menu the same way
          an unverifiable signature does; the verifier MUST NOT
          accept it. A menu is signed by the API Provider, or by
          the party that runs the agent (for example, a customer
          that deploys it). A verifier uses a menu only if it is
          signed by a key the verifier is configured to trust for
          that API; the key never comes from the menu itself, per
          the same rule <xref target="verification"/> states for the
          root attestation's key. A verifier that looks for a menu
          uses one signed by the API Provider first. If there is
          none, it may use one signed by the party that runs the
          agent, if configured to trust that key. If there is
          neither, the method default applies. A menu signed by the
          party that runs the agent is that party's own choice and
          responsibility. It limits only the verifiers that party
          configures, such as its own harness; it never makes an
          API Provider's verifier admit more. Its signature
          algorithm is EdDSA using Ed25519, with no negotiation; any
          other algorithm does not verify.
        </t>
      </section>
      <section anchor="menu-publication" numbered="true" toc="default">
        <name>Publication</name>
        <t>
          This section is non-normative. An API Provider may publish
          its menu in its API description, for example as a
          per-operation extension in an OpenAPI document with a
          detached JWS at a well-known location; the format of such
          a mechanism is out of scope for this document. Until
          providers do this, the party that runs the agent can build
          its own menu, using a tool that reads API descriptions
          mechanically: it gives each call a starting class from its
          method and from the verbs and nouns in its name, and marks
          calls it has no evidence for so a person can check them.
          Such a tool picks the tighter class when its reading is
          unsure, except that it keeps GET, HEAD and OPTIONS calls at
          r without reading their names. One such tool, run over 5465
          labelled calls it was tuned on (each vendor left out in
          turn), gave the right class for 78.4% of calls, a tighter
          class than needed for 17.5%, and a looser class than needed
          for 4.2%; a looser class lets through a call that a floor
          should refuse. The party that signs a menu is responsible
          for the menu it signs. The same menu can also
          give an agent tool hints. When API Providers take this up,
          they can run the same kind of tool over their own APIs,
          review only the calls it marked, and sign their own menu.
        </t>
      </section>
      <section anchor="action-class-verifier-placement" numbered="true" toc="default">
        <name>Verifier Placement</name>
        <t>
          An enforcement boundary is a role in an effect-capable path,
          not a component: the first admission point on that path whose
          successful admission is necessary for the protected effect to
          complete through that path. Any component can occupy this
          role for a given path, including a harness or orchestration
          layer, provided that component actually refuses or permits
          the request rather than merely observing or advising on it; a
          component that only coordinates or advises does not classify
          or admit, whatever else it does. Different paths to the same
          effect can realize the role at different components or
          layers, and no single component need be common to all of
          them. The verifier, as used throughout this document, is the
          component occupying the enforcement-boundary role for a given
          effect-capable path; the two terms refer to the same role,
          not two different things.
        </t>
        <t>
          Boundary identification under this section is performed
          separately for each effect-capable path. A deployment with
          more than one path to the same protected effect identifies a
          qualifying enforcement boundary on each path individually;
          identifying one on a single path says nothing about any
          other.
        </t>
        <t>
          Closure is a separate, system-wide requirement: every
          effect-capable path MUST cross an enforcement boundary
          qualifying under the definition above before the protected
          effect can complete. This is checked by the bypass test: for
          a given protected effect, if any path capable of producing
          that effect can do so without crossing a qualifying
          enforcement boundary, closure fails for that effect,
          regardless of how many other paths to it are correctly
          guarded. This closure requirement is consistent with, and
          distinct from, the complete-mediation condition of
          <xref target="I-D.schrock-action-evidence-boundary"/>. That
          document requires a deployment to document every direct,
          break-glass, administrator, alternate-protocol, and
          system-of-record path that bypasses its implementation, and
          forbids a claim of complete mediation unless the protected
          system rejects all material alternate paths or subjects them
          to an equivalent boundary. That condition governs what one
          deployment may claim about the system it protects. This
          document's closure requirement governs admission itself: it
          is an invariant over the complete set of effect-capable paths
          to a given protected effect, checked by the bypass test,
          however many components realize the enforcement-boundary
          role across them, and independent of any conformance claim.
        </t>
        <t>
          An agent that declines to attempt an action it expects to be
          refused does not, by that restraint, satisfy this section:
          satisfaction requires an enforcement boundary that would
          refuse the request had it actually arrived, not an agent's
          choice not to send it. Admission is an act of an enforcement
          boundary refusing or permitting a request, not an act of a
          harness or an agent abiding by a floor it was merely told
          about.
        </t>
      </section>
    </section>

    <section anchor="write-budget" numbered="true" toc="default">
      <name>Budgets</name>
      <t>
        This section defines rBudget, wBudget, and xBudget, the budget
        axes of <xref target="floors"/>: what each limits (<xref
        target="write-budget-axis"/>), the limit and count and how a
        request is admitted (<xref
        target="write-budget-limit-count"/>), how the count is keyed
        (<xref target="write-budget-chain-identifier"/>), and the
        scope of the limits (<xref target="write-budget-limits"/>).
      </t>
      <section anchor="write-budget-axis" numbered="true" toc="default">
        <name>Budget Axes</name>
        <t>
          rBudget, wBudget, and xBudget are each a non-negative
          integer, compared with the max comparator of <xref
          target="floors"/>. Each limits one actionClass value, per
          <xref target="classification"/>: rBudget bounds r-class
          calls, wBudget bounds w-class calls, xBudget bounds x-class
          calls, and a call never spends against the other two. A
          child link MUST NOT carry a higher value than its parent's
          for any of the three; raising one violates rule 2 of <xref
          target="attenuation"/>.
        </t>
        <t>
          Where no link sets a given budget and the issuer's published
          floor sets none either, that class has no count limit:
          actionClass alone governs, so a link classed x with no
          xBudget allows unlimited x-class calls. Where the published
          floor does set a budget, that value applies (the
          link-to-published-floor case of <xref target="floors"/>);
          omission is inheritance, never widening. Where a parent
          link sets a budget and a child omits it, the chain MUST be
          rejected (the link-to-link case, rule 2 of <xref
          target="attenuation"/>).
        </t>
        <t>
          A short label such as "rw+2x" means actionClass x, xBudget
          2, no rBudget or wBudget: no limit on r or w, at most 2 x
          calls. The label is shorthand for talking about a link, not
          a wire format.
        </t>
      </section>
      <section anchor="write-budget-limit-count" numbered="true" toc="default">
        <name>Limit and Count</name>
        <t>
          "Limit" and "count" are kept apart. The LIMIT is a budget
          value carried in the chain (<xref
          target="write-budget-axis"/>), carried on the wire like any
          floor value. The COUNT is the number already spent for a
          chain; it is verifier-held state, keyed by the chain
          identifier of <xref target="write-budget-chain-identifier"/>,
          one per budget per chain, never carried in a link. A verifier
          MUST NOT treat any link field as the count.
        </t>
        <t>
          A request is classed per <xref target="classification"/> and
          admitted only if the count remaining for that class is at
          least one, where a limit applies; with no limit, the
          actionClass rule of <xref target="action-class-values"/>
          alone governs. Where a limit applies, the count MUST be
          decremented in the same admission decision, with no window
          in which two concurrent requests on the same chain both see
          the same remaining count. At zero, the request MUST be
          rejected with the uniform outcome <xref
          target="verification"/> requires for any failed check,
          without disclosing the remaining count. The count can only
          live in verifier-held state because anything on the wire can
          be sent again (<xref target="binding-not-replay"/>).
        </t>
      </section>
      <section anchor="write-budget-chain-identifier" numbered="true" toc="default">
        <name>Chain Identifier</name>
        <t>
          The chain identifier MUST be derived by the verifier itself
          from the chain, and MUST NOT be taken from the request; a
          verifier that accepts a caller-supplied identifier does not
          conform, whatever else it checks. It is the SHA-256 <xref
          target="RFC6234"/> digest of the signature value of L(0),
          the root link, in the encoding the credential format uses on
          the wire (<xref target="header"/>, <xref
          target="attestation"/>): the same bytes a verifier already
          extracts and verifies as L(0)'s signature under item 1 of
          <xref target="attestation"/>.
        </t>
        <t>
          Every link, from L(0) to L(n), shares the identifier derived
          from L(0)'s signature. Re-delegation produces a new child
          link L(i) with its own signature but does not change L(0),
          so it does not change the chain identifier; re-delegation
          MUST NOT create new budgets, and a verifier MUST NOT derive
          the identifier from any link other than L(0).
        </t>
      </section>
      <section anchor="write-budget-limits" numbered="true" toc="default">
        <name>Limits</name>
        <t>
          Counts are held per verifier, not shared across verifiers: N
          verifiers with no shared state can together admit up to N
          times a budget before any one reaches zero. Sharing a count
          across verifiers is out of scope; this limit is stated here,
          not hidden.
        </t>
        <t>
          Each budget is cumulative per chain and per class, not per
          operation, because a per-operation cap is defeated by
          splitting one action into many smaller ones, each under its
          own cap. rBudget limits how many reads an agent makes, not
          how much data one read returns; how much a read returns is
          up to the API Provider, for example through page size.
        </t>
        <t>
          This document makes no claim about what budget size is
          practical; choosing a value is left to the deployment.
        </t>
      </section>
    </section>

    <section anchor="attestation" numbered="true" toc="default">
      <name>Attestation Properties</name>
      <t>
        This document does not mandate a specific credential format. A
        JSON Web Token, a CBOR Object Signing and Encryption (COSE)
        structure, or another signed structure MAY be used, if it meets
        every property in this section. A conforming attestation MUST:
      </t>
      <ol spacing="normal" type="1">
        <li>
          <t>
            be signed by the Attestation Issuer over the exact bytes
            presented to the verifier. The verifier MUST verify the
            signature over those exact bytes and MUST NOT re-serialize
            the object first, or instead;
          </t>
        </li>
        <li>
          <t>
            carry the verifier's nonce, echoed back exactly;
          </t>
        </li>
        <li>
          <t>
            carry an expiry. A missing or non-numeric expiry MUST be
            treated as already expired; it MUST NOT be treated as "no
            deadline";
          </t>
        </li>
        <li>
          <t>
            carry only a yes/no result of the floor evaluation, and MUST
            NOT carry the value behind that result. An attestation that
            carries a raw value, such as a date of birth or a tenure
            count, instead of or next to the result, does not conform to
            this profile;
          </t>
        </li>
        <li>
          <t>
            be rejected by the verifier if its serialization has a
            duplicate top-level key. A first-wins or last-wins rule for
            duplicate keys lets the issuer and the verifier disagree
            about which value was signed; this profile requires
            rejection instead of a resolution rule.
          </t>
        </li>
      </ol>
      <section anchor="refusal" numbered="true" toc="default">
        <name>Signed Refusal</name>
        <t>
          Where an Attestation Issuer cannot or will not answer a floor,
          it MUST return a signed refusal, not silence,
          an unsigned error, or a bare protocol failure. The refusal
          MUST be signed by the Attestation Issuer, MUST carry the
          verifier's nonce, and MUST carry an expiry, under the same
          rules as an attestation, so it cannot be forged in the
          issuer's name or stripped without detection. A verifier MUST
          verify a refusal along a path independent of the one it uses
          for an accepted attestation, so a defect in one path does not
          admit a forged instance of the other.
        </t>
        <t>
          If a floor names an axis the Attestation Issuer cannot
          answer, the issuer MUST return a signed refusal and MUST NOT
          skip that axis and answer on the rest; for example, a telecom
          issuer cannot answer ageMin, and an identity-document issuer
          cannot answer tenureMin. If the issuer holds incomplete data
          for an axis, it MUST return a signed refusal and MUST NOT
          round the answer to yes or no.
        </t>
        <t>
          A signed yes, a signed no, and a signed refusal are three
          different results: a signed no is an attestation, under item
          4 above, whose result is false; a signed refusal means the
          issuer cannot or will not answer at all. A verifier MUST NOT
          read a signed refusal as a signed no.
        </t>
      </section>
      <section anchor="binding-not-replay" numbered="true" toc="default">
        <name>Nonce Binding Is Not Replay Protection</name>
        <t>
          The nonce ties a signed response to the request that carried
          it; it does not, by itself, stop that same response from being
          presented again. The same response verifies again for as long
          as it has not expired. Stopping replay, for example by
          tracking nonces already used within their validity window, is
          the verifier's own job and is outside the scope of this
          document. Implementers MUST NOT assume that nonce binding, by
          itself, gives replay protection.
        </t>
      </section>
    </section>

    <section anchor="agent-identifier" numbered="true" toc="default">
      <name>Agent Identifier</name>
      <t>
        A Delegate that presents a delegation chain is identified to a
        Relying Service by an identifier whose only required property, in
        this profile, is: the identifier MUST be unlinkable across
        distinct Relying Services and MUST remain stable across separate
        interactions with the same Relying Service. This document does
        not specify how such an identifier is derived, what inputs
        contribute to it, its length, or its encoding; those are
        deployment choices left to the entity that issues or constructs
        the identifier.
      </t>
      <t>
        This requirement pulls against related work.
        <xref target="I-D.klrc-aiagent-auth"/>, Section 6, wants an agent
        identifier that is stable for the life of a workload identity, so
        that audit records can be linked over time. An identifier stable
        enough for that audit at one Relying Service is also a strong link
        across Relying Services if it is reused there, or can be derived
        in a reused form. This document does not resolve this tension. It
        is an open item for the Working Group.
      </t>
    </section>

    <section anchor="verification" numbered="true" toc="default">
      <name>Verification Procedure</name>
      <t>
        A verifier processing a request that carries an Agent-Delegation
        field MUST perform the following steps, in the order given. A
        failure at any step MUST produce one uniform rejection outcome,
        indistinguishable from the outcome of a failure at any other
        step, so that the specific reason for rejection does not become
        available to an unauthenticated party as an oracle (see
        <xref target="security"/>).
      </t>
      <ol spacing="normal" type="1">
        <li><t>Parse the Agent-Delegation Structured Field List into its ordered member links.</t></li>
        <li><t>Compare the number of links against the verifier's configured maximum chain depth; reject if the chain exceeds it.</t></li>
        <li><t>Verify the RFC 9421 HTTP Message Signature applied by the immediate sender over the request, including the Agent-Delegation field among its covered components, per the profile of <xref target="rfc9421-profile"/>.</t></li>
        <li><t>Verify each link's own signature, per <xref target="attestation"/>, over its exact presented bytes.</t></li>
        <li><t>Verify the root attestation, L(0), against the Attestation Issuer's public key, where that key is obtained from a configured trust source and MUST NOT be obtained from the message itself.</t></li>
        <li><t>Verify the three attenuation rules of <xref target="attenuation"/> for every adjacent pair of links in the chain.</t></li>
        <li><t>Verify L(0) against the root Attestation Issuer's published floor and the Delegator's own authority, per <xref target="attenuation"/>. Reject any floor, published or carried in a link, that names an axis not listed in <xref target="floors"/>. Where no link up to L(n) constrains an axis, apply the published floor for that axis.</t></li>
        <li><t>Verify that every floor attestation carried by the chain echoes, exactly, the nonce the verifier issued for this exchange, and reject if any does not.</t></li>
        <li><t>Verify that no link in the chain has an expiry that has passed, evaluated at the time of verification.</t></li>
        <li><t>Verify that every floor attestation carried by the chain has a yes result; reject if any is a signed no or a signed refusal, per <xref target="refusal"/>.</t></li>
        <li><t>Classify the request's actionClass per <xref target="classification"/>, using the declared menu of <xref target="declared-menu"/> where the chain's classSource is declared. Admit the request only if its class is not higher than the actionClass that applies to L(n) (<xref target="action-class-values"/>) and, where a budget applies to that class, the count for that class is at least one; decrement that count in the same admission decision, per <xref target="write-budget-limit-count"/>. Per <xref target="action-class-verifier-placement"/>, this step is performed by whichever component occupies the enforcement-boundary role for the effect-capable path the request is on, not by a component that only coordinates or advises.</t></li>
        <li><t>Only after every preceding step has succeeded, apply the Relying Service's authorization policy to the resulting, fully verified chain.</t></li>
      </ol>
      <t>
        A verifier that implements only a subset of these steps, or that
        implements them out of order such that a later step's success can
        mask an earlier step's failure, does not conform to this profile.
      </t>
    </section>

    <section anchor="privacy" numbered="true" toc="default">
      <name>Privacy Considerations</name>
      <t>
        This profile is built around boolean-only disclosure: a floor
        attestation states whether a subject meets a condition and MUST
        NOT carry the underlying value. This bound holds throughout the
        chain; an intermediate Delegate that re-derives or forwards a raw
        value defeats the disclosure minimization this profile exists to
        provide, even where every attenuation rule in
        <xref target="attenuation"/> is otherwise satisfied.
      </t>
      <t>
        The agent identifier requirement in
        <xref target="agent-identifier"/> requires unlinkability across
        Relying Services specifically so that two Relying Services
        receiving requests from the same underlying agent cannot, from
        the identifier alone, determine that the requests originated from
        the same principal.
      </t>
      <t>
        This profile does not eliminate the Attestation Issuer's own
        visibility into the queries it answers. The Attestation Issuer
        necessarily observes that some verifier asked whether some
        subject meets some floor, even though the verifier itself learns
        only a boolean result. Where the query pattern itself is
        sensitive, this residual, issuer-side query log is a real and
        unsolved limitation of this profile, not an oversight to be
        corrected by better protocol design; deployments that need to
        eliminate it need a mechanism outside this document's scope.
      </t>
    </section>

    <section anchor="related-work" numbered="true" toc="default">
      <name>Relationship to Existing Work</name>
      <t>
        The OAuth Working Group charter <xref target="OAUTH-CHARTER"/>,
        as updated 2026-06-04, carries a work item for "authorization of
        automated agents working on behalf of users, including addressing
        scenarios where automated agents act across multiple
        administrative domains". This document targets that work item
        directly: it defines an attenuated chain that a verifier can
        check at every hop across administrative domains.
      </t>
      <t>
        <xref target="I-D.klrc-aiagent-auth"/> is an individual
        submission and has not been adopted by the Working Group; version
        -03 is current as of 2026-09-17. Its Section 8 describes a
        credential-provisioning and posture-attestation stack for agents,
        and its Section 10.6 defines an Identity Assertion JWT
        Authorization Grant. This document fills both extension points
        rather than competing with that draft: any link in the chain can
        carry a floor attestation produced by a stack of that kind, and
        the chain can be presented alongside, or as an input to, that
        grant type. The two documents do pull against each other on agent
        identifiers; <xref target="agent-identifier"/> states that
        tension and leaves it open.
      </t>
      <t>
        Three further individual Internet-Drafts entered adjacent space
        recently and are cited here so that this document does not appear
        unaware of its neighbors:
        <xref target="I-D.asor-wimse-agent-delegation-chain"/>,
        <xref target="I-D.reece-wimse-cross-org-delegation"/>, and
        <xref target="I-D.sweeney-wimse-credential-delegation"/>. This
        document does not compare itself against each in detail. It takes
        its comparators from the first of the three, and
        <xref target="floors"/> states how its axes relate to that
        draft's registry.
      </t>
    </section>

    <section anchor="implementation-status" numbered="true" toc="default">
      <name>Implementation Status</name>
      <t>
        This section records the status of known implementations of
        this profile, per <xref target="RFC7942"/>. The information
        is believed to be accurate at the time of writing, has not
        been independently verified, and is not an endorsement of
        this profile by any party. Both implementations below are
        single-author and open source. Neither has had external
        review or been adopted by any organization, and no
        Attestation Issuer has reviewed or validated this profile.
      </t>
      <t>
        <strong>justabit proof-of-concept.</strong> A
        zero-dependency Node.js proof-of-concept is available at
        <eref target="https://github.com/hamr0/justabit"/>. Its
        suites passed on 2026-09-14, checked by exit code. They
        cover:
      </t>
      <ul spacing="normal">
        <li>the attestation core, in 114 cases in <tt>camara/v2/poc</tt>: Ed25519 signatures with a pinned algorithm, nonce binding, expiry, rejection of a raw value next to the result, duplicate top-level key rejection, and the signed refusal (<xref target="attestation"/>, <xref target="refusal"/>);</li>
        <li>floor comparison, in 26 cases in <tt>ietf/v3/poc</tt>: a closed axis set, the duration grammar, and tightening only (<xref target="floors"/>);</li>
        <li>action classes, in 43 cases in <tt>ietf/v3/poc</tt>: classification by method default or by a signed menu, path-template matching and origin binding of menus, tightening of actionClass and classSource, the two omitted-axis cases, a budget count keyed by the chain identifier the verifier derives, and three Verifier Placement cases, one of them a bypass path as a negative control (<xref target="action-class"/>, <xref target="write-budget-chain-identifier"/>).</li>
      </ul>
      <t>
        The code has not caught up with this revision in four ways,
        and these parts of the text are not tested:
      </t>
      <ul spacing="normal">
        <li>the floor code uses the -01 axis names, including partialPolicy, not the axes of <xref target="floors"/>;</li>
        <li>it has one writeBudget shared by w and x calls, where no number means zero and r calls never count, not the three budgets of <xref target="write-budget"/>, where no number means no limit;</li>
        <li>its method default classes PATCH as x, not w (<xref target="classification"/>);</li>
        <li>it does not run the steps of <xref target="verification"/> end to end.</li>
      </ul>
      <t>
        It does not implement RFC 9421 message signatures or any
        HTTP transport, the Agent-Delegation header, a delegation
        chain of more than two links, or the agent identifier of
        <xref target="agent-identifier"/>.
      </t>
      <t>
        <strong>rwxmap.</strong> rwxmap <xref target="RWXMAP"/> is a
        work-in-progress tool that drafts a menu from OpenAPI
        descriptions with fixed rules and no language model. Step 1
        classes GET, HEAD, and OPTIONS calls, and POST calls whose
        lead verb is a read verb, as r. Step 2 starts PUT, DELETE,
        and PATCH calls at w and raises a call to x when its verb
        acts on something live or a noun names another party; it
        marks a w call that no rule vouched for, so a person can
        check it. Step 3 leaves every other POST call at x. rwxmap
        signs nothing and does not yet produce MCP tool annotations.
        At rwxmap commit 56b3310, run on 2026-09-14 and reproduced
        from a clean copy, it gave these results over 5465 calls
        from 332 vendors, with each vendor left out in turn:
      </t>
      <table>
        <thead>
          <tr><th>Result against the truth labels</th><th>Calls</th><th>Share</th></tr>
        </thead>
        <tbody>
          <tr><td>right class</td><td>4282</td><td>78.4%</td></tr>
          <tr><td>tighter class than needed</td><td>955</td><td>17.5%</td></tr>
          <tr><td>looser class than needed (leak)</td><td>228</td><td>4.2%</td></tr>
        </tbody>
      </table>
      <t>
        Of the 228 leaks, 17 are GET calls that the method default
        keeps at r, and 179 are among the 1998 calls rwxmap marked
        for a person to check. Three limits apply to these figures.
        The rules were tuned on these same calls, so they are not a
        clean test, and there is no clean-test figure today. The
        truth labels were written by language-model agents that read
        each call blind, not by people. The tool's goal is about 80%
        right, 20% tighter, and 1-2% looser; the measured 4.2% is
        above that goal.
      </t>
    </section>

    <section anchor="security" numbered="true" toc="default">
      <name>Security Considerations</name>
      <t>
        <strong>A verifier that checks only the last link defeats
        this profile.</strong> A Delegate can present a well-formed
        last link while an earlier link widened scope, loosened a
        floor, or extended an expiry. Conformance requires every
        step of <xref target="verification"/> for every link; there
        is no mode that checks only the last link.
      </t>
      <t>
        <strong>Nonce binding does not stop replay.</strong> Per
        <xref target="binding-not-replay"/>, the same attestation
        verifies again until it expires. A deployment needs its own
        replay defense, such as tracking used nonces; this document
        does not supply one.
      </t>
      <t>
        <strong>Cost-based floors do not create uniqueness.</strong>
        A tenure or credential-age floor makes a fresh qualifying
        credential slower and more costly to get. It does not show
        that a unique person holds the account, or that one subject
        does not back many agents. A deployment that needs
        uniqueness needs a mechanism this profile does not provide.
      </t>
      <t>
        <strong>This profile defines no revocation.</strong> Once
        issued, an attestation or a link stays valid until its
        expiry. The only mitigation this profile offers is a short
        expiry.
      </t>
      <t>
        <strong>The trust source is a central point.</strong> Every
        verifier depends on the trust source that gives it an
        Attestation Issuer's public key, per step 5 of
        <xref target="verification"/>. A compromise or outage of
        that trust source affects every verification that depends
        on it.
      </t>
      <t>
        <strong>Rejection MUST be uniform.</strong> Per
        <xref target="verification"/>, a verifier MUST NOT return
        outcomes that differ by which check failed, such as an
        expired link, a bad signature, a loosened floor, or a chain
        that is too deep. A verifier that shows which check failed
        lets an unauthenticated party probe the edges of a valid
        chain without ever holding one.
      </t>
      <t>
        <strong>The Attestation Issuer's query log is still a
        disclosure channel.</strong> The verifier learns only a
        yes, a no, or a refusal, but the issuer sees every query. A
        party that can read or compel disclosure of that log learns
        what the wire protocol withholds.
      </t>
      <t>
        <strong>Budgets hold only if the verifier derives the chain
        identifier.</strong> Per
        <xref target="write-budget-chain-identifier"/>, the
        verifier derives the chain identifier from L(0)'s
        signature. A verifier that takes the identifier from the
        request lets the presenter reset rBudget, wBudget, or
        xBudget by sending a new value on the next request; V11 in
        <xref target="vectors"/> tests this failure. Counts are
        held per verifier, per <xref target="write-budget-limits"/>;
        a deployment that needs one shared count across verifiers
        needs a mechanism this document does not supply.
      </t>
      <t>
        <strong>A declared menu can lower a class.</strong> A menu
        can declare a call below its method default, for example a
        POST as r. The origin match and the configured-key rule of
        <xref target="declared-menu"/> stop a menu signed for one
        API from being applied to another; a verifier that skips
        either check does not conform to this document. A menu
        signed by the party that runs the agent is that party's
        responsibility: it limits only the verifiers that party
        configures, and it never makes an API Provider's verifier
        admit more.
      </t>
      <t>
        <strong>The method default can be too loose for
        GET.</strong> The method default classes every GET, HEAD,
        and OPTIONS call as r. Some GET calls change something,
        contrary to the safe-method rule of
        <xref target="RFC9110"/>; a link limited to r admits them
        unless a declared menu gives them a higher class.
      </t>
    </section>

    <section anchor="iana" numbered="true" toc="default">
      <name>IANA Considerations</name>
      <t>
        This document registers the following entry in the "Hypertext
        Transfer Protocol (HTTP) Field Name Registry" defined by
        <xref target="RFC9110"/>:
      </t>
      <table>
        <thead>
          <tr><th>Field Name</th><th>Status</th><th>Structured Type</th><th>Reference</th></tr>
        </thead>
        <tbody>
          <tr><td>Agent-Delegation</td><td>permanent</td><td>List</td><td>This document, <xref target="header"/></td></tr>
        </tbody>
      </table>
      <t>
        No other IANA actions are required by this document.
      </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"/>
      <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9651.xml"/>
      <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9421.xml"/>
      <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9110.xml"/>
      <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.7515.xml"/>
      <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.6454.xml"/>
      <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.6234.xml"/>
    </references>
    <references>
      <name>Informative References</name>
      <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.7942.xml"/>
      <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9396.xml"/>
      <reference anchor="I-D.das-agentic-tool-binding" target="https://datatracker.ietf.org/doc/html/draft-das-agentic-tool-binding-03">
        <front>
          <title>tool_use Is Not invoke(): Binding Execution-Finality to Agentic Tool-Call Interfaces and MCP</title>
          <author fullname="Sangam Das" initials="S." surname="Das">
            <organization>Independent Inventor</organization>
          </author>
          <date day="5" month="September" year="2026"/>
        </front>
        <seriesInfo name="Internet-Draft" value="draft-das-agentic-tool-binding-03"/>
      </reference>
      <reference anchor="I-D.schrock-ep-authorization-receipts" target="https://datatracker.ietf.org/doc/html/draft-schrock-ep-authorization-receipts-13">
        <front>
          <title>Authorization Receipts for High-Risk Agent Actions</title>
          <author fullname="Iman Schrock" initials="I." surname="Schrock">
            <organization>EMILIA Protocol, Inc.</organization>
          </author>
          <date day="11" month="September" year="2026"/>
        </front>
        <seriesInfo name="Internet-Draft" value="draft-schrock-ep-authorization-receipts-13"/>
      </reference>
      <reference anchor="I-D.schrock-action-evidence-boundary" target="https://datatracker.ietf.org/doc/html/draft-schrock-action-evidence-boundary-05">
        <front>
          <title>The Action Evidence Boundary for Consequential Agent Effects</title>
          <author fullname="Iman Schrock" initials="I." surname="Schrock">
            <organization>EMILIA Protocol, Inc.</organization>
          </author>
          <date day="31" month="August" year="2026"/>
        </front>
        <seriesInfo name="Internet-Draft" value="draft-schrock-action-evidence-boundary-05"/>
      </reference>
      <reference anchor="I-D.klrc-aiagent-auth" target="https://datatracker.ietf.org/doc/draft-klrc-aiagent-auth/">
        <front>
          <title>AI Agent Authentication and Authorization</title>
          <author initials="P." surname="Kasselman"><organization>Defakto Security</organization></author>
          <author initials="J." surname="Lombardo"><organization>AWS</organization></author>
          <author initials="Y." surname="Rosomakho"><organization>Zscaler</organization></author>
          <author initials="B." surname="Campbell"><organization>Ping Identity</organization></author>
          <author initials="N." surname="Steele"><organization>OpenAI</organization></author>
          <author initials="A." surname="Parecki"><organization>Okta</organization></author>
          <date month="July" day="6" year="2026"/>
        </front>
        <seriesInfo name="Internet-Draft" value="draft-klrc-aiagent-auth-03"/>
      </reference>
      <reference anchor="I-D.asor-wimse-agent-delegation-chain" target="https://datatracker.ietf.org/doc/html/draft-asor-wimse-agent-delegation-chain-01">
        <front>
          <title>Verifiable Attenuated Delegation for AI Agent Chains</title>
          <author fullname="Rafael Asor" initials="R." surname="Asor">
            <organization>Attenu</organization>
          </author>
          <date day="3" month="September" year="2026"/>
        </front>
        <seriesInfo name="Internet-Draft" value="draft-asor-wimse-agent-delegation-chain-01"/>
      </reference>
      <reference anchor="I-D.reece-wimse-cross-org-delegation" target="https://datatracker.ietf.org/doc/draft-reece-wimse-cross-org-delegation/">
        <front>
          <title>Cross-Organizational Delegation for Workload and Agent Identity: Problem Statement and Requirements</title>
          <author initials="M." surname="Reece"><organization>TowerGuardian Consulting</organization></author>
          <date month="August" day="31" year="2026"/>
        </front>
        <seriesInfo name="Internet-Draft" value="draft-reece-wimse-cross-org-delegation-02"/>
      </reference>
      <reference anchor="I-D.sweeney-wimse-credential-delegation" target="https://datatracker.ietf.org/doc/draft-sweeney-wimse-credential-delegation/">
        <front>
          <title>Credential Delegation Protocol for AI Agents in Multi-System Environments</title>
          <author initials="K." surname="Sweeney"/>
          <date month="July" day="27" year="2026"/>
        </front>
        <seriesInfo name="Internet-Draft" value="draft-sweeney-wimse-credential-delegation-00"/>
      </reference>
      <reference anchor="CAMARA-PROPOSAL" target="https://github.com/camaraproject/APIBacklog/issues/330">
        <front>
          <title>CarrierAttestation: An API Proposal for Attested Windowed Disclosure</title>
          <author initials="A." surname="Hassan"><organization>Independent</organization></author>
          <date year="2026" month="August" day="28"/>
        </front>
      </reference>
      <reference anchor="CAMARA-SIGNING" target="https://github.com/camaraproject/Commonalities/issues/705">
        <front>
          <title>Attested responses for CAMARA APIs: signed, nonce-bound, expiring answers; floor menu; range on open responses</title>
          <author initials="A." surname="Hassan"><organization>Independent</organization></author>
          <date year="2026" month="September" day="1"/>
        </front>
      </reference>
      <reference anchor="OAUTH-CHARTER" target="https://datatracker.ietf.org/wg/oauth/about/">
        <front>
          <title>Web Authorization Protocol (oauth) Working Group Charter</title>
          <author><organization>IETF</organization></author>
          <date year="2026" month="June" day="4"/>
        </front>
      </reference>
      <reference anchor="I-D.google-cfrg-libzk" target="https://datatracker.ietf.org/doc/draft-google-cfrg-libzk/">
        <front>
          <title>Longfellow ZK</title>
          <author initials="M." surname="Frigo"><organization>Google</organization></author>
          <author initials="A." surname="shelat"><organization>Google</organization></author>
          <date month="July" day="22" year="2026"/>
        </front>
        <seriesInfo name="Internet-Draft" value="draft-google-cfrg-libzk-02"/>
      </reference>
      <reference anchor="ISO18013-5" target="https://www.iso.org/standard/69084.html">
        <front>
          <title>ISO/IEC 18013-5: Personal identification - ISO-compliant driving licence - Part 5: Mobile driving licence (mDL) application</title>
          <author><organization>International Organization for Standardization</organization></author>
          <date year="2021"/>
        </front>
      </reference>
      <reference anchor="ICAO9303">
        <front>
          <title>ICAO Doc 9303, Machine Readable Travel Documents, Eighth Edition</title>
          <author><organization>International Civil Aviation Organization</organization></author>
          <date year="2021"/>
        </front>
      </reference>
      <reference anchor="RWXMAP" target="https://github.com/hamr0/rwxmap">
        <front>
          <title>rwxmap: Mechanical r/w/x Classification of OpenAPI Operations</title>
          <author initials="A." surname="Hassan"><organization>Independent</organization></author>
          <date year="2026" month="September"/>
        </front>
      </reference>
      <reference anchor="ZKAGENT" target="https://github.com/hamr0/zkagent">
        <front>
          <title>zkagent: One Yes/No Answer from a Passport or ID Card Chip</title>
          <author initials="A." surname="Hassan"><organization>Independent</organization></author>
          <date year="2026" month="September"/>
        </front>
      </reference>
      <reference anchor="ZK8EEN" target="https://github.com/hamr0/8een">
        <front>
          <title>8een: Stateless, One-Bit, Unlinkable Age Verification</title>
          <author initials="A." surname="Hassan"><organization>Independent</organization></author>
          <date year="2026" month="July"/>
        </front>
      </reference>
      <reference anchor="MCP" target="https://modelcontextprotocol.io/specification/2026-07-28">
        <front>
          <title>Model Context Protocol Specification, Version 2026-07-28</title>
          <author><organization>Model Context Protocol</organization></author>
          <date year="2026" month="July" day="28"/>
        </front>
      </reference>
    </references>

    <section anchor="appendix-a" numbered="true" toc="default">
      <name>Non-Normative Instantiations</name>
      <t>
        This appendix is non-normative. Directions 1 to 3 could fill the
        Attestation Issuer role. None of them makes an attestation that
        meets <xref target="attestation"/> today, so each one states what
        it lacks. Direction 4 is harness experience. This profile does
        not require, prefer, or depend on any of them.
      </t>
      <t>
        <strong>Direction 1: a mobile network operator.</strong> The
        operator answers floors about a line: subjectClass is the service
        type (voice and data, or machine to machine), tenureMin is the
        subscription tenure, and credentialAgeMin is the time since the
        last SIM swap. Many qualifying lines cost money over time, but
        cost does not make a line unique. CAMARA reviewed a companion
        proposal <xref target="CAMARA-PROPOSAL"/> in August and September
        2026 and asked for changes; the author answered them. The signing
        part is now a separate proposal <xref target="CAMARA-SIGNING"/>.
        On 2026-09-15 both are open, and neither is accepted or adopted.
        The two documents meet only at the header of
        <xref target="header"/>. Lacks: CAMARA API responses are not
        signed.
      </t>
      <t>
        <strong>Direction 2: 8een.</strong> The holder's wallet makes a
        zero-knowledge age proof over an ISO/IEC 18013-5
        <xref target="ISO18013-5"/> mobile document, bound to the site's
        nonce, with the scheme of <xref target="I-D.google-cfrg-libzk"/>.
        8een <xref target="ZK8EEN"/> checks the proof against an issuer
        trust list and returns one bit. A one-time nonce and the
        credential expiry limit its use. Its own limit: "No proof from a
        real phone has ever reached this verifier." Lacks: no issuer
        signs the result, because the verifier checks the proof itself.
        This document does not define that model.
      </t>
      <t>
        <strong>Direction 3: zkagent.</strong> zkagent
        <xref target="ZKAGENT"/> reads a passport or ID card chip on the
        holder's phone, checks the chip signature (passive
        authentication, <xref target="ICAO9303"/>), and returns one bit.
        It is not zero-knowledge. Tier A sends the bit with no signature.
        Tier B signs the bit with a phone key and adds a per-site tag; the
        site binds that key the first time it sees it. Passive
        authentication does not bind the person who presents the
        document, and a chip without chip authentication can be cloned.
        Lacks: tier A has no signature, and the tier B key does not come
        from a configured trust source (<xref target="verification"/>).
      </t>
      <t>
        For Directions 2 and 3, subjectClass is interactive and ageMin
        applies; tenureMin and credentialAgeMin do not. They limit
        identities to the documents a holder has. They do not give one
        identity per person.
      </t>
      <t>
        <strong>Direction 4: harness experience.</strong> bareguard
        <eref target="https://github.com/hamr0/bareguard"/>, bareloop
        <eref target="https://github.com/hamr0/bareloop"/>, and bareagent
        <eref target="https://github.com/hamr0/bareagent"/> are
        single-author, open-source experiments. They do not implement this
        profile, so <xref target="implementation-status"/> does not list
        them. Each puts the point of control at a different component: a
        single gate, an outer arbiter, and a policy chokepoint. What they
        share is a component that refuses or permits, not a location. This
        supports the role model of
        <xref target="action-class-verifier-placement"/>.
      </t>
    </section>

    <section anchor="vectors" numbered="true" toc="default">
      <name>Test Vectors</name>
      <t>
        This appendix is non-normative. Where a vector and a normative
        section differ, the normative section governs. Each vector below
        is a negative control: a verifier that skips one check accepts a
        chain or admits a request that a conforming verifier rejects or
        refuses. A suite of only accepted cases cannot tell a conforming
        verifier from one that accepts everything.
      </t>
      <t>
        In V4 and V6, every link has subjectClass interactive, and each
        link's expiry is earlier than its parent's, so rule 3 of
        <xref target="attenuation"/> holds.
      </t>
      <table>
        <thead>
          <tr><th>Vector</th><th>Input</th><th>Expected outcome</th><th>A verifier that gets it wrong</th></tr>
        </thead>
        <tbody>
          <tr><td>V4</td><td>L(0): scope booking:read, tenureMin P2Y, credentialAgeMin P180D. L(1): scope booking:read, tenureMin P2Y, no credentialAgeMin.</td><td>reject: L(1) leaves out an axis its parent set (rule 2 of <xref target="attenuation"/>)</td><td>compares only the axes both links carry, or treats the omission as inheritance from the published floor</td></tr>
          <tr><td>V6</td><td>L(0): scope booking:read. L(1): scope booking:read, booking:create. L(2): scope booking:read. The floors are the same on every link.</td><td>reject: L(1) names booking:create, which L(0) did not hold (rule 1)</td><td>checks only the last pair of links, not every pair as step 6 of <xref target="verification"/> requires</td></tr>
          <tr><td>V9</td><td>One link: actionClass r, classSource declared. Request: POST /check to https://api.example.com. Menu: signed by a key the verifier trusts for that API, declares "POST /check" as r, iss https://attacker.example.com.</td><td>refuse: the iss does not match the request target's origin, so the menu fails; the method default for POST is x, which is higher than r (<xref target="declared-menu"/>)</td><td>checks the menu's signature but does not compare iss with the request target's origin</td></tr>
          <tr><td>V11</td><td>One link: actionClass x, xBudget 2. Four x-class requests on this chain; the fourth carries a chain identifier the caller chose.</td><td>admit, admit, refuse, refuse: the verifier derives the chain identifier from L(0), so the fourth request spends from the same count, which is 0 (<xref target="write-budget-chain-identifier"/>)</td><td>takes the chain identifier from the request, so the fourth request gets a new count of 2 and is admitted</td></tr>
        </tbody>
      </table>
      <t>
        The full set, V1 to V11, with the accepted cases, is in the
        Test Vectors appendix of draft-hamr-oauth-agent-delegation-01
        (<eref target="https://datatracker.ietf.org/doc/html/draft-hamr-oauth-agent-delegation-01"/>).
        There, V10 and V11 use writeBudget 2; in this document that is
        xBudget 2.
      </t>
      <t>
        These vectors do not test link signatures, nonce binding, expiry
        against the clock, or the header encoding. The author derived
        them by hand from the rules. The justabit proof-of-concept
        (<xref target="implementation-status"/>) runs V9 as written, a
        two-request form of V11 with the single writeBudget of -01, and
        the V4 rule on classSource and writeBudget but not on
        credentialAgeMin. No code runs V6.
      </t>
    </section>

    <section anchor="changes-since-01" numbered="true" toc="default">
      <name>Changes since -01</name>
      <t>
        This section is non-normative. It will be removed before this
        document leaves individual submission status.
      </t>
      <ul spacing="normal">
        <li><t>Shortened the whole document. Cut the worked example and the changes since -00, folded the open reconciliation item into <xref target="agent-identifier"/>, and cut <xref target="vectors"/> to four negative controls, citing the full set in -01.</t></li>
        <li><t>Rewrote <xref target="action-class-verifier-placement"/>. An enforcement boundary is now a role on an effect-capable path, identified for each path, rather than a component fixed at the resource or credential boundary. Added closure, a system-wide requirement that every effect-capable path cross a qualifying boundary, with its bypass test, and stated how that differs from the complete-mediation condition of <xref target="I-D.schrock-action-evidence-boundary"/>. <xref target="verification"/> follows the same model.</t></li>
        <li><t>Corrected the layering of <xref target="I-D.schrock-ep-authorization-receipts"/> in <xref target="layers"/>: that draft gives a pre-execution approval bundle and terminal consumption evidence, and does not itself establish that the action occurred. Layer (c) is relabelled "what was approved and consumed, once". Added <xref target="I-D.schrock-action-evidence-boundary"/> alongside it.</t></li>
        <li><t>Dropped the floor axis registry, which the registry of <xref target="I-D.asor-wimse-agent-delegation-chain"/> already covers, and dropped the axes accountClass and partialPolicy. Added ageMin (<xref target="floors"/>).</t></li>
        <li><t>Defined r, w, and x by effect (<xref target="action-class"/>); PATCH now defaults to w. Replaced the single writeBudget with one budget per class (<xref target="write-budget"/>), where no number means no limit.</t></li>
        <li><t>Renamed the Resource Owner role API Provider, and stated that the party running the agent may sign its own menu (<xref target="declared-menu"/>).</t></li>
        <li><t>Rewrote <xref target="appendix-a"/>. Each direction now states what it does today and what it lacks against <xref target="attestation"/>; the earlier claim that all of them fill the Attestation Issuer role was wrong.</t></li>
        <li><t>Added measured figures from a mechanical classifier trial to <xref target="implementation-status"/>, in place of an unsupported accuracy claim.</t></li>
        <li><t>Named Sangam Das and Jijie Wei (varwof) in the Acknowledgments.</t></li>
      </ul>
    </section>

    <section anchor="acknowledgments" numbered="false" toc="default">
      <name>Acknowledgments</name>
      <t>
        The author thanks:
      </t>
      <t>
        Iman Schrock for clarifying the receipts/AEB execution boundary
        and bypass relationship.
      </t>
      <t>
        The in-process enforcement-placement case was raised by Jijie Wei
        (varwof). The enforcement-boundary definition and the bypass
        formulation are due to Sangam Das. The per-path identification and
        system-wide closure split, and the framing of the boundary as a
        role rather than a component, were settled with both on the WIMSE
        and OAuth lists in September 2026.
      </t>
    </section>
  </back>
</rfc>
