An External Verifier Contract for Agent Authorization Decisions
draft-kondoju-evc-00
This document is an Internet-Draft (I-D).
Anyone may submit an I-D to the IETF.
This I-D is not endorsed by the IETF and has no formal standing in the
IETF standards process.
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]