Skip to main content

An External Verifier Contract for Agent Authorization Decisions
draft-kondoju-evc-00

The information below is for an old version of the document.
Document Type
This is an older version of an Internet-Draft whose latest revision state is "Active".
Author Viswanadha Pratap Kondoju
Last updated 2026-07-21
RFC stream (None)
Formats
Stream Stream state (No stream defined)
Consensus boilerplate Unknown
RFC Editor Note (None)
IESG IESG state I-D Exists
Telechat date (None)
Responsible AD (None)
Send notices to (None)
draft-kondoju-evc-00
Individual Submission                                      V. P. Kondoju
Internet-Draft                                                    Bolyra
Intended status: Informational                              21 July 2026
Expires: 22 January 2027

    An External Verifier Contract for Agent Authorization Decisions
                          draft-kondoju-evc-00

Abstract

   This document specifies the External Verifier Contract (EVC): a
   small, testable, proof-system-agnostic boundary between a host (the
   program about to take a privileged action on an agent's behalf) and
   an external verifier (a subprocess that renders an allow/deny verdict
   on an opaque proof bundle).  The contract governs only the transport
   and verdict envelope: how the host hands a single JSON request to a
   verifier subprocess over stdin, how the verifier answers with exactly
   one JSON verdict on stdout, and how the host interprets exit codes,
   timeouts, and malformed output under a fail-closed rule.  Three
   properties make the boundary standardizable: (1) a single-shot
   subprocess transport with a closed JSON verdict schema; (2) fail-
   closed host semantics that are independently testable by a host-
   conformance suite; and (3) proof-system agnosticism, so the same
   envelope carries classical-signature, zero-knowledge, and third-party
   verdicts, distinguished only by an OPTIONAL self-description field.
   EVC is deliberately not a governance framework, not a delegation
   model, and not a policy language.  It is the narrow decision boundary
   those larger systems all require at the point of enforcement.

Status of This Memo

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

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

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

   This Internet-Draft will expire on 22 January 2027.

Kondoju                  Expires 22 January 2027                [Page 1]
Internet-Draft         External Verifier Contract              July 2026

Copyright Notice

   Copyright (c) 2026 IETF Trust and the persons identified as the
   document authors.  All rights reserved.

   This document is subject to BCP 78 and the IETF Trust's Legal
   Provisions Relating to IETF Documents (https://trustee.ietf.org/
   license-info) in effect on the date of publication of this document.
   Please review these documents carefully, as they describe your rights
   and restrictions with respect to this document.  Code Components
   extracted from this document must include Revised BSD License text as
   described in Section 4.e of the Trust Legal Provisions and are
   provided without warranty as described in the Revised BSD License.

Table of Contents

   1.  Introduction  . . . . . . . . . . . . . . . . . . . . . . . .   4
     1.1.  Problem . . . . . . . . . . . . . . . . . . . . . . . . .   4
     1.2.  What EVC Standardizes . . . . . . . . . . . . . . . . . .   4
     1.3.  What EVC Deliberately Excludes  . . . . . . . . . . . . .   4
     1.4.  Why Interoperable, Why Now  . . . . . . . . . . . . . . .   4
     1.5.  Positioning Versus Online Introspection
           (Non-Normative) . . . . . . . . . . . . . . . . . . . . .   5
   2.  Conventions and Requirements Language . . . . . . . . . . . .   5
     2.1.  Terminology . . . . . . . . . . . . . . . . . . . . . . .   5
   3.  Architecture and Trust Model  . . . . . . . . . . . . . . . .   6
     3.1.  Trust Boundaries  . . . . . . . . . . . . . . . . . . . .   7
     3.2.  Time Source . . . . . . . . . . . . . . . . . . . . . . .   7
   4.  Request and Verdict Envelopes . . . . . . . . . . . . . . . .   7
     4.1.  Host to Verifier Request (stdin)  . . . . . . . . . . . .   8
     4.2.  Verifier to Host Verdict (stdout) . . . . . . . . . . . .   9
       4.2.1.  Allow . . . . . . . . . . . . . . . . . . . . . . . .   9
       4.2.2.  Allow With Host-Owned Nonce Consumption . . . . . . .   9
       4.2.3.  Deny  . . . . . . . . . . . . . . . . . . . . . . . .  10
     4.3.  Denial-Code Registry  . . . . . . . . . . . . . . . . . .  11
     4.4.  stdout / stderr / fd-Level Isolation (Load-Bearing) . . .  12
   5.  Proof-System Classes and the kind Self-Description  . . . . .  14
     5.1.  Selective Disclosure as a First-Class, Optional Lane  . .  14
     5.2.  Normative Rules for kind  . . . . . . . . . . . . . . . .  15
     5.3.  Guardrails (Advisory Metadata, Not an Authorization
           Input)  . . . . . . . . . . . . . . . . . . . . . . . . .  15
     5.4.  Relationship to the Related APS Draft . . . . . . . . . .  16
   6.  Exit Codes, Timeouts, and Host Fail-Closed Obligations  . . .  16
     6.1.  Exit-Code Semantics (Verifier)  . . . . . . . . . . . . .  16
     6.2.  Exit Status Dominates a stdout Allow  . . . . . . . . . .  16
     6.3.  Timeout and Input Bounds  . . . . . . . . . . . . . . . .  17
     6.4.  The Fail-Closed Set . . . . . . . . . . . . . . . . . . .  17
   7.  Replay Protection and Reserve-Before-Act  . . . . . . . . . .  17

Kondoju                  Expires 22 January 2027                [Page 2]
Internet-Draft         External Verifier Contract              July 2026

     7.1.  Reserve-Before-Act (Host Nonce Mode)  . . . . . . . . . .  18
   8.  Host Conformance  . . . . . . . . . . . . . . . . . . . . . .  18
     8.1.  Misbehaving-Verifier Fixtures . . . . . . . . . . . . . .  19
     8.2.  Host-Under-Test (HUT) Convention  . . . . . . . . . . . .  22
     8.3.  Host Failure Classes  . . . . . . . . . . . . . . . . . .  23
     8.4.  Two Independent Reference Hosts and Scope Limits  . . . .  24
   9.  Wire Versioning . . . . . . . . . . . . . . . . . . . . . . .  25
   10. Relationship to Receipt Signer Discovery  . . . . . . . . . .  26
   11. Relationship to Other Work  . . . . . . . . . . . . . . . . .  26
     11.1.  draft-klrc-aiagent-auth-03 (Complementary) . . . . . . .  26
     11.2.  draft-pidlisnyi-aps-02 (Related -- Agent Passport
            System)  . . . . . . . . . . . . . . . . . . . . . . . .  27
   12. Implementation Status . . . . . . . . . . . . . . . . . . . .  28
     12.1.  Verifier: bolyra verify (production) . . . . . . . . . .  28
     12.2.  Reference hosts (two languages)  . . . . . . . . . . . .  28
     12.3.  Hosted verifier (preview)  . . . . . . . . . . . . . . .  29
     12.4.  Independent implementation experience  . . . . . . . . .  29
   13. Security Considerations . . . . . . . . . . . . . . . . . . .  29
     13.1.  Fail-Closed Is the Whole Safety Property . . . . . . . .  29
     13.2.  kind Is Unauthenticated Self-Report  . . . . . . . . . .  29
     13.3.  Nonce Reservation and Replay . . . . . . . . . . . . . .  30
     13.4.  Redirect and Downgrade in Signer Discovery . . . . . . .  30
     13.5.  Output-Bound Truncation and Resource Exhaustion  . . . .  30
     13.6.  Tail Truncation of Hash-Chained Receipts
            (Informative)  . . . . . . . . . . . . . . . . . . . . .  30
     13.7.  Domain Separation  . . . . . . . . . . . . . . . . . . .  31
     13.8.  Binding of Credential Expiry . . . . . . . . . . . . . .  31
     13.9.  Privacy Considerations . . . . . . . . . . . . . . . . .  32
   14. IANA Considerations . . . . . . . . . . . . . . . . . . . . .  32
     14.1.  External Verifier Denial Codes Registry  . . . . . . . .  32
     14.2.  Proof-System Classes: Closed Enum, No Registry
            Requested  . . . . . . . . . . . . . . . . . . . . . . .  33
     14.3.  Deferred Registrations . . . . . . . . . . . . . . . . .  33
   15. References  . . . . . . . . . . . . . . . . . . . . . . . . .  34
     15.1.  Normative References . . . . . . . . . . . . . . . . . .  34
     15.2.  Informative References . . . . . . . . . . . . . . . . .  34
   Appendix A.  Request and Verdict JSON Schemas (Normative) . . . .  35
     A.1.  Request Schema  . . . . . . . . . . . . . . . . . . . . .  35
     A.2.  Verdict Schema (Closed) . . . . . . . . . . . . . . . . .  36
   Appendix B.  Binding Signature (Informative)  . . . . . . . . . .  38
   Appendix C.  Worked Examples (Informative)  . . . . . . . . . . .  39
     C.1.  Allow (Local Nonce Mode)  . . . . . . . . . . . . . . . .  39
     C.2.  Allow With consume_nonces (Host Nonce Mode) . . . . . . .  40
     C.3.  Deny -- scope_exceeded  . . . . . . . . . . . . . . . . .  40
     C.4.  Deny -- model_mismatch  . . . . . . . . . . . . . . . . .  40
     C.5.  Deny -- malformed_input . . . . . . . . . . . . . . . . .  40
     C.6.  Deny -- internal_error (Fail-Closed, Non-Zero Exit) . . .  41
     C.7.  Allow -- classical-kind Verifier  . . . . . . . . . . . .  41

Kondoju                  Expires 22 January 2027                [Page 3]
Internet-Draft         External Verifier Contract              July 2026

     C.8.  Deny -- external-kind Verifier  . . . . . . . . . . . . .  41
   Appendix D.  Host-Conformance Fixture List (Informative)  . . . .  41
   Author's Address  . . . . . . . . . . . . . . . . . . . . . . . .  41

1.  Introduction

1.1.  Problem

   Agent frameworks -- Model Context Protocol (MCP) hosts,
   orchestrators, and gateways -- increasingly need to gate a privileged
   tool call on a single question: is this agent actually authorized to
   take this action right now?  Today each host reimplements a bespoke
   check and couples itself to one proof format.  There is no
   interoperable, language-neutral boundary at which a host can ask an
   external verifier -- one the host did not build -- for a decision.

1.2.  What EVC Standardizes

   EVC standardizes the transport and verdict envelope only.
   Specifically: how the host hands a request to a verifier subprocess
   (one JSON object on stdin, then EOF), how the verifier answers
   (exactly one JSON verdict on stdout), how exit codes and timeouts are
   interpreted, and the fail-closed obligations that bind the host.  The
   proof bundle itself is opaque to the host: only a verifier vendor
   needs to understand its internal structure.  A host adopts the
   contract by learning four steps (Section 3), not a proof format.

1.3.  What EVC Deliberately Excludes

   EVC does not define credential issuance, the internal proof format,
   policy authoring, delegation semantics, revocation infrastructure, or
   any PKI.  These belong to the verifier vendor or to adjacent
   specifications.  Keeping the boundary this small is the design
   thesis: it is what makes the contract testable rather than
   aspirational.  Every normative obligation in this document is placed
   either on the host or on a conforming verifier at the wire boundary,
   and nothing below it.

1.4.  Why Interoperable, Why Now

   The contract is not tied to one runtime.  Two independent reference
   hosts already exist -- one in JavaScript ("spec/reference-host.js")
   and one in Rust ("spec/reference-host-rs/") -- demonstrating that the
   host obligations can be implemented and tested in more than one
   language.  The maintainer of a separate MCP project
   (mcp_agent_mail_rust) built an off-by-default Ed25519 registration-
   proof gate designed to this contract's v1 boundary (issue #183
   defines the corresponding spawnable-verifier slot shape); that gate

Kondoju                  Expires 22 January 2027                [Page 4]
Internet-Draft         External Verifier Contract              July 2026

   exercises the contract's request/verdict surface without depending on
   any Bolyra component.  The host obligations are mechanically testable
   by a conformance suite (Section 8), so a host's fail-closed behavior
   is a checkable property rather than an assertion.  See also the
   Implementation Status section (Section 12).

1.5.  Positioning Versus Online Introspection (Non-Normative)

   A verdict is produced offline from a self-contained bundle plus host-
   supplied context and the host's wall-clock; there is no round-trip to
   an authorization server.  This is a deliberate contrast with, not a
   competitor to, OAuth 2.0 token introspection [RFC7662], which
   resolves a token's state through an online request to an
   introspection endpoint.  EVC and online introspection can coexist: a
   bundle can carry a token that a classical-class verifier (Section 5)
   validates by whatever means it chooses, including an out-of-band
   introspection call, but the host↔verifier decision boundary itself is
   a single local subprocess exchange.

2.  Conventions and Requirements Language

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

   The wire representations in this document are JSON [RFC8259].

2.1.  Terminology

   host:  The program about to take a privileged action on an agent's
      behalf.  The host spawns the verifier, supplies the request, and
      enforces the verdict fail-closed.

   verifier:  A program the host spawns as a subprocess to decide
      whether a bundle authorizes a privileged action.  It reads one
      request on stdin and writes one verdict on stdout.

   bundle:  An opaque proof string carried in the request.  It is opaque
      to the host: the host MUST NOT inspect, rewrite, or normalize it.
      Only a verifier vendor understands its internal structure.

   verdict:  The single JSON object the verifier writes to stdout: an
      allow, an allow with host-owned nonce consumption, or a deny with
      a code and message.

   request:  The single JSON object the host writes to the verifier's

Kondoju                  Expires 22 January 2027                [Page 5]
Internet-Draft         External Verifier Contract              July 2026

      stdin, describing the privileged action it is about to authorize.

   decision:  The host's enforced outcome (proceed or reject) after
      applying the verdict and all fail-closed obligations.

   proof-system class:  The cryptographic family a verifier implements,
      one of "classical", "zk", or "external" (Section 5).  Self-
      described by the OPTIONAL verdict field "kind".

   host nonce mode / local nonce mode:  The two replay-protection modes
      (Section 7).  In local mode the verifier owns the durable nonce
      store; in host mode the verifier returns nonces for the host to
      reserve.

   reserve-before-act:  The host obligation, in host nonce mode, to
      durably reserve every returned nonce with unique-insert semantics
      before performing the privileged action (Section 7).

   Host-Under-Test (HUT):  A host driven by the conformance runner
      through the environment convention of Section 8, so hosts in any
      language can be tested against the same fixtures.

3.  Architecture and Trust Model

   The host↔verifier boundary is a single-shot subprocess protocol of
   four steps:

   1.  The host spawns the verifier command.

   2.  The host writes exactly one JSON request object to the verifier's
       stdin, then closes (EOF) stdin.

   3.  The verifier writes exactly one JSON verdict object to its stdout
       and exits.

   4.  The host reads that one verdict and enforces it fail-closed
       (Section 6).

   Language-neutral pseudocode for the host side:

Kondoju                  Expires 22 January 2027                [Page 6]
Internet-Draft         External Verifier Contract              July 2026

child   = spawn(verifier_cmd, timeout = 10_000ms)
write(child.stdin, json(request)); close(child.stdin)
status, out, err = wait(child)                 // out = stdout, err = stderr

if status != 0:                               reject("non-zero exit")   // Sec 6
if timed_out(child):                          reject("timeout")         // Sec 6
verdict = parse_single_json_object(out)        // Sec 4; on any failure -> reject
if verdict is null:                            reject("unparseable stdout")

switch verdict.verdict:
  case "allow":
    for entry in (verdict.consume_nonces or []):        // Sec 7 host mode
      if not reserve_nonce_atomically(entry): reject("replay")   // reserve ALL
    proceed()
  case "deny":
    reject(verdict.code)                        // branch on Sec 4.2 registry
  default:
    reject("unknown verdict")

   The contract is host-agnostic and language-agnostic: a host in any
   ecosystem MAY implement or consume the contract without depending on
   any verifier's internals.

3.1.  Trust Boundaries

   The host trusts exactly two things: (a) the verifier command it
   configured -- which binary and argument vector it spawned -- and (b)
   the trust configuration it supplied to that verifier, such as trusted
   roots, a capability map, or a signer list.  The host does NOT trust
   the bundle, and it MUST NOT infer trust from any self-reported field
   in the verdict, notably the "kind" self-description (Section 5).

   The verifier trusts its configured roots and policy.  "No roots
   configured" is never "all roots trusted": a verifier that has no
   trust source configured MUST fail closed rather than accept anything.

3.2.  Time Source

   The host owns the clock.  The request field "now_unix" is host-
   supplied, and the verifier MUST evaluate credential expiry against
   that value rather than its own clock.  This is an explicit trust-
   model property: expiry is judged against the host's wall-clock, so
   the host, not the verifier, is authoritative about the current time.

4.  Request and Verdict Envelopes

   This section transcribes the wire envelopes.  The full JSON Schemas
   appear normatively in Appendix A.

Kondoju                  Expires 22 January 2027                [Page 7]
Internet-Draft         External Verifier Contract              July 2026

4.1.  Host to Verifier Request (stdin)

   The host MUST write exactly one JSON object, UTF-8 encoded, to the
   verifier's stdin, and then MUST close (EOF) stdin.  The host MUST NOT
   write more than one object, trailing bytes, or a stream of objects.

   {
     "version": 1,
     "bundle": "<opaque proof string>",
     "request": {
       "agent_name": "research-bot",
       "project_key": "/work/acme/research",
       "program": "crewai",
       "model": "opus-4.1",
       "granted_capabilities": ["fetch_inbox", "send_message"]
     },
     "now_unix": 1751990400
   }

   Field requirements:

   *  "version" (integer, REQUIRED) -- envelope version of the host
      request.  A verifier that implements this contract MUST support
      "1" and MUST reject any other value with "deny
      code=unsupported_version" (Section 4.2).  Negotiation is by major
      only (Section 10).

   *  "bundle" (string, REQUIRED, non-empty) -- opaque to the host.  The
      verifier owns its internal structure.  The host MUST NOT inspect,
      rewrite, or normalize it.

   *  "request" (object, REQUIRED) -- the privileged action the host is
      about to authorize.  It MUST contain:

      -  "agent_name" (string, REQUIRED).

      -  "project_key" (string, REQUIRED) -- compared literally, byte-
         for-byte, against the bundle's signed binding.  The verifier
         MUST NOT apply path canonicalization (no ".." resolution, no
         symlink resolution): "a/../b" and "b" are distinct keys.  This
         byte-literal rule is a subtle interoperability hazard and is
         stated explicitly to prevent a verifier from silently
         normalizing a project key.

      -  "program" (string, REQUIRED).

      -  "model" (string, REQUIRED).

Kondoju                  Expires 22 January 2027                [Page 8]
Internet-Draft         External Verifier Contract              July 2026

      -  "granted_capabilities" (array of strings, REQUIRED) -- host-
         defined capability tokens the host intends to grant.  They are
         opaque strings that the verifier maps to its internal
         permission model.  An empty array is permitted.

   *  "now_unix" (integer, REQUIRED, positive) -- the host's current
      wall-clock time in seconds since the Unix epoch.  The verifier
      MUST evaluate credential expiry against this value, not against
      its own clock.

   A request that is not a JSON object, is missing a REQUIRED field, or
   has a field of the wrong type MUST yield "deny code=malformed_input".
   A well-formed request whose "version" is not "1" MUST yield "deny
   code=unsupported_version".

4.2.  Verifier to Host Verdict (stdout)

   The verifier MUST write exactly one JSON object to stdout and nothing
   else (Section 4.3 governs this strictly).  The verdict is one of
   three shapes.

4.2.1.  Allow

   { "verdict": "allow" }

4.2.2.  Allow With Host-Owned Nonce Consumption

   Emitted only in host nonce mode (Section 7).  It instructs the host
   to durably record each one-time nonce so the same proof cannot be
   replayed.  A presentation can carry more than one nonce to reserve --
   for example the agent nullifier plus a human-uniqueness nullifier
   when the bundle is human-backed (the human entry's "nonce" is
   namespaced "human:NULLIFIER-HASH").

   {
     "verdict": "allow",
     "consume_nonces": [
       {
         "issuer_key": "15617329...:20201653...",
         "nonce": "12616665119450508255185458876855962314592339945640375882344193391684757282246",
         "retain_until": 4102444800
       }
     ]
   }

Kondoju                  Expires 22 January 2027                [Page 9]
Internet-Draft         External Verifier Contract              July 2026

   "consume_nonces" (array of objects, OPTIONAL, allow-only).  When
   present it is a non-empty list (the key is omitted entirely when
   there is nothing to burn).  The host MUST reserve-before-act EACH
   entry (Section 7).  Each entry:

   *  "issuer_key" (string, REQUIRED) -- the issuer/operator key that
      scopes the nonce namespace, for host bookkeeping only; the "nonce"
      is already globally unique (Section 7).

   *  "nonce" (string, REQUIRED) -- the one-time value the host MUST
      record.

   *  "retain_until" (integer, REQUIRED) -- Unix seconds until which the
      host MUST retain the consumed nonce.

4.2.3.  Deny

   {
     "verdict": "deny",
     "code": "scope_exceeded",
     "message": "required scope exceeds the credential effective scope",
     "detail": { "required_scope": "2", "effective_scope": "1", "excess_bits": "2" }
   }

   *  "verdict" (string, REQUIRED) -- "allow" or "deny".

   *  "code" (string, REQUIRED on deny) -- a stable machine token from
      the registry in Section 4.2.  The host MAY branch on it.

   *  "message" (string, REQUIRED on deny) -- a one-line, human-
      actionable reason.  It MUST NOT contain secrets.

   *  "detail" (object, OPTIONAL) -- structured context for logs/
      debugging.  A verifier SHOULD echo the originating internal error
      code here so the coarse wire "code" stays stable while logs stay
      precise.

   *  "kind" (string, OPTIONAL) -- the verifier's proof-system self-
      description, one of "classical", "zk", or "external" (Section 5).
      It MAY appear on either an "allow" or a "deny" verdict.  When
      absent, the host MUST interpret the verdict as "zk".  A verifier
      whose class is not "zk" MUST set "kind" explicitly, because
      omission is read as "zk".

Kondoju                  Expires 22 January 2027               [Page 10]
Internet-Draft         External Verifier Contract              July 2026

4.3.  Denial-Code Registry

   The stable, lowercase snake_case vocabulary for the verdict "code"
   field.  This table is the single normative source; hosts MAY branch
   on these tokens and MUST treat an unrecognized future "code" as deny.
   Verifiers MUST NOT add, remove, or rename a code without a wire-major
   version bump (Section 10).  The registry is proof-system-agnostic
   (Section 5): a "classical"- or "external"-kind verifier reuses the
   same codes -- for example "invalid_proof" covers a failed classical
   signature or token check as well as a failed zero-knowledge
   verification.

    +=====================+===========================================+
    | code                | Meaning                                   |
    +=====================+===========================================+
    | malformed_input     | stdin missing / oversized (> 1 MiB) / not |
    |                     | JSON, or a required request field is      |
    |                     | missing or ill-typed.                     |
    +---------------------+-------------------------------------------+
    | unsupported_version | host request "version" is not "1", or the |
    |                     | bundle's internal version is unsupported. |
    +---------------------+-------------------------------------------+
    | invalid_bundle      | the opaque "bundle" is undecodable, not   |
    |                     | an object, or structurally wrong.         |
    +---------------------+-------------------------------------------+
    | invalid_proof       | envelope validation or proof verification |
    |                     | failed, the mandatory verification-key    |
    |                     | pin is absent/mismatched, or the          |
    |                     | credential-to-proof anchoring failed.     |
    +---------------------+-------------------------------------------+
    | untrusted_root      | a proof's trust anchor (agent, human, or  |
    |                     | delegatee) is not in the configured       |
    |                     | trusted-root source.                      |
    +---------------------+-------------------------------------------+
    | delegation_invalid  | a delegation chain break, hop-cap         |
    |                     | overflow, scope expansion, or expiry      |
    |                     | expansion.                                |
    +---------------------+-------------------------------------------+
    | invalid_signature   | the request-binding signature             |
    |                     | (Appendix B) did not verify against the   |
    |                     | proven operator key.                      |
    +---------------------+-------------------------------------------+
    | request_mismatch    | a host "request" field does not match the |
    |                     | signed binding, or "granted_capabilities" |
    |                     | are not covered by the binding's          |
    |                     | capabilities.                             |
    +---------------------+-------------------------------------------+
    | model_mismatch      | the proof's committed model does not      |

Kondoju                  Expires 22 January 2027               [Page 11]
Internet-Draft         External Verifier Contract              July 2026

    |                     | equal the requested model -- the proof is |
    |                     | for a different model.                    |
    +---------------------+-------------------------------------------+
    | unknown_capability  | a requested capability has no scope       |
    |                     | mapping (fail-closed; unmapped is never   |
    |                     | silently allowed).                        |
    +---------------------+-------------------------------------------+
    | scope_exceeded      | the capability-required permission bits   |
    |                     | are not a cumulative-bit subset of the    |
    |                     | proven effective scope.                   |
    +---------------------+-------------------------------------------+
    | expired             | proof-anchored "now_unix >=               |
    |                     | effective_expiry" -- a strict comparison; |
    |                     | the equality boundary is rejected.        |
    +---------------------+-------------------------------------------+
    | nonce_missing       | the proof lacks a usable nullifier /      |
    |                     | session-nonce signal.                     |
    +---------------------+-------------------------------------------+
    | nonce_replayed      | the proof's one-time nonce was already    |
    |                     | seen (local mode).                        |
    +---------------------+-------------------------------------------+
    | internal_error      | unexpected failure, missing artifacts, or |
    |                     | missing trust configuration.  Emitted as  |
    |                     | a deny AND with a non-zero exit code.     |
    +---------------------+-------------------------------------------+

                                  Table 1

   All rows are terminal denies.  Only "internal_error" additionally
   sets a non-zero exit code (Section 6).  This 15-code table is the
   candidate IANA registry (Section 13).

4.4.  stdout / stderr / fd-Level Isolation (Load-Bearing)

   The whole contract depends on stdout carrying exactly one JSON object
   and nothing else.  This is the single most fragile part of the
   implementation and is normative for both sides.

   Verifier obligations:

   *  The verifier MUST write exactly one serialized verdict to the
      host-facing stdout, at the very end.  A single trailing newline is
      OPTIONAL.

Kondoju                  Expires 22 January 2027               [Page 12]
Internet-Draft         External Verifier Contract              July 2026

   *  No other code path may write to the host-facing stdout.  Proof-
      verification libraries (native, WASM, or otherwise) can write to
      file descriptor 1 directly -- progress bars, warnings, debug
      output -- bypassing any language-level stdout wrapper.  A
      "console.log"-style monkeypatch is therefore insufficient: it
      misses native writes.

   *  A conforming verifier MUST separate verification from verdict-
      emission at the file-descriptor level.  The reference mechanism is
      process isolation with a private verdict channel: the command
      spawns a worker whose stdio binds fd 0 = request in, fd 1 =
      captured (everything native writes here), fd 2 = inherited parent
      stderr, and fd 3 = the private verdict channel.  Any raw fd-1
      writes inside the worker MUST be captured by the parent and
      forwarded to the parent's stderr, and MUST NOT reach the host-
      facing stdout.  The worker emits the single verdict only on fd 3;
      the parent performs the sole write to fd 1.

   *  If the private verdict channel cannot be established on the
      platform, the verifier MUST fail closed ("deny
      code=internal_error", non-zero exit).  It MUST NOT silently fall
      back to sharing fd 1.  An in-process fd-redirect realization is
      equally acceptable where the platform exposes the primitive; the
      process-isolation form is the reference implementation and is
      strictly stronger.

   Host obligations (single-object parse):

   The host MUST parse the verifier's stdout as exactly one JSON object
   with no trailing bytes, and MUST reject (treat as deny, Section 6)
   any of:

   *  stdout that is empty, not valid JSON, or contains a leading/
      trailing prefix or suffix around the object;

   *  multiple concatenated JSON values (a lenient parser that reads
      only the first object MUST NOT be used -- two objects is a fail-
      closed condition);

   *  a verdict whose "verdict" field is neither "allow" nor "deny", or
      a "deny" missing "code"/"message".

   All diagnostics, timing, and debug logging MUST go to stderr.  Hosts
   MUST NOT parse stderr for the verdict.

Kondoju                  Expires 22 January 2027               [Page 13]
Internet-Draft         External Verifier Contract              July 2026

5.  Proof-System Classes and the kind Self-Description

   Because the contract is proof-system-agnostic (Section 1), the same
   wire envelope carries verdicts from verifiers built on different
   cryptographic foundations: a classical-signature verifier, a zero-
   knowledge verifier, and a third-party verifier all speak the
   identical request and verdict schema and differ only in which command
   the host spawns.  This is the property that lets a host adopt
   privacy-preserving verification without changing its wire contract.

   The OPTIONAL verdict field "kind" lets a verifier declare which class
   it implements, so a host that spawns a verifier it did not build --
   or that fans a request across several verifiers -- can record and, if
   it chooses, branch on the class of proof that produced the decision.
   "kind" takes exactly one of three values:

   +===========+============================+=========================+
   | kind      | Proof-system class         | Examples                |
   +===========+============================+=========================+
   | classical | Classical public-key       | ES256K-signed receipts, |
   |           | crypto -- signatures,      | JWT delegation tokens,  |
   |           | tokens, policy/nonce       | capability/scope and    |
   |           | checks, no zero-knowledge. | replay-nonce checks.    |
   +-----------+----------------------------+-------------------------+
   | zk        | Zero-knowledge circuit     | Zero-knowledge proofs   |
   |           | proofs.                    | for private delegation, |
   |           |                            | credential predicates,  |
   |           |                            | human-uniqueness        |
   |           |                            | nullifiers.             |
   +-----------+----------------------------+-------------------------+
   | external  | A third-party verifier     | A vendor verifier the   |
   |           | implementing this contract | host adopts without     |
   |           | with its own proof system. | depending on any        |
   |           |                            | specific internals.     |
   +-----------+----------------------------+-------------------------+

                                 Table 2

5.1.  Selective Disclosure as a First-Class, Optional Lane

   A "zk"-class verifier proves properties of a credential -- scope,
   expiry, a human-uniqueness nullifier, delegation narrowing -- without
   revealing the agent's identity or the underlying attribute values.
   This selective-disclosure capability is one that no signature-only
   contract can express, because a classical signature necessarily
   discloses the signed content it authenticates.

Kondoju                  Expires 22 January 2027               [Page 14]
Internet-Draft         External Verifier Contract              July 2026

   The normative point is deliberately narrow: EVC does NOT require
   zero-knowledge verification, but it reserves a first-class lane for
   it via "kind=zk".  A host that wants privacy-preserving verification
   and a host that wants a plain signature check speak the identical
   wire protocol and differ only in which verifier command they spawn.
   The privacy property is a property of the verifier the host
   configures, not of the wire contract.

5.2.  Normative Rules for kind

   *  A verifier SHOULD set "kind" to the class it implements.

   *  A verifier that implements this contract as a "classical"- or
      "external"-class verifier MUST set "kind" explicitly, because
      omission is read as "zk".  A verifier MUST NOT emit a "kind" value
      outside the three-member set above; a host MUST treat an
      unrecognized "kind" as a malformed verdict and fail closed
      (Section 6).

   *  For backward compatibility, a verdict without "kind" MUST be
      interpreted by the host as "zk".  A "zk"-class verifier MAY
      therefore omit the field.  Because a host validating against the
      original closed verdict schema rejects any "kind"-bearing verdict
      (Section 10), a "zk"-class verifier that must remain interoperable
      with such a host SHOULD omit "kind" rather than emit "kind":"zk".

5.3.  Guardrails (Advisory Metadata, Not an Authorization Input)

   "kind" is advisory metadata about provenance, and it is self-reported
   by the verifier and NOT authenticated.  The "verdict", "code", and
   (in host nonce mode) "consume_nonces" fields are the sole enforcement
   surface.  A host:

   *  MUST NOT upgrade a "deny" to an "allow" on the basis of "kind";

   *  MUST NOT relax any Section 6 fail-closed obligation, schema
      validation, verifier selection, or nonce handling on the basis of
      "kind";

   *  MUST establish a verifier's proof-system class from its configured
      verifier identity or policy -- which command it spawned and the
      trust configuration it supplied -- never from the self-reported
      "kind" string alone.

Kondoju                  Expires 22 January 2027               [Page 15]
Internet-Draft         External Verifier Contract              July 2026

   A host MAY use "kind" for logging, or to deny more strictly on a
   mismatch between the expected and reported class.  An unrecognized
   "kind" is a malformed verdict and MUST fail closed (Section 6).  The
   threat guarded against is a hostile or buggy verifier claiming
   "kind=zk" to imply privacy guarantees it did not provide.

5.4.  Relationship to the Related APS Draft

   The related draft [I-D.pidlisnyi-aps] does not define a zero-
   knowledge or selective-disclosure verifier class (Section 12.2).
   EVC's "kind" lane is where privacy-preserving verification plugs into
   an otherwise-identical host contract, regardless of which framework
   produced the bundle.

6.  Exit Codes, Timeouts, and Host Fail-Closed Obligations

6.1.  Exit-Code Semantics (Verifier)

   *  Exit "0" -- a verdict object was produced ("allow" or any policy/
      crypto "deny").  The host reads stdout for the decision.

   *  Exit non-zero -- the verifier could not produce a trustworthy
      verdict at all (could not read stdin, could not load required
      artifacts or trust configuration, catastrophic internal failure).
      Decision-level outcomes (invalid proof, expired, replay, mismatch)
      are NOT errors: they exit "0" with "verdict=deny".

   *  The one nuance: "internal_error" is emitted as "deny
      code=internal_error" AND exits non-zero, so the host both sees a
      machine reason and fail-closes.

6.2.  Exit Status Dominates a stdout Allow

   A host MUST treat a non-zero exit as deny even when a syntactically
   valid "allow" was written to stdout.  Exit status is part of the
   contract; whenever it is non-zero the host MUST ignore stdout for the
   purpose of the decision.  This is the one place an implementer may be
   tempted to trust stdout, and it is stated explicitly to close that
   temptation.  No non-zero exit -- including "internal_error" -- ever
   yields an allow.

Kondoju                  Expires 22 January 2027               [Page 16]
Internet-Draft         External Verifier Contract              July 2026

6.3.  Timeout and Input Bounds

   The host owns the timeout; the verifier does not implement its own.
   The host MUST enforce a wall-clock timeout on the spawned process and
   treat expiry as deny.  The RECOMMENDED timeout is 10 000 ms (10 s); a
   conforming verifier targets under 2 s at the 99th percentile, so 10 s
   leaves ample headroom.  The verifier MUST bound the request read from
   stdin; the reference limit is 1 MiB (1 048 576 bytes).  A request
   over the bound MUST yield "deny code=malformed_input"; the verifier
   MUST NOT buffer an unbounded request.

6.4.  The Fail-Closed Set

   The host MUST treat ALL of the following as deny, regardless of what
   (if anything) reached stdout:

   *  non-zero exit code;

   *  timeout -- the host MUST kill the process and deny;

   *  death by signal / crash;

   *  unparseable, empty, oversized, or multi-object stdout
      (Section 4.3);

   *  an unknown "verdict" value or a "deny" missing required fields;

   *  a verdict that otherwise fails the verdict schema (Appendix A) --
      including an unrecognized "kind" value or any disallowed
      additional property.

   The verifier is designed so these are the only ways it fails
   ambiguously; every outcome it can reason about is an explicit "deny"
   with a "code" (Section 4.2).

7.  Replay Protection and Reserve-Before-Act

   A verifier supports one of two replay modes, selected by the host at
   spawn time.

   *  local (default).  The verifier owns durable replay state.  On an
      otherwise-allow it burns the proof's one-time nonce locally; a
      second presentation of the same proof yields "deny
      code=nonce_replayed".  Suitable for a single-host deployment.  If
      the store errors, the verifier MUST fail closed ("deny
      code=internal_error", non-zero exit).

Kondoju                  Expires 22 January 2027               [Page 17]
Internet-Draft         External Verifier Contract              July 2026

   *  host.  The verifier does not persist nonces.  On an otherwise-
      allow it returns "consume_nonces" (Section 4.2), one entry per
      one-time nullifier the presentation carries (the agent nullifier,
      plus the human-uniqueness nullifier for a human-backed bundle),
      and the host owns durable storage under the reserve-before-act
      rule.  This is the mode for multi-host or clustered deployments
      where the host already owns a database.

   The agent nonce value is globally unique per (credential, session-
   nonce), so no separate operator namespacing is needed;
   "consume_nonces[].issuer_key" is provided for host-side bookkeeping
   only.  Delegation hops add no entry: each per-hop delegation
   nullifier is bound to the agent's session nonce, so reserving the
   agent nullifier already covers delegation replay.  The human-
   uniqueness nullifier for a human-backed bundle is carried as a second
   reserved entry, namespaced "human:NULLIFIER-HASH".

7.1.  Reserve-Before-Act (Host Nonce Mode)

   When the host runs the verifier in host nonce mode and receives
   "allow" with "consume_nonces", the host MUST reserve every entry in
   the list before performing the privileged action:

   1.  For each entry in "consume_nonces", atomically insert
       "entry.nonce" into durable storage with a unique-insert / "on
       conflict reject" semantic, retaining it until
       "entry.retain_until".

   2.  If all inserts are novel, proceed with the action.

   3.  If any insert conflicts (that nonce was already recorded), the
       host MUST reject the action as a replay -- even though the
       verifier returned "allow".

   "Record after proceeding" is a replay window and is FORBIDDEN.  The
   verifier's "allow" in host mode is conditional on every host insert
   being novel.

8.  Host Conformance

   The differentiator between this contract and a governance framework
   is that the host obligations are mechanically testable.  Sections 4,
   6, and 7 place the load-bearing obligations on the host: it owns the
   timeout, the single-object stdout parse, the exit-code
   interpretation, and reserve-before-act nonce consumption.  A verifier
   that follows the contract is not enough -- a host that trusts a
   misbehaving verifier is the actual vulnerability.  This section makes
   those host obligations testable.

Kondoju                  Expires 22 January 2027               [Page 18]
Internet-Draft         External Verifier Contract              July 2026

   The suite spawns a deliberately misbehaving verifier fixture in place
   of a real verifier and asserts that the host-under-test fails closed.
   A conforming host MUST deny in every non-positive case below.

8.1.  Misbehaving-Verifier Fixtures

   The fixtures are small, dependency-free scripts, each violating
   exactly one contract obligation.  The "Required host outcome" column
   is the normative behavior a conforming host MUST produce; the "Class"
   column is the reference host's canonical failure classification
   (Section 8.3).  The inventory below is 26 fixtures that drive 27
   "host_behavior" vectors, confirmed in "spec/test-vectors.json": the
   "allow-consume-nonces.js" fixture drives two vectors (a novel-reserve
   allow and a replay deny), so the fixture count is one fewer than the
   vector count.

   +===========+==================+==============+====================+
   |Fixture    | Violates         | Required     | Class              |
   |           |                  | host outcome |                    |
   +===========+==================+==============+====================+
   |well-      | -- (positive     | relay allow  | --                 |
   |behaved-   | control)         |              |                    |
   |allow      |                  |              |                    |
   +-----------+------------------+--------------+--------------------+
   |well-      | -- (positive     | relay deny   | --                 |
   |behaved-   | control)         | code=expired |                    |
   |deny       |                  |              |                    |
   +-----------+------------------+--------------+--------------------+
   |well-      | -- (positive     | relay allow  | --                 |
   |behaved-   | control, "kind") |              |                    |
   |allow-kind-|                  |              |                    |
   |classical  |                  |              |                    |
   +-----------+------------------+--------------+--------------------+
   |well-      | -- (positive     | relay allow  | --                 |
   |behaved-   | control, "kind") |              |                    |
   |allow-kind-|                  |              |                    |
   |external   |                  |              |                    |
   +-----------+------------------+--------------+--------------------+
   |non-json-  | stdout not valid | deny         | unparseable_stdout |
   |stdout     | JSON             |              |                    |
   +-----------+------------------+--------------+--------------------+
   |multiple-  | two concatenated | deny         | multiple_objects | |
   |objects    | verdicts         |              | unparseable_stdout |
   +-----------+------------------+--------------+--------------------+
   |schema-    | "verdict"        | deny         | schema_invalid     |
   |invalid-   | neither allow    |              |                    |
   |verdict    | nor deny         |              |                    |
   +-----------+------------------+--------------+--------------------+

Kondoju                  Expires 22 January 2027               [Page 19]
Internet-Draft         External Verifier Contract              July 2026

   |deny-      | "deny" missing   | deny         | schema_invalid     |
   |missing-   | code/message     |              |                    |
   |fields     |                  |              |                    |
   +-----------+------------------+--------------+--------------------+
   |allow-     | valid allow then | deny         | unparseable_stdout |
   |trailing-  | trailing bytes   |              |                    |
   |garbage    |                  |              |                    |
   +-----------+------------------+--------------+--------------------+
   |binary-    | raw non-UTF-8    | deny         | unparseable_stdout |
   |garbage-   | bytes on stdout  |              |                    |
   |stdout     |                  |              |                    |
   +-----------+------------------+--------------+--------------------+
   |leading-   | non-JSON prefix  | deny         | unparseable_stdout |
   |garbage    | before a valid   |              |                    |
   |           | allow            |              |                    |
   +-----------+------------------+--------------+--------------------+
   |allow-     | closed schema -- | deny         | schema_invalid     |
   |extra-     | disallowed       |              |                    |
   |property   | additional       |              |                    |
   |           | property         |              |                    |
   +-----------+------------------+--------------+--------------------+
   |bad-kind   | "kind" outside   | deny         | schema_invalid     |
   |           | classical/zk/    |              |                    |
   |           | external         |              |                    |
   +-----------+------------------+--------------+--------------------+
   |empty-     | "consume_nonces" | deny         | schema_invalid     |
   |consume-   | violates         |              |                    |
   |nonces     | minItems:1       |              |                    |
   +-----------+------------------+--------------+--------------------+
   |malformed- | nonce entry      | deny         | schema_invalid     |
   |consume-   | missing          |              |                    |
   |nonce      | issuer_key/      |              |                    |
   |           | retain_until     |              |                    |
   +-----------+------------------+--------------+--------------------+
   |deny-extra-| closed "deny" -- | deny         | schema_invalid     |
   |property   | disallowed       |              |                    |
   |           | additional       |              |                    |
   |           | property         |              |                    |
   +-----------+------------------+--------------+--------------------+
   |nonce-     | nonce entry has  | deny         | schema_invalid     |
   |entry-     | an extra         |              |                    |
   |extra-     | property         |              |                    |
   |property   |                  |              |                    |
   +-----------+------------------+--------------+--------------------+
   |nonce-     | nonce entry      | deny         | schema_invalid     |
   |entry-     | retain_until not |              |                    |
   |wrong-type | an integer       |              |                    |
   +-----------+------------------+--------------+--------------------+

Kondoju                  Expires 22 January 2027               [Page 20]
Internet-Draft         External Verifier Contract              July 2026

   |no-output- | no output, never | deny (kill   | timeout            |
   |hang       | exits            | on timeout)  |                    |
   +-----------+------------------+--------------+--------------------+
   |partial-   | partial verdict  | deny (kill   | timeout            |
   |json-hang  | then hangs       | on timeout)  |                    |
   +-----------+------------------+--------------+--------------------+
   |slow-allow-| valid allow, but | deny (kill   | timeout            |
   |past-      | only after the   | on timeout)  |                    |
   |deadline   | deadline         |              |                    |
   +-----------+------------------+--------------+--------------------+
   |nonzero-   | valid allow on   | deny         | nonzero_exit       |
   |exit-after-| stdout but non-  | (Section     |                    |
   |allow      | zero exit        | 6.2)         |                    |
   +-----------+------------------+--------------+--------------------+
   |killed-by- | death by signal, | deny         | signal_death |     |
   |signal     | no verdict       |              | unparseable_stdout |
   +-----------+------------------+--------------+--------------------+
   |oversize-  | floods stdout    | deny (bound  | oversize_stdout    |
   |flood      | past the output  | + kill)      |                    |
   |           | bound            |              |                    |
   +-----------+------------------+--------------+--------------------+
   |allow-     | -- (drives       | reserve then | replay on conflict |
   |consume-   | reserve-before-  | allow, or    |                    |
   |nonces     | act)             | deny on      |                    |
   |           |                  | replay       |                    |
   +-----------+------------------+--------------+--------------------+
   |allow-     | -- (drives       | deny if any  | replay on conflict |
   |consume-   | reserve-all)     | entry        |                    |
   |nonces-    |                  | conflicts    |                    |
   |multi      |                  |              |                    |
   +-----------+------------------+--------------+--------------------+

                                 Table 3

   The "schema_invalid" fixtures collectively exercise the closed
   verdict schema at every level: an unknown "verdict", a "deny" missing
   "code"/"message", additional properties on both the "allow" and
   "deny" objects, a bad "kind", and -- for "consume_nonces" -- an empty
   array, a missing required entry field, an extra entry property, and a
   wrong-typed entry field.  A host that validates only the outer
   object, or only a subset of the nonce-entry schema, is therefore
   caught.

Kondoju                  Expires 22 January 2027               [Page 21]
Internet-Draft         External Verifier Contract              July 2026

8.2.  Host-Under-Test (HUT) Convention

   The runner drives any host through a thin, language-neutral
   convention so a host in any ecosystem can be tested against the same
   fixtures.  The runner spawns the host once per vector and
   communicates as follows.

   Environment -- the runner sets, and the host MUST honor:

   *  "HUT_VERIFIER_CMD" -- a JSON array (argv) the host MUST spawn as
      its verifier.  The host MUST NOT substitute a different command.

   *  "HUT_TIMEOUT_MS" -- the wall-clock timeout the host MUST enforce.

   *  "HUT_MAX_STDOUT_BYTES" -- the stdout output bound the host MUST
      enforce; exceeding it is a fail-closed condition.

   *  "HUT_NONCE_MODE" -- "local" or "host".

   *  "HUT_NONCE_STORE" -- a filesystem path the host uses as its
      durable nonce store in host nonce mode.  For the harness only, the
      store format is newline-delimited decimal nonce strings, UTF-8.  A
      production host with a different store tests against this suite by
      pointing a thin test adapter at this file format; the format is a
      test convention, not part of the wire contract.

   *  "HUT_ACTION_LOG" -- a filesystem path.  The host MUST append a
      non-empty marker at the moment it authorizes the action -- after
      every reservation succeeds and immediately before returning
      "allow" -- and MUST NOT write on any deny.  This is the observable
      proxy for the "act" in reserve-before-act.

   *  "HUT_FIXTURE_PIDFILE" -- a filesystem path the host MUST propagate
      to the spawned verifier so the kill-proof fixtures can record
      their PID; the runner then confirms the host killed the verifier
      on timeout/oversize.  For kill-proof vectors a missing pidfile is
      itself a failure.

   stdin -- the runner writes exactly one request object (Section 4.1)
   to the host's stdin and closes it.  The host forwards this to the
   verifier.

   stdout -- the host MUST write exactly one decision object and exit
   "0".  The fail-closed signal is the decision object, NOT the host's
   own exit code; a host that itself exits non-zero fails the
   convention.  The decision is exactly one of three closed shapes:

Kondoju                  Expires 22 January 2027               [Page 22]
Internet-Draft         External Verifier Contract              July 2026

   *  {"decision":"allow"} -- the verifier allowed (and, in host nonce
      mode, every nonce was reserved as novel); no other key is
      permitted;

   *  {"decision":"deny","code":"CODE"} -- a schema-valid verifier
      "deny", relayed unchanged; the host MUST NOT attach a
      "failure_class" to a genuine verifier deny;

   *  {"decision":"deny","failure_class":"CLASS"} -- the host itself
      fail-closed or rejected a replay; it MUST NOT also carry a "code".

   This decision envelope exists only for the conformance harness; it is
   NOT part of the wire contract and imposes nothing on a production
   host's internal API.

8.3.  Host Failure Classes

   When a host fails closed, the runner asserts why, using the
   classification the host reports in "decision.failure_class".  This
   proves the host detected the specific violation rather than denying
   by accident.  The normative requirement is always deny; the failure
   class is the finer-grained assertion.

Kondoju                  Expires 22 January 2027               [Page 23]
Internet-Draft         External Verifier Contract              July 2026

   +====================+=============================================+
   | failure_class      | Fail-closed condition                       |
   +====================+=============================================+
   | nonzero_exit       | verifier exited non-zero                    |
   +--------------------+---------------------------------------------+
   | timeout            | host timeout fired; host killed the process |
   +--------------------+---------------------------------------------+
   | signal_death       | verifier died by an unsolicited signal      |
   +--------------------+---------------------------------------------+
   | unparseable_stdout | stdout empty, not JSON, or with trailing    |
   |                    | bytes                                       |
   +--------------------+---------------------------------------------+
   | multiple_objects   | stdout carried more than one JSON value     |
   +--------------------+---------------------------------------------+
   | oversize_stdout    | stdout exceeded the host output bound       |
   +--------------------+---------------------------------------------+
   | schema_invalid     | a parsed verdict failed the verdict schema  |
   +--------------------+---------------------------------------------+
   | replay             | a "consume_nonces" entry was already        |
   |                    | reserved                                    |
   +--------------------+---------------------------------------------+
   | spawn_error        | the host could not spawn or drive the       |
   |                    | verifier at all                             |
   +--------------------+---------------------------------------------+

                                 Table 4

   Because several fail-closed conditions can co-occur for one input, a
   vector MAY admit more than one acceptable class.  For example, a
   verifier killed by a signal both dies by signal and leaves stdout
   empty.  Where a fixture triggers a single unambiguous condition, the
   vector pins the single class: a host that denies but misclassifies it
   (for example calling an unbounded flood "unparseable_stdout" instead
   of enforcing the "oversize_stdout" bound) is flagged, because the
   misclassification reveals a real gap.

8.4.  Two Independent Reference Hosts and Scope Limits

   Two independent reference hosts demonstrate portability: one in
   JavaScript ("spec/reference-host.js") and one in Rust ("spec/
   reference-host-rs/", producing an "evc-reference-host" binary).  Both
   pass the full suite; each is concrete interop evidence that the host
   obligations are implementable and testable in more than one language.

   As a black-box harness the suite proves three things about reserve-
   before-act: the reservation is durably written (novel case),
   authorization is gated on the durable uniqueness check (replay case,
   the primary guarantee), and every entry is checked (reserve-all

Kondoju                  Expires 22 January 2027               [Page 24]
Internet-Draft         External Verifier Contract              July 2026

   case).  It does NOT prove the fine-grained intra-allow-path ordering
   -- that within a single "allow" the durable write is committed
   strictly before the action marker -- because distinguishing "reserve
   then act" from "act then reserve, both before returning allow" would
   require fault injection, which a portable black-box runner cannot
   induce.  That crash-safety property remains a host obligation
   (Section 7); this suite asserts the observable gate, not the write
   barrier.  Implementers SHOULD additionally cover the crash-window
   ordering with an in-process test in their own codebase.  Stating this
   limit openly is deliberate: it scopes exactly what the conformance
   claim does and does not cover.

9.  Wire Versioning

   Two version tokens live at different layers with deliberately
   different rules.

   Wire-facing envelopes are integer-major.  The host request "version"
   (Section 4.1) and the internal bundle version are single integers,
   negotiated by major only.  A verifier supports a fixed set of wire
   majors; an unsupported major is "deny code=unsupported_version".
   There is no minor/patch at the wire layer: any breaking change to the
   request or verdict envelope increments the integer.  The denial-code
   registry (Section 4.2) is part of the wire contract and is therefore
   also governed by the integer-major rule: adding, removing, or
   renaming a code is a wire-major change.  Embedded proofs carried
   inside the opaque bundle keep their own semantic-version string and
   the verifier's existing major-only compatibility rule.

   Additive OPTIONAL fields with a defined default do not bump the wire
   major.  The verdict "kind" field (Section 5) is OPTIONAL and its
   absence has a defined meaning ("zk").  Adding it is compatible in the
   two directions that occur in practice: (i) a verifier that omits
   "kind" -- which includes every wire-1 verifier that predates the
   field, all of them "zk"-class -- produces verdicts that both pre-
   revision and revision-aware hosts accept; and (ii) a revision-aware
   host accepts those older no-"kind" verdicts unchanged, reading them
   as "zk".

   The one caveat, stated plainly: the verdict schema is closed
   ("additionalProperties: false"), so a host validating against the
   original schema will reject any verdict that actually carries "kind"
   -- including a "zk" verifier's explicit "kind":"zk".  The
   compatibility guarantee is therefore precise rather than absolute:
   omitted-"kind" verdicts interoperate in both directions, but any
   verdict that emits "kind" requires a revision-aware host.  This does
   not force a wire-major bump, because the two classes of verifier that
   would emit "kind" are already paired with revision-aware hosts: a

Kondoju                  Expires 22 January 2027               [Page 25]
Internet-Draft         External Verifier Contract              July 2026

   "zk"-class verifier that must remain interoperable with a strict pre-
   revision host SHOULD simply omit the field, and a "classical"- or
   "external"-class verifier -- which MUST emit "kind" -- did not exist
   under the original wire-1 contract and so is consumed only by
   revision-aware hosts.  A field that is REQUIRED, or whose omission
   would change how an existing verdict is interpreted, would instead
   remain a wire-major change.

10.  Relationship to Receipt Signer Discovery

   A sibling specification, Receipt Signer Discovery (RSD), defines a
   ".well-known/bolyra-signers.json" document (an RFC 8615 [RFC8615]
   well-known URI) that lets a receipt verifier learn which ES256K
   signer address(es) to accept.  It is relevant to EVC because a
   "classical"-class verifier that checks signed receipts MAY consume it
   to establish which signers it trusts.

   RSD is not part of the EVC wire boundary, and a full treatment is out
   of scope for this document.  Three consumer rules from RSD are pulled
   into the Security Considerations (Section 12) because they bear on a
   verifier's trust configuration: a consumer MUST fetch over HTTPS, and
   plain "http://" MUST be rejected except for loopback addresses
   (development); a consumer MUST NOT follow redirects; and a consumer
   MUST treat any transport or schema failure as a verification failure,
   never as "no signer restriction".  A separate RSD Internet-Draft,
   including any well-known-URI registration, is deferred past this
   document (Section 13).

11.  Relationship to Other Work

11.1.  draft-klrc-aiagent-auth-03 (Complementary)

   [I-D.klrc-aiagent-auth] ("AI Agent Authentication and Authorization",
   draft-klrc-aiagent-auth-03, 2026-07-06, Informational) addresses
   agent authentication and authorization by leveraging existing
   standards rather than defining new protocols.  Its Section 7 ("Agent
   Credentials") adopts the WIMSE credential formats -- the Workload
   Identity Token (WIT) and the X.509 Workload Identity Certificate
   (WIC); Section 6 addresses agent identifiers (WIMSE identifiers,
   potentially SPIFFE IDs); and Section 10 covers authorization via
   OAuth 2.0 [RFC6749] access tokens, including Section 10.3 ("Use of
   OAuth 2.0 Access Tokens") on how the "sub" claim carries a delegated
   subject and Section 10.4.1 ("User Delegates Authorization") on
   delegating authorization to an agent through an OAuth Authorization
   Code Grant.

Kondoju                  Expires 22 January 2027               [Page 26]
Internet-Draft         External Verifier Contract              July 2026

   EVC addresses the downstream decision boundary that draft-klrc-
   aiagent-auth-03 does not: given a bundle -- which can carry a WIMSE
   credential or an OAuth access token inside its opaque "bundle" -- how
   does a host obtain and enforce a fail-closed authorization verdict at
   the point of a privileged action?  The framing is that EVC is the
   verdict-and-enforcement companion to the identity and authorization
   layer that draft-klrc-aiagent-auth-03 assembles from WIMSE and OAuth.
   A WIMSE/OAuth token is one possible payload a "classical"-class
   verifier (Section 5) validates; a zero-knowledge credential is what a
   "zk"-class verifier validates.  The host contract is identical either
   way.  The two documents are complementary and operate at different
   points in the flow; EVC does not compete with, restate, or depend on
   the draft-klrc-aiagent-auth-03 mechanisms.

      *RESOLVED 2026-07-15 (founder):* keep citations as-is; section-
      number re-verification is a MANDATORY pre-submission step.
      Original note: pin exact draft-klrc-aiagent-auth-03 section
      numbers before submit.  The numbers above (6, 7, 10.3, 10.4.1)
      were read from the live datatracker version on 2026-07-14.  The
      -03 revision is built on WIMSE (WIT/WIC) plus OAuth 2.0, which
      differs materially from the "Agent Identity Token / client-to-
      agent auth" characterization in the outline (that description
      tracked the earlier -01).  Re-verify the section numbers and
      titles against the -03 text at submission time, since datatracker
      section numbering can shift with each revision.

11.2.  draft-pidlisnyi-aps-02 (Related -- Agent Passport System)

   [I-D.pidlisnyi-aps] ("Agent Passport System", draft-pidlisnyi-aps-02,
   2026-07-04) is a broad governance framework.  It defines a DID method
   "did:aps" using multibase-encoded Ed25519 public keys, Ed25519-based
   agent passports, and a three-signature policy chain (intent,
   evaluation, receipt) whose receipt signatures are computed with EdDSA
   per RFC 8032.

   A factual, citable point: draft-pidlisnyi-aps-02 does not define a
   zero-knowledge or selective-disclosure verifier class; its signed
   artifacts are Ed25519-signed and content-addressed.  Its Security
   Considerations (Section 8) speak to evidence authenticity rather than
   disclosure minimization -- for example, that "A valid receipt
   signature proves that the issuer attests the receipt's payload; it
   does not prove the payload corresponds to external fact."

   The positioning is neutral and about altitude, not merit.  APS is a
   full governance/passport/receipt framework; EVC is the narrow,
   testable host↔verifier decision boundary that any such framework
   needs at the point of enforcement.  The two are complementary: an APS
   policy-decision engine could be wrapped as an "external"-class (or

Kondoju                  Expires 22 January 2027               [Page 27]
Internet-Draft         External Verifier Contract              July 2026

   "classical"-class) EVC verifier, and in the lane APS does not define,
   a "zk"-class verifier adds selective disclosure -- proving authority
   without revealing the passport or the underlying attributes.  This
   document does not characterize APS's disclosure model beyond the
   factual statement that it defines no zero-knowledge class.

      *RESOLVED 2026-07-15 (founder):* keep only the citable no-ZK-class
      claim; no stronger characterization in -00.  Original note: if the
      draft is to characterize APS's disclosure model beyond "no ZK
      class," quote the exact draft-pidlisnyi-aps-02 Security-
      Considerations sentence rather than paraphrasing.  The Section 8
      sentence quoted above was read from the live datatracker version
      on 2026-07-14; re-verify it against the -02 text at submission
      time.

12.  Implementation Status

   [Note to the RFC Editor: please remove this section before
   publication, per [RFC7942].]

   This section records the status of known implementations of the
   contract at the time of writing, per the process in [RFC7942].
   Listing here does not imply endorsement.

12.1.  Verifier: bolyra verify (production)

   The "@bolyra/cli" npm package (version 0.7.0 at the time of writing)
   ships "bolyra verify", a spawnable external verifier implementing the
   verifier side of this contract: one JSON request on stdin, one
   verdict object on stdout, fail-closed on every malformed, oversized,
   or unexpected input, with the denial-code registry of this document.
   It implements the "zk" and "classical" verifier classes.  Maturity:
   released, publicly installable.

12.2.  Reference hosts (two languages)

   Two reference hosts implement the host obligations independently: a
   JavaScript host ("spec/reference-host.js") and a Rust host ("spec/
   reference-host-rs/").  Both pass the host-conformance suite
   (Section 8): 26 misbehaving- and well-behaved-verifier fixtures
   driving 27 "host_behavior" vectors.  Maturity: reference
   implementations, maintained alongside this document.

Kondoju                  Expires 22 January 2027               [Page 28]
Internet-Draft         External Verifier Contract              July 2026

12.3.  Hosted verifier (preview)

   A hosted HTTP mapping of the same contract ("POST /v1/verify", one
   request object in, one verdict object out, fail-closed) is operated
   as a preview for design partners.  It carries no service-level
   commitment and is not a normative part of this document; it exists to
   demonstrate that the contract is transport-portable.  Maturity:
   preview, access-controlled.

12.4.  Independent implementation experience

   The maintainer of an unrelated MCP server project
   (mcp_agent_mail_rust) built an off-by-default Ed25519 registration-
   proof gate designed to the v1 contract boundary, developed without
   depending on any component of the authors' implementations.
   Maturity: shipped in that project, off by default.

13.  Security Considerations

13.1.  Fail-Closed Is the Whole Safety Property

   Every ambiguous outcome -- non-zero exit, timeout, signal death,
   unparseable, oversized, or multi-object stdout, schema failure, or an
   unrecognized "kind" -- MUST result in deny (Section 6).  This is the
   entire safety property of the contract: the host never proceeds on
   ambiguity.  The host-conformance suite (Section 8) is the mechanism
   that makes this claim checkable rather than asserted, spawning
   deliberately misbehaving verifiers and confirming the host fails
   closed.  Exit status dominates a stdout allow (Section 6.2): a non-
   zero exit is a deny even when a syntactically valid "allow" reached
   stdout.

13.2.  kind Is Unauthenticated Self-Report

   A host MUST establish a verifier's proof-system class from configured
   verifier identity or policy, never from the "kind" string, and MUST
   NOT relax trust, verifier selection, schema validation, or nonce
   handling on its basis (Section 5).  The threat is a hostile or buggy
   verifier that claims "kind=zk" to imply privacy guarantees it did not
   provide, or that emits an unfamiliar "kind" to probe for a lenient
   host.  An unrecognized "kind" is a schema failure and MUST fail
   closed.

Kondoju                  Expires 22 January 2027               [Page 29]
Internet-Draft         External Verifier Contract              July 2026

13.3.  Nonce Reservation and Replay

   Reserve-before-act ordering (Section 7) is a load-bearing property.
   "Record after proceeding" is a replay window and is FORBIDDEN.  On a
   multi-entry "consume_nonces" (agent nullifier plus human-uniqueness
   nullifier), the host MUST reserve every entry and MUST reject on any
   conflict.  In local nonce mode, a store error MUST fail closed.  The
   black-box conformance suite proves the observable gate -- that
   authorization is conditioned on the durable uniqueness check -- but
   not the intra-allow-path write-barrier ordering (Section 8.4); hosts
   SHOULD cover that crash-window ordering with an in-process test.

13.4.  Redirect and Downgrade in Signer Discovery

   For a "classical"-class verifier that consumes a Receipt Signer
   Discovery document (Section 11): a consumer MUST fetch over HTTPS,
   and plain "http://" MUST be rejected except for loopback addresses
   (development); a consumer MUST NOT follow redirects, because a
   redirect can move the fetch to a plaintext or attacker-chosen origin
   after the protocol check; and a consumer MUST treat any transport,
   status, or schema failure as a verification failure, never as "no
   signer restriction".  Discovery is trust-in-origin, not endorsement
   and not PKI.  When both an out-of-band pinned signer and a discovery-
   URL signer source are supplied, a consumer MUST require the two to
   agree.

13.5.  Output-Bound Truncation and Resource Exhaustion

   The stdout output bound and the stdin bound (Section 6.3) bound a
   hostile verifier that floods or hangs; the host MUST enforce both and
   kill on breach.  The "oversize-flood" and hang fixtures (Section 8.1)
   prove this.  A truncated stdout is unparseable and MUST deny, never
   yield a partial allow.

13.6.  Tail Truncation of Hash-Chained Receipts (Informative)

      *NEW -- draft-only; not in shipped EVC v1 / RSD v1.*

   For a hash-chained signed-receipt log that a "zk"- or "classical"-
   class verifier may consult, an attacker who can drop the newest
   entries can mount a tail-truncation attack: the chain still verifies
   as a valid prefix, so the verifier sees an older, shorter-but-
   consistent history.  This is a real property, but it sits below the
   EVC host↔verifier boundary -- receipt-chain semantics live in the
   receipt library, not in the EVC wire envelope -- so this document
   states no new normative EVC obligation here.  As guidance a verifier
   vendor may adopt: a verifier that relies on receipt ordering should
   anchor the expected chain tip (a signed high-water mark or a

Kondoju                  Expires 22 January 2027               [Page 30]
Internet-Draft         External Verifier Contract              July 2026

   monotonic counter) out of band and treat a shorter-than-expected
   chain as a verification failure.  A normative mitigation, if desired,
   belongs in a dedicated receipts/RSD companion draft where a MUST can
   be properly grounded, and would be referenced from here.

      *RESOLVED 2026-07-15 (founder):* keep informative in -00 (option
      a); companion draft only if chain anchoring becomes its own
      standards conversation.  Original note: cross-check the receipt-
      chain semantics of the "@bolyra/receipts" library before
      finalizing this wording, and decide between (a) keeping this as
      the informative note above, or (b) moving a normative MUST into a
      receipts/RSD companion draft and citing it.  The outline's
      recommendation (task 18/19) is (a): keep it informative in this
      document.

13.7.  Domain Separation

   The request-binding signature (Appendix B) is domain-separated by a
   tag that prevents a binding signature from being replayed as any
   other signature the verifier's scheme produces, and vice-versa.  This
   closes a cross-context signature replay class.  The tag is versioned;
   the binding revision described in Appendix B uses the ".v2" tag.
   This consideration applies only to a verifier whose internal binding
   follows Appendix B; it is below the EVC boundary (the host never sees
   the binding) and is included because Appendix B is retained as
   informative.

13.8.  Binding of Credential Expiry

   This consideration applies only to a verifier whose internal binding
   follows Appendix B; it is below the EVC boundary (the host never sees
   the binding) and is included because Appendix B is retained as
   informative.

   The binding in Appendix B originally signed five fields and did not
   cover the credential expiry; that revision is referred to here as
   binding v1.  A verifier whose proof system does not independently
   bind expiry -- a "classical"- or "external"-class verifier
   (Section 5) -- could therefore be presented a binding whose signed
   fields matched an issued mandate while an expiry value drawn from
   outside the signature was substituted.  A presenter able to re-anchor
   a later expiry onto an already-issued mandate could obtain an "allow"
   past the mandate's intended lifetime, extending the credential's
   effective duration.  The escalation is bounded: it extends duration
   at the already-granted permission tier and audience, and does not
   permit tier, audience, or payee escalation.  A "zk"-class verifier
   that binds expiry within its proof was not exposed to this
   substitution.

Kondoju                  Expires 22 January 2027               [Page 31]
Internet-Draft         External Verifier Contract              July 2026

   The current binding (binding v2, Appendix B) places "expiry" inside
   the signed binding, versions the domain-separation tag to
   "bolyra.external-verifier.binding.v2", and, after the signature
   verifies, requires the signed "binding.expiry" to equal the
   credential expiry that the scope and expiry checks consume; a
   mismatch is a verification failure ("deny code=invalid_bundle").  A
   binding carrying only the earlier five fields is treated as an
   obsolete binding v1 and rejected as an unsupported version ("deny
   code=unsupported_version"), with no compatibility mode and no
   advisory-expiry fallback.  A verifier following Appendix B therefore
   signs "expiry" as part of the binding and rejects a five-field
   binding rather than accept an expiry that no signature covers.  As
   with Domain Separation above, this is a property of the reference
   verifier's internal binding, stated with the lowercase "must"
   convention of Appendix B; it is below the EVC wire boundary and
   imposes no new normative obligation on the host contract.

13.9.  Privacy Considerations

   A "zk"-class verifier lets the host authorize an action without
   learning the agent's identity or attribute values; this is the
   selective-disclosure property of Section 5.  EVC bounds what the
   verifier learns, not what the host already knows: the request context
   the host supplies ("agent_name", "project_key", "program", "model",
   "granted_capabilities") is host-chosen and may itself be identifying.
   Implementers should note this asymmetry -- a privacy-preserving
   verifier does not make the host's own request context private -- and
   minimize identifying content in the request where the deployment's
   threat model calls for it.

14.  IANA Considerations

      *NEW -- draft-only, not in shipped EVC v1.*

14.1.  External Verifier Denial Codes Registry

   This document requests that IANA create a new registry, "External
   Verifier Denial Codes", to catalog the machine-readable denial-code
   vocabulary of Section 4.2.

   *  Registry name: External Verifier Denial Codes.

   *  Registration policy: Specification Required [RFC8126].

   *  Columns: "code" (the snake_case token); brief description; wire
      version introduced; change controller; reference.

Kondoju                  Expires 22 January 2027               [Page 32]
Internet-Draft         External Verifier Contract              July 2026

   Critical clarification reconciling the registry with the closed wire
   enum: the wire denial-code set is a closed enum on the wire.  A
   verifier MUST NOT add, remove, or rename a code without a wire-major
   version bump (Section 4.2 and Section 10).  The IANA registry does
   NOT relax this and does NOT open the enum at runtime.  It is a
   catalog of the codes each spec version defines: a new registration is
   admissible only as part of a published EVC wire-major/spec revision
   that adds the code, and each registry entry records the wire version
   that introduced it.  A verifier that emits an unregistered code is
   non-conformant exactly as it is today; the registry changes
   discoverability, not the closed-set semantics or the version-bump
   rule.  Third-party ("external"-class) verifiers reuse the existing
   vocabulary and do not mint private codes at runtime.

   Initial contents: the 15 codes of Section 4.2, each recorded with its
   meaning, the wire version that introduced it ("1"), change controller
   (this document / the EVC specification), and reference (this
   document).

14.2.  Proof-System Classes: Closed Enum, No Registry Requested

   The "kind" value set ("classical", "zk", "external") of Section 5 is
   deliberately NOT proposed as an IANA registry.  The set is small and
   closed, and adding a value is a wire-facing change already governed
   by the wire-major rule (Section 10).  A fixed enum in the
   specification is preferable to a registry here; the tradeoff is that
   a future proof-system class requires a spec revision rather than a
   registration, which is acceptable given how rarely the class set is
   expected to change.

14.3.  Deferred Registrations

   *  Well-known URI.  The Receipt Signer Discovery document (".well-
      known/bolyra-signers.json", Section 11) would need an RFC 8615
      [RFC8615] well-known-URI registration, but that belongs to a
      separate RSD Internet-Draft, not this one.  It is out of scope for
      this document.

   *  Media type.  A proof-envelope media type exists in the wider
      ecosystem but is below the EVC boundary (the bundle is opaque);
      EVC itself defines no new media type.  No media-type registration
      is requested.

Kondoju                  Expires 22 January 2027               [Page 33]
Internet-Draft         External Verifier Contract              July 2026

      *RESOLVED 2026-07-15 (founder):* no media-type registration in
      -00; revisit if the HTTP transport is specified in a later rev.
      Original note: confirm before submit that the wire request/verdict
      objects receive no structured-suffix or media-type registration
      for -00.  Recommendation (outline task): no registration -- the
      objects are transported over a subprocess pipe, not a network
      media type.

15.  References

15.1.  Normative References

   [RFC2119]  Bradner, S., "Key words for use in RFCs to Indicate
              Requirement Levels", BCP 14, RFC 2119,
              DOI 10.17487/RFC2119, March 1997,
              <https://www.rfc-editor.org/rfc/rfc2119>.

   [RFC8174]  Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC
              2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174,
              May 2017, <https://www.rfc-editor.org/rfc/rfc8174>.

   [RFC8259]  Bray, T., Ed., "The JavaScript Object Notation (JSON) Data
              Interchange Format", STD 90, RFC 8259,
              DOI 10.17487/RFC8259, December 2017,
              <https://www.rfc-editor.org/rfc/rfc8259>.

15.2.  Informative References

   [I-D.klrc-aiagent-auth]
              Kasselman, P., Lombardo, D., Rosomakho, Y., Campbell, B.,
              and A. Steele, "AI Agent Authentication and
              Authorization", 6 July 2026.

   [I-D.pidlisnyi-aps]
              Pidlisnyi, "Agent Passport System", 4 July 2026.

   [RFC6749]  Hardt, D., Ed., "The OAuth 2.0 Authorization Framework",
              RFC 6749, DOI 10.17487/RFC6749, October 2012,
              <https://www.rfc-editor.org/rfc/rfc6749>.

   [RFC7519]  Jones, M., Bradley, J., and N. Sakimura, "JSON Web Token
              (JWT)", RFC 7519, DOI 10.17487/RFC7519, May 2015,
              <https://www.rfc-editor.org/rfc/rfc7519>.

   [RFC7662]  Richer, J., Ed., "OAuth 2.0 Token Introspection",
              RFC 7662, DOI 10.17487/RFC7662, October 2015,
              <https://www.rfc-editor.org/rfc/rfc7662>.

Kondoju                  Expires 22 January 2027               [Page 34]
Internet-Draft         External Verifier Contract              July 2026

   [RFC7942]  Sheffer, Y. and A. Farrel, "Improving Awareness of Running
              Code: The Implementation Status Section", BCP 205,
              RFC 7942, DOI 10.17487/RFC7942, July 2016,
              <https://www.rfc-editor.org/rfc/rfc7942>.

   [RFC8126]  Cotton, M., Leiba, B., and T. Narten, "Guidelines for
              Writing an IANA Considerations Section in RFCs", BCP 26,
              RFC 8126, DOI 10.17487/RFC8126, June 2017,
              <https://www.rfc-editor.org/rfc/rfc8126>.

   [RFC8615]  Nottingham, M., "Well-Known Uniform Resource Identifiers
              (URIs)", RFC 8615, DOI 10.17487/RFC8615, May 2019,
              <https://www.rfc-editor.org/rfc/rfc8615>.

Appendix A.  Request and Verdict JSON Schemas (Normative)

A.1.  Request Schema

Kondoju                  Expires 22 January 2027               [Page 35]
Internet-Draft         External Verifier Contract              July 2026

   {
     "$schema": "https://json-schema.org/draft/2020-12/schema",
     "$id": "https://bolyra.ai/spec/external-verifier-request-v1.json",
     "title": "External Verifier Request v1",
     "type": "object",
     "required": ["version", "bundle", "request", "now_unix"],
     "additionalProperties": true,
     "properties": {
       "version": { "type": "integer", "const": 1 },
       "bundle": { "type": "string", "minLength": 1 },
       "request": {
         "type": "object",
         "required": [
           "agent_name",
           "project_key",
           "program",
           "model",
           "granted_capabilities"
         ],
         "additionalProperties": true,
         "properties": {
           "agent_name": { "type": "string" },
           "project_key": { "type": "string" },
           "program": { "type": "string" },
           "model": { "type": "string" },
           "granted_capabilities": {
             "type": "array",
             "items": { "type": "string" }
           }
         }
       },
       "now_unix": { "type": "integer", "exclusiveMinimum": 0 }
     }
   }

A.2.  Verdict Schema (Closed)

   {
     "$schema": "https://json-schema.org/draft/2020-12/schema",
     "$id": "https://bolyra.ai/spec/external-verifier-verdict-v1.json",
     "title": "External Verifier Verdict v1",
     "oneOf": [
       {
         "type": "object",
         "required": ["verdict"],
         "additionalProperties": false,
         "properties": {
           "verdict": { "const": "allow" },

Kondoju                  Expires 22 January 2027               [Page 36]
Internet-Draft         External Verifier Contract              July 2026

           "kind": { "type": "string", "enum": ["classical", "zk", "external"] },
           "consume_nonces": {
             "type": "array",
             "minItems": 1,
             "items": {
               "type": "object",
               "required": ["issuer_key", "nonce", "retain_until"],
               "additionalProperties": false,
               "properties": {
                 "issuer_key": { "type": "string" },
                 "nonce": { "type": "string" },
                 "retain_until": { "type": "integer" }
               }
             }
           }
         }
       },
       {
         "type": "object",
         "required": ["verdict", "code", "message"],
         "additionalProperties": false,
         "properties": {
           "verdict": { "const": "deny" },
           "kind": { "type": "string", "enum": ["classical", "zk", "external"] },
           "code": {
             "type": "string",
             "enum": [
               "malformed_input",
               "unsupported_version",
               "invalid_bundle",
               "invalid_proof",
               "untrusted_root",
               "delegation_invalid",
               "invalid_signature",
               "request_mismatch",
               "model_mismatch",
               "unknown_capability",
               "scope_exceeded",
               "expired",
               "nonce_missing",
               "nonce_replayed",
               "internal_error"
             ]
           },
           "message": { "type": "string" },
           "detail": { "type": "object" }
         }
       }

Kondoju                  Expires 22 January 2027               [Page 37]
Internet-Draft         External Verifier Contract              July 2026

     ]
   }

Appendix B.  Binding Signature (Informative)

   This appendix describes the request-authorizing signature carried
   inside a bundle by Bolyra's "zk"- and "classical"-class reference
   verifiers.  It is included to illustrate one verifier's internal
   binding.  It is explicitly below the interoperability boundary: the
   host never sees these bytes, and a conforming verifier of any class
   may use any binding it chooses.  Nothing in this appendix is a
   normative obligation of the EVC wire contract.  The lowercase
   "must"/"must not" phrasing below states requirements of the reference
   verifier implementation, not requirements of this document; they are
   not BCP 14 keywords.

   The bundle carries a request-authorizing signature that binds the
   request context to the cryptographic key the proof commits to.  The
   binding is versioned independently of the wire envelope; this
   appendix describes binding v2, in which the credential expiry is
   signature-bound.  In the reference verifier:

   *  Canonical payload (binding v2).  Let "binding" be the object with
      exactly the six fields "agent_name", "project_key", "program",
      "model", "capabilities" (a string array), and "expiry" (a positive
      integer, Unix seconds).  The canonical payload is the sorted-key,
      compact (no insignificant whitespace) JSON serialization of
      "binding".  Object keys must be sorted and array elements
      (including "capabilities") must not be reordered.  The verifier
      compares "capabilities" as a set for authorization, but the signed
      bytes are order-sensitive. "expiry" became part of the signed
      binding in binding v2; the Security Considerations note on expiry
      binding (Section 12) states why.

   *  Version discrimination is structural, not a declared version
      field.  The reference verifier classifies a binding by its exact
      key set: the six fields above and no others.  A member outside
      that set is rejected ("deny code=invalid_bundle") before any
      version is inferred.  A well-formed binding carrying only the
      earlier five fields (no "expiry") is an obsolete binding v1 and is
      rejected ("deny code=unsupported_version"); no compatibility mode
      accepts it.  An "expiry" that is not a positive integer is
      rejected ("deny code=invalid_bundle").

Kondoju                  Expires 22 January 2027               [Page 38]
Internet-Draft         External Verifier Contract              July 2026

   *  Domain separation.  A domain-separation tag prevents a binding
      signature from being replayed as any other signature.  The tag is
      versioned; binding v2 uses the ".v2" tag: DST =
      utf8("bolyra.external-verifier.binding.v2"), and dsInput = DST ||
      0x00 || payload.  The single 0x00 byte separates the ASCII domain
      tag from the canonical binding bytes.

   *  Digest to field element. digest = SHA-256(dsInput) (32 bytes);
      msgField = BigInt("0x" || hex(digest)) mod BN254_FIELD_ORDER,
      where BN254_FIELD_ORDER is
      21888242871839275222246405745257275088548364400416034343698204186575808495617.

   *  Sign / verify.  Sign with EdDSA-Poseidon over BabyJubjub; verify
      by recomputing msgField from the bundle's own "binding" bytes and
      checking the signature against the operator public key the proof
      commits to.  A signing key disjoint from the credential is
      rejected ("deny code=invalid_signature"), closing the cross-signer
      replay class.  After the signature verifies, the reference
      verifier additionally requires "binding.expiry" to equal the
      credential expiry that the scope and expiry checks consume,
      rejecting a mismatch ("deny code=invalid_bundle"); this binds the
      strict-expiry check to the signed value.  A verifier consuming a
      foreign bundle recomputes the digest from the bundle's own
      "binding" bytes and does not trust a self-asserted digest.

Appendix C.  Worked Examples (Informative)

   Each example is a real (stdin request, stdout verdict) pair against
   the reference verifier.  Requests are abbreviated ("bundle" elided).

C.1.  Allow (Local Nonce Mode)

   Request:

   { "version": 1, "bundle": "...", "request": {
       "agent_name": "research-bot", "project_key": "/work/acme/research",
       "program": "crewai", "model": "opus-4.1",
       "granted_capabilities": ["fetch_inbox", "send_message"] },
     "now_unix": 1751990400 }

   Verdict (exit 0):

   { "verdict": "allow" }

Kondoju                  Expires 22 January 2027               [Page 39]
Internet-Draft         External Verifier Contract              July 2026

C.2.  Allow With consume_nonces (Host Nonce Mode)

   Same request, verifier spawned in host nonce mode.  This agent-only
   bundle yields a single entry; a human-backed bundle would add a
   second "human:NULLIFIER-HASH" entry.  Verdict (exit 0):

   {
     "verdict": "allow",
     "consume_nonces": [
       {
         "issuer_key": "15617329766995256858590222302430068383949745072531974464084158078905448850943:20201653676552407165606319978171745645181779505176156736762229713293662347780",
         "nonce": "12616665119450508255185458876855962314592339945640375882344193391684757282246",
         "retain_until": 4102444800
       }
     ]
   }

   The host MUST reserve every entry's "nonce" atomically before acting
   (Section 7).

C.3.  Deny -- scope_exceeded

   Request whose credential proves only READ_DATA but whose
   "send_message" capability requires WRITE_DATA.  Verdict (exit 0):

   {
     "verdict": "deny",
     "code": "scope_exceeded",
     "message": "required scope exceeds the credential effective scope",
     "detail": { "required_scope": "2", "effective_scope": "1", "excess_bits": "2" }
   }

C.4.  Deny -- model_mismatch

   Request whose "model" differs from the model the proof commits to.
   Verdict (exit 0):

   {
     "verdict": "deny",
     "code": "model_mismatch",
     "message": "proven model hash does not match the requested model",
     "detail": { "requestModel": "model-beta" }
   }

C.5.  Deny -- malformed_input

   Truncated JSON on stdin.  Verdict (exit 0):

Kondoju                  Expires 22 January 2027               [Page 40]
Internet-Draft         External Verifier Contract              July 2026

   { "verdict": "deny", "code": "malformed_input", "message": "request stdin is not valid JSON" }

C.6.  Deny -- internal_error (Fail-Closed, Non-Zero Exit)

   No trusted-root source configured.  Verdict on stdout AND a non-zero
   exit code, so the host fail-closes on either signal:

   { "verdict": "deny", "code": "internal_error", "message": "no trusted root source configured" }

C.7.  Allow -- classical-kind Verifier

   A "classical"-class verifier -- for example one that checks an
   ES256K-signed receipt and a JWT delegation token [RFC7519] rather
   than a zero-knowledge proof -- returns the same "allow" envelope,
   tagged with its "kind".  Verdict (exit 0):

   { "verdict": "allow", "kind": "classical" }

C.8.  Deny -- external-kind Verifier

   A third-party verifier denies with the shared registry (Section 4.2)
   and its own "kind".  Verdict (exit 0):

   { "verdict": "deny", "kind": "external", "code": "expired", "message": "credential expired" }

   An agent-only "zk" verdict carries no "kind"; the host reads its
   absence as "zk".

Appendix D.  Host-Conformance Fixture List (Informative)

   The 27 "host_behavior" vectors and the Host-Under-Test environment
   variables of Section 8 are reproduced there as tables.  Implementers
   building or testing a host should consult Section 8.1 (fixture
   inventory), Section 8.2 (the HUT convention and environment
   variables), and Section 8.3 (failure classes), together with the two
   reference hosts at "spec/reference-host.js" and "spec/reference-host-
   rs/".

Author's Address

   Viswanadha Pratap Kondoju
   Bolyra
   Email: viswa@bolyra.ai

Kondoju                  Expires 22 January 2027               [Page 41]