Internet-Draft GAP June 2026
Shovan Expires 1 January 2027 [Page]
Workgroup:
Network Working Group
Internet-Draft:
draft-shovan-gap-00
Published:
Intended Status:
Informational
Expires:
Author:
J. Shovan
SynOI Inc

Governed Action Protocol (GAP)

Abstract

The Governed Action Protocol (GAP) is an open wire protocol for a Universal Action Coordination Fabric. GAP defines a four-phase lifecycle (Declare, Grant, Invoke, Receipt) that governs every action an AI agent, smart device, industrial controller, or automated pipeline may take. Every gate decision produces a content-addressed, immutable receipt; receipts are signed at L2+ (Ed25519) and L4 (hybrid ML-DSA-65). The protocol is designed to be language- and platform-neutral, applicable across enterprise AI agent pipelines, consumer smart home devices, medical equipment, industrial control systems, and game engines.

This document specifies the wire format, object model, grant evaluation rules, HTTP API surface, workflow semantics, revocation mechanisms, and conformance tiers for GAP version 1.0.

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 1 January 2027.

Table of Contents

1. Introduction

As AI agents and automated systems proliferate, the absence of a common authorization and auditability layer creates compounding risk. Each environment (AI agent frameworks, smart home hubs, industrial SCADA systems, game engines, medical devices) builds its own integration layer with its own permission model, its own audit log (if any), and its own revocation mechanism. None share an audit trail. None speak to each other. None produce verifiable, portable evidence of what was authorized.

GAP is the fabric underneath all of them. An actor declares what it can do. An operator grants what it may do, under what conditions. Every invocation is evaluated against an active grant. Every gate decision (allow, deny, defer, timeout) produces a content-addressed, immutable receipt (signed at L2+). The same protocol that governs an AI agent's tool calls also governs an industrial valve controller, a door lock, and a medication infusion pump.

1.1. Motivation

Several properties motivate a unified open protocol:

Portability:

Authorization evidence must be independently verifiable by any party with access to the gateway's public key, without contacting the gateway.

Non-repudiation:

A signed, content-addressed receipt cannot be silently modified after issuance.

Composability:

Delegation chains, multi-party approvals, and workflow orchestration must compose from the same primitive objects.

Physical-safety applicability:

The protocol must carry safety classification metadata and define fail-closed semantics for capabilities that affect physical systems.

1.2. Scope

This document defines:

  • The CDRO (Content-addressed, Deterministic, Replayable Object) wire format

  • OID computation (canonical JSON + SHA-256)

  • The four lifecycle phases: Declare, Grant, Invoke, Receipt

  • Grant evaluation algorithm including scope narrowing and delegation

  • Workflow orchestration and Human-in-the-Loop (HITL) signaling

  • Revocation (immediate, scheduled, provisional block, and L3 quorum)

  • The HTTP API surface for GAP-conformant gateways

  • Conformance tiers L1 through L4

Security considerations for implementing a GAP gateway are specified in {{security-considerations}}. The protocol specification is dedicated to the public domain under CC0 1.0 Universal. Implementations of this specification may be licensed independently.

1.3. Conventions and Definitions

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 following terms are used throughout this document:

Actor:

An entity (AI agent, service, device, or human user) that can declare capabilities and invoke them subject to grants.

Operator:

The human or organizational entity that issues grants. An operator holds the root of trust for a tenant.

Tenant:

An isolated authorization domain. CDROs never implicitly cross tenant boundaries.

CDRO:

Content-addressed, Deterministic, Replayable Object. The base unit of the GAP object model.

OID:

Object Identifier. A content-addressed string of the form sha256:<hex> computed over the canonical JSON serialization of a CDRO's body fields.

Gateway:

A GAP-conformant server that evaluates invocations against active grants and produces signed decision receipts.

Physical-safety capability:

A capability whose invocation can cause irreversible physical consequences. Identified by physical_safety: true in the capability declaration.

2. Object Model

2.1. CDRO Envelope

Every GAP record is a CDRO. A CDRO is a JSON object with the following fields:

Table 1
Field Required Description
oid yes sha256:<64 hex chars>, content-addressed identifier
type yes One of the gap:* type strings defined in this document
gap_version yes Always "1.0" for this specification
tenant_id yes The owning tenant identifier
created_at_ms yes Unix epoch milliseconds
created_by yes OID of the actor creating this record
body yes Type-specific payload
signature no Ed25519 signature over the canonical envelope, base64url
signature_key_id no Identifies the signing key
signature_algorithm no Signature algorithm: Ed25519, ML-DSA-65, or Ed25519+ML-DSA-65. Verifiers MUST NOT guess the algorithm from key length.
supersedes no OID of the CDRO this record replaces

The defined type values are:

Table 2
Type string Body type
gap:capability_declaration CapabilityDeclarationBody
gap:capability_grant CapabilityGrantBody
gap:capability_invocation CapabilityInvocationBody
gap:decision_receipt GapDecisionReceiptBody
gap:revocation_event RevocationEventBody
gap:workflow_definition WorkflowDefinitionBody
gap:workflow_instance WorkflowInstanceBody
gap:stage_transition StageTransitionBody
gap:channel_event ChannelEventBody
gap:break_glass_token BreakGlassTokenBody
gap:local_override_credential LocalOverrideCredentialBody
gap:lca_root LcaRootBody ({ root_public_key_base64: string, algorithm: string, tenant_id: string, valid_from_ms: number, expires_at_ms: number })
gap:erasure_event ErasureEventBody
gap:orchestration_chain OrchestrationChainBody [DESIGN]
gap:consent_record ConsentRecordBody [DESIGN]
gap:pip_response PipResponseBody [DESIGN]

2.2. OID Computation

An OID is computed as follows:

  1. Take the CDRO envelope object.

  2. Remove the fields oid, gap_version, signature, signature_key_id, and supersedes.

  3. Canonicalize the resulting object per {{canonical-json}}.

  4. Compute SHA-256 over the UTF-8 encoding of the canonical string.

  5. Hex-encode the digest (lowercase).

  6. Prepend sha256:.

The result is the OID for this CDRO. The same input MUST always produce the same OID. Implementations MUST compute and store the OID before signing so that the signature covers the canonical content.

2.3. Canonical JSON

The canonical JSON form of a value is defined recursively:

  • string: standard JSON string encoding

  • number: standard JSON number encoding (no trailing zeros beyond the decimal point)

  • boolean: true or false

  • null: excluded; null values MUST be omitted from canonical form

  • array: [ + elements joined by , (order preserved, null elements omitted) + ]

  • object: { + key-value pairs joined by ,, keys sorted lexicographically (Unicode code-point order), pairs with null values omitted, keys encoded as JSON strings + }

Note: GAP canonical JSON is NOT identical to JSON Canonicalization Scheme (JCS) [RFC8785]. The primary difference is that GAP omits null values while JCS preserves them. Implementors MUST NOT use a JCS library for OID computation without first stripping null values.

3. Phase 1: Declare

3.1. Purpose

Before any capability can be granted or invoked, the actor offering it MUST publish a CapabilityDeclaration. The declaration is an immutable record of what the actor can do, the safety classification of each capability, and optional scope constraints.

3.2. CapabilityDeclarationBody

Table 3
Field Required Description
actor_type yes One of: service, device, agent, human_user, mcp_server, gateway_subsystem, skill
actor_id yes Stable identifier for the declaring actor
actor_name yes Human-readable name
actor_version yes Semantic version string
capabilities yes Array of CapabilitySpec

Each CapabilitySpec:

Table 4
Field Required Description
capability yes Dotted-taxonomy capability name (e.g. home.lock.engage)
safety_class yes A (reversible), B (state-changing), or C (irreversible/critical)
physical_safety no Boolean. true if invocation can cause physical harm
description no Human-readable description
scope_narrowing_schema no JSON Schema describing allowed scope_narrowing keys
privacy_classification no none, pii, phi, financial, or privileged (legally privileged content: attorney-client, work product, common-interest; the gateway routes receipts to a privilege-isolated store and suppresses them from standard list endpoints)
require_signed_receipt no Boolean. When true, the gateway MUST attach a cryptographic signature to every decision receipt for this capability. When false, the gateway SHOULD omit signing even on a server that signs by default (useful for high-frequency low-risk capabilities where signing cost outweighs benefit). When absent, the gateway applies its configured default signing policy. The operator MAY override this per-grant via GrantedCapabilityScope.require_signed_receipt.
pii_args no Array of arg key strings whose values contain PII, PHI, or NPI requiring tokenization before storage. The gateway MUST replace each listed key's value with a keyed HMAC token (one-way, using a per-tenant key) before constructing the invocation CDRO and receipt body. The original value is used for capability execution by the adapter but MUST NOT be stored in any CDRO.
privilege_protected no Boolean. When true, the gateway: routes receipts to a privilege-isolated store; suppresses the receipt body from GET /receipts list endpoint; requires an explicit attorney-assertion header on fetch by OID; excludes the receipt from automated compliance exports. Controls access routing, not deletion. Add privilege_asserted to the compliance_tags vocabulary when this field is true.

When privilege_protected: true is set on a CapabilitySpec, the gateway: routes receipts to a privilege-isolated store; suppresses the receipt body from GET /receipts list endpoint; requires an explicit attorney-assertion header on fetch by OID; excludes the receipt from automated compliance exports. privilege_protected controls access routing, not deletion.

For capabilities with privacy_classification: phi or matching medical.* or financial.*, any arg key whose name is listed in pii_args MUST be tokenized before CDRO construction. The canonical JSON used for OID computation is taken from the unencrypted args before tokenization so OID integrity is maintained. A GET /v1/gap/receipts/:oid?include_pii=true path with elevated authorization serves authorized reviewers.

3.3. Supersession

An actor MAY publish a new declaration superseding an existing one by setting supersedes to the OID of the prior declaration. A gateway MUST treat the prior declaration as inactive once the superseding declaration is accepted. A gateway MUST NOT accept a new declaration with the same actor_id unless it includes a valid supersedes pointer to the currently active declaration for that actor_id within the tenant.

3.4. Ephemeral Actors

Some actors have a lifecycle tied to a session, job, or deployment instance rather than a persistent identity.

Add actor_lifecycle to CapabilityDeclarationBody:

Table 5
Field Required Description
actor_lifecycle no persistent (default) or ephemeral. Ephemeral declarations are exempt from the supersession uniqueness rule; each declaration gets a fresh OID and is valid for its session only.
actor_instance_id no UUID or job ID distinguishing this instance from others with the same actor_id. When present, two declarations with the same actor_id but different actor_instance_id MUST NOT be treated as superseding each other.
session_expires_at_ms no For ephemeral actors: when this session ends. The gateway MUST auto-revoke all grants scoped to this actorinstanceid at this time.

Grants issued to an actor_id without actor_instance_id apply to all instances of that actor_id. Grants scoped to a specific actor_instance_id expire with that instance.

3.5. MCP Tool-Call Governance [DESIGN]

Actors of type mcp_server expose tools via the Model Context Protocol (MCP) tools/list response. A GAP gateway auto-generates CapabilityDeclarations from those responses.

Capability names for MCP tools follow the pattern: mcp.<server_id>.<tool_name>.

MUST: a gateway MUST reject any auto-generated capability name that starts with gap: or matches any normative capability name defined in this specification. This prevents namespace pollution from attacker-controlled MCP servers that return malicious tools/list responses.

An invocation for an MCP-originated capability MAY carry mcp_tool_call on the CapabilityInvocationBody:

Table 6
Field Required Description
server_id yes Stable identifier for the MCP server
tool_name yes Name of the tool as returned by tools/list
tool_schema_hash no SHA-256 hash of the tool's JSON schema, for drift detection

3.6. Identity Binding [DESIGN]

A CapabilityDeclarationBody MAY carry an identity_binding object that ties the actor_oid to a real-world credential using a hardware-backed signature.

The canonical binding payload is domain-separated:

"gap-identity-binding-v1" + ":" + actor_oid + ":" + tenant_id + ":" + credential_identifier

The binding_signature field carries the credential holder's signature over the canonical binding payload.

IdentityBinding fields:

Table 7
Field Required Description
credential_kind yes One of the normative values below
credential_identifier yes Stable identifier within the credential_kind namespace
binding_signature yes Signature over canonical binding payload, base64url
binding_alg yes Signature algorithm (e.g. Ed25519, ES256, RS256)
bound_at_ms yes Unix epoch ms when binding was established
issuer no Issuer identifier (CA DN, OIDC issuer URL, etc.)
expires_at_ms no Expiry of the binding; absent means no expiry

Normative credential_kind values:

Table 8
Value Credential type
piv_cac US Federal PIV or Common Access Card
x509 X.509 certificate
fido2 FIDO2 / WebAuthn credential
tpm_attestation TPM 2.0 attestation key
oidc_sub OIDC subject claim from a trusted IdP
spiffe_svid SPIFFE SVID (X.509 or JWT)
wallet_address Cryptographic wallet address
professional_license Licensed professional credential (e.g. medical, legal)

3.7. Compartment-Based Access Scoping [DESIGN]

A compartment field MAY be added to CapabilityDeclarationBody, CapabilityGrantBody, and CapabilityInvocationBody.

Accepted values: UNCLASS, CUI, or any reverse-domain operator-defined label (e.g. com.acme.project-alpha).

MUST: at invocation time, if the grant carries a compartment, the invocation compartment MUST exactly match. A compartment mismatch MUST be treated as a denial.

MUST: a gateway MUST return HTTP 404 (not HTTP 403) when the invoking actor's compartment level is insufficient to know that a resource exists. OID existence MUST NOT leak across compartment boundaries.

Cross-compartment access requires a bridge grant issued through a TPI-gated HITL workflow. Bridge grants MUST be issued by an operator whose declaration carries both compartment labels and MUST produce a compliance_tag of cross_compartment_bridge on the receipt.

4. Phase 2: Grant

4.1. Purpose

A CapabilityGrant authorizes a specific actor (the grantee) to invoke one or more capabilities declared by a specific actor (the declarer), subject to optional scope constraints, time limits, and preconditions.

4.2. CapabilityGrantBody

Table 9
Field Required Description
grantee yes ActorRef identifying the authorized actor
capability_scopes yes Array of GrantedCapabilityScope
granted_at_ms yes Unix epoch milliseconds
granted_by yes OID of the operator issuing the grant
expires_at_ms no Expiry timestamp in milliseconds
parent_grant_oid no OID of the parent grant (delegation chains)
limits no InvocationLimits
additional_preconditions no Array of Precondition
timestamp_window_seconds no For safetyclass C without physicalsafety: override for the default timestamp validation window in seconds. Gateway applies its default if absent.
offline_grace_seconds no Additional seconds beyond grant expiry during which offline provisional receipts are accepted at reconciliation. Defaults to 0.
max_grant_offline_ttl_ms no Maximum duration any device may use this grant without syncing to the gateway. After this window, further invocations MUST be denied until the device reconnects.
max_revocation_bundle_age_ms no Maximum acceptable age of a revocation bundle for this grant. Devices MUST deny physical_safety/class C invocations if the bundle is older than this value.

Each GrantedCapabilityScope:

Table 10
Field Required Description
capability yes Capability name or wildcard pattern
capability_declaration_oid yes* OID of the CapabilityDeclaration. REQUIRED when safety_class is C or physical_safety is true
scope_narrowing no Object constraining invocation arguments (see Scope Narrowing Evaluation)
additional_preconditions no Array of Precondition evaluated at invocation time for this scope (in addition to grant-level preconditions)
require_signed_receipt no Operator override for receipt signing. When set, takes precedence over the capability declaration's require_signed_receipt. Allows an operator to require signing for a capability the actor declared unsigned, or suppress signing for a high-frequency capability the actor flagged as requiring it.

InvocationLimits:

Table 11
Field Description
max_invocations Maximum total invocation count
max_per_window Maximum invocations within a rolling time window
window_seconds Window duration in seconds
aggregate_limits Array of AggregateLimitEntry

AggregateLimitEntry:

Table 12
Field Description
key The invocation argument key to sum
max Maximum rolling sum (non-negative)
window_seconds Rolling window duration in seconds

4.2.1. Cross-Grant Aggregate Limit Groups

An aggregate_limit_group field on InvocationLimits references a named shared limit pool at the tenant level. Multiple grants referencing the same pool share a single rolling counter enforced atomically by the gateway.

Table 13
Field Required Description
aggregate_limit_group no Named pool identifier. All grants with the same aggregate_limit_group share rolling aggregate counters defined in the tenant's pool configuration.

The gateway MUST maintain atomic counters per pool and MUST deny any invocation from any grant in the pool that would exceed the pool ceiling. Pool configuration is out of scope for this specification and is implementation-defined.

4.3. Precondition Kind Registry

A normative precondition_kind registry defines semantics for standard kinds. Custom kinds use reverse-domain prefixes (e.g. com.example.custom_check).

Table 14
Kind Required args Description
time_window days_of_week, start_hour_utc, end_hour_utc Invocation only permitted in the specified UTC time window
rate_limit max_count, window_seconds Maximum invocations per rolling window (cross-invocation, tracked by gateway)
sanctions_screening list_version, screening_provider, subject_fields Screens named arg keys against a sanctions list. Gateway MUST record screening result OID in the receipt. Gateway MUST NOT proceed to execution if result is denied.
external_pip endpoint_url, cache_ttl_seconds, subject_fields, pip_response_oid (optional) POSTs invocation args to an external Policy Information Point and evaluates the boolean allowed response. Result is cached per (tenant, capability, args-hash) for cache_ttl_seconds. When pip_response_oid is present the response is ENFORCING (see Signed PIP Response below).
inventory_check resource_key, min_available Verifies the named resource has sufficient availability before execution
token_budget model_scope, max_input_tokens, max_output_tokens, max_cost_usd, window_seconds [DESIGN] Rolling token-budget cap evaluated post_invoke. See Token Budget Governance below.
consent_current (none) [DESIGN] Evaluates at invocation time whether the actor's most recent gap:consent_record for the capability's context has consented: true. The gateway MUST NOT use the idempotency cache for this evaluation. See Consent Version Chain below.

4.3.1. Token Budget Governance [DESIGN]

The token_budget precondition kind governs token consumption across invocations that call an LLM. Evaluation timing: post_invoke (settled after execution). The gateway writes the actual consumption to token_consumption on the decision receipt.

TokenBudgetArgs:

Table 15
Field Required Description
model_scope yes Shell-glob pattern matching model IDs (e.g. anthropic/claude-*)
max_input_tokens no Maximum input tokens within window_seconds
max_output_tokens no Maximum output tokens within window_seconds
max_cost_usd no Maximum cost in USD within window_seconds. [MODELED]; not authoritative until a conformance vector exists.
window_seconds yes Rolling window length in seconds

The gateway settles actual consumption onto the receipt via the token_consumption field (see Phase 4: Receipt). Uses the aggregate_limit_group counter mechanism for cross-grant budgeting.

4.3.2. Signed PIP Response [DESIGN]

When an external_pip precondition carries pip_response_oid, the referenced CDRO is a gap:pip_response that the external PIP emitted and the gateway re-signed.

Distinction:

  • Unsigned external reads (no pip_response_oid): ADVISORY. The response influences the gate decision but MUST NOT be the sole basis for an allow outcome.

  • Signed gap:pip_response (with pip_response_oid): ENFORCING. The gateway MAY use it as the sole basis for allow or deny.

MUST: if pip_response_oid is present, the gateway MUST verify the CDRO signature before treating the response as enforcing.

PipResponseBody:

Table 16
Field Required Description
pip_endpoint yes URL of the external PIP endpoint
request_args_hash yes SHA-256 hash of the canonical request args sent to the PIP
response_body_hash yes SHA-256 hash of the raw response body from the PIP
response_summary no Human-readable summary (not authoritative)
evaluated_at_ms yes Unix epoch ms when the PIP was queried
cache_ttl_ms yes How long (ms) this response may be cached by the gateway
pip_signature no Optional signature from the PIP itself, base64url
pip_signature_alg no Algorithm used for pip_signature

For any invocation of financial.wire.initiate or financial.payment.initiate, a gateway MUST enforce sanctions_screening precondition evaluation even when absent from the grant's additional_preconditions. The gateway MUST reject the invocation and produce a denial receipt if the screening result is denied.

4.4. Scope Narrowing Evaluation

When a grant contains scope_narrowing, a gateway MUST enforce the following rules at invocation time:

  1. For each key K in scope_narrowing:

    • The invocation arguments MUST contain key K. If absent, the invocation MUST be denied.

    • If the scope value is a string: args[K] MUST equal the scope value (exact match, case-sensitive). Key names are exact and singular/plural variants are distinct keys.

    • If the scope value is a boolean: args[K] MUST equal the scope value.

    • If the scope value is a number and the key name has the prefix min_: args[K] MUST be greater than or equal to the scope value (lower bound).

    • If the scope value is a number and the key name does not have the prefix min_: args[K] MUST be less than or equal to the scope value (upper bound). For physical_safety: true capabilities, negative values for args[K] MUST be rejected even when they would satisfy the upper-bound check.

    • If the scope value is a string array: args[K] MUST be a member of the array.

Dot-path expansion: A scope_narrowing key containing a dot (e.g. position.x) evaluates against the nested arg path args.position.x. This enables constraint of nested arg structures without requiring flat arg schemas.

Envelope constraint (optional): when a scope_narrowing value is an object with $constraint_oid, the gateway evaluates the named constraint CDRO (a pre-registered function) against the full args object, returning boolean. This enables correlated multi-axis constraints (e.g. motor speed AND torque within a 2D operating envelope).

4.5. Granted-By Verification

A gateway MUST verify at grant acceptance time that body.granted_by OID matches the actor OID associated with the authenticated Bearer token or SSC credential. A grant where these do not match MUST be rejected with HTTP 403.

4.6. Delegation

A grant MAY delegate authority to a sub-grantee by setting parent_grant_oid. A gateway MUST enforce all of the following when evaluating a delegated grant:

  1. The granted_by field of the child grant MUST equal the grantee.actor_oid of the parent grant.

  2. For every capability scope in the child grant, a matching scope MUST exist in the parent grant (using capability pattern matching per {{patterns}}).

  3. For every key in the child grant's scope_narrowing, the child value MUST satisfy the subset rule against the parent value for the same key:

    • String: child value MUST equal parent value.

    • String array: every element of the child array MUST appear in the parent array.

    • Number (upper bound, no min_ prefix): child value MUST be less than or equal to the parent value.

    • Number (lower bound, min_ prefix): child value MUST be greater than or equal to the parent value.

    • Key present in parent but absent in child: MUST be denied (a child cannot drop a parent constraint).

  4. The child grant MUST NOT increase max_delegation_depth beyond the parent's value. For physical_safety: true grants, max_delegation_depth MUST default to 0 (no further delegation) when absent.

  5. For physical_safety: true grants, the child MUST inherit all additional_preconditions from all ancestors in the chain.

  6. At invocation time, the gateway MUST re-validate that all grants in the delegation chain are non-expired and non-revoked.

4.7. Capability Pattern Matching

A capability pattern matches a capability name as follows:

  • * matches any capability name (match-all).

  • prefix.* matches direct children only (single path segment). game.* matches game.session but NOT game.admin.delete.users.

  • prefix.** matches all descendants recursively. game.** matches all nested paths under game, including game.session, game.admin.delete.users, etc. The prefix itself also matches (game.** matches game).

  • An exact string matches only that exact name (no wildcard).

Two wildcard levels are defined: prefix.* matches direct children only (single path segment); prefix.** matches all descendants recursively. Implementations MUST support both levels.

5. Phase 3: Invoke

5.1. Purpose

A CapabilityInvocation is the actor's request to exercise a granted capability. The gateway evaluates the invocation against active grants and produces a GapDecisionReceipt.

5.2. CapabilityInvocationBody

Table 17
Field Required Description
caller yes CallerRef (actortype, actoroid, grant_oid)
capability yes The capability being invoked
args yes Invocation arguments (flat JSON object)
invoked_at_ms yes Client-supplied or server-stamped timestamp
idempotency_key no Client-provided deduplication key
client_event_ms no Unix epoch ms when the action originally occurred in the caller's reference frame (game-world time, clinical queue time, SCADA scan cycle). Populated by the client; not used for replay prevention. Stored in receipt for audit.
queued_at_ms no Unix epoch ms when the invocation was enqueued for submission (e.g. at reconnect after offline period). Optional; aids debugging of delivery latency.

5.3. Timestamp Validation

Timestamp validation is per safety_class:

Table 18
safety_class physical_safety Max client_timestamp age Gateway behavior
A any 5 minutes Reject if invokedatms (client-supplied) is more than 5 minutes in the past
B any 120 seconds Reject if more than 120 seconds in the past
C false operator-configurable Grant field timestamp_window_seconds sets the window; gateway default if absent
C true server-stamp only Gateway MUST ignore client-supplied invokedatms; stamps receipt time authoritatively

When rejecting due to timestamp, the denial receipt MUST include: - detail: 'timestamp_rejected' - server_time_ms field containing the gateway's current Unix epoch ms so clients can resync clocks

For physical_safety: true capabilities:

  • The gateway MUST server-stamp invoked_at_ms and ignore any client-supplied value. The client-supplied value, if present, MUST be stored in client_claimed_at_ms on the receipt for audit purposes.

5.4. Idempotency

A gateway SHOULD implement idempotency keyed by (tenant_id, capability, idempotency_key). On a cache hit:

  • The gateway MUST re-validate that the grant is still non-revoked and non-expired. If the grant has been revoked since the original execution, the gateway MUST return HTTP 410 with a new denial receipt.

  • The gateway MUST verify that the arguments in the new request exactly match the stored arguments. If they differ, the gateway MUST return HTTP 409.

  • The receipt MUST have is_idempotency_replay: true.

  • For physical_safety: true capabilities, the maximum idempotency window MUST NOT exceed 60 seconds.

5.5. Grant Selection

When multiple active grants match an invocation, a gateway MUST use the following deterministic selection order:

  1. Prefer the grant with more scope_narrowing keys (more specific).

  2. Among grants with equal key count, prefer lower numeric upper-bound values.

  3. Among grants with equal numeric bounds, prefer smaller string-array cardinality.

6. Phase 4: Receipt

6.1. GapDecisionReceiptBody

A GapDecisionReceipt is produced for every gate decision. It is immutable.

6.2. GDPR Erasure

GDPR Article 17 (right to erasure) requires a mechanism to handle erasure requests for receipts containing personal data. The GAP erasure mechanism preserves OID integrity while satisfying erasure obligations.

A gap:erasure_event CDRO replaces the body of a targeted receipt with a fixed erasure sentinel while preserving envelope metadata.

ErasureEventBody:

Table 19
Field Required Description
target_oid yes OID of the CDRO being erased
erasure_reason yes gdpr_article_17, ccpa, or operator_policy
erased_at_ms yes Unix epoch ms of erasure
erased_by yes Actor OID issuing the erasure
fields_erased yes Array of field paths erased from the target CDRO body

The erasure event's OID anchors to the original CDRO OID and is itself a signed CDRO, making it non-repudiable. Verifiers MUST treat an erasure event as authoritative over the prior OID body. Add gdpr_erasure to the compliance_tags vocabulary.

For privacy_classification: pii capabilities, gateways operating under GDPR SHOULD use pii_args tokenization (see Phase 1: Declare) to minimize PII stored in receipts before erasure is required.

Table 20
Field Required Description
subject_kind yes capability_invocation, workflow_stage, provisional_block, or grant_evaluation
subject_oid yes OID of the evaluated object
status yes DecisionStatus (see below)
capability_grant_oids yes Array of grant OIDs that were evaluated
decided_at_ms yes Gateway-stamped decision timestamp
detail no Error code string (from the error code registry)
compliance_tags no Array of compliance tag strings (gateway-populated, not in OID hash)
is_idempotency_replay no Boolean. true if this receipt is a cached replay
client_claimed_at_ms no Client-supplied invoked_at_ms for physical_safety invocations
max_offline_ttl_ms no Maximum duration a verifier MAY cache this receipt offline
signer_identity no For 21 CFR Part 11 contexts: display name, role, and credential identifier of the authorizing human. The granted_by actor OID SHOULD resolve to this identity.
sequence_number no Monotonically increasing integer within the tenant, incremented per receipt, gapless. Gaps in the sequence indicate dropped receipts. Provides determinable ordering within a millisecond for high-frequency deployments (MiFID II RTS 25).
decided_at_ns no Optional nanoseconds since Unix epoch for sub-millisecond precision. RECOMMENDED for financial.* capabilities.

A gateway MUST guarantee strict monotonicity of sequence_number within a tenant. For financial.* capabilities, the gateway SHOULD populate decided_at_ns.

DecisionStatus values: ok, denied, failed, deferred, timed_out, pending, rate_limited.

6.3. Token Consumption on Receipt [DESIGN]

When a token_budget precondition is active, the gateway settles actual consumption onto the receipt after execution via the token_consumption field.

TokenConsumption:

Table 21
Field Required Description
input_tokens yes Input (prompt) tokens consumed; non-negative integer
output_tokens yes Output (completion) tokens consumed; non-negative integer
model yes Model identifier
cost_usd no Estimated cost in USD. [MODELED]
settled_at_ms yes Unix epoch ms when consumption was settled

6.4. Compliance Tags

A gateway MUST populate compliance_tags on every receipt. The minimum tag set:

Table 22
Tag When set
safety_class:A, safety_class:B, safety_class:C Capability safety class
physical_safety physical_safety: true capability
hitl_approved Workflow reached terminal_outcome: approved
hitl_denied Workflow reached terminal_outcome: denied
idempotency_replay is_idempotency_replay: true
rate_limited status: rate_limited
phi Declaration has privacy_classification: phi

compliance_tags are gateway-populated. They MUST NOT be included in the OID hash computation. A verifier MUST NOT trust compliance_tags as normative; they are informational audit annotations.

7. Agent Delegation Chain [DESIGN]

A gap:orchestration_chain CDRO captures the ordered sequence of delegation hops that authorized a terminal capability invocation. It consolidates multi-agent pipelines into a single auditable object.

7.1. Structure

An OrchestrationChainBody:

Table 23
Field Required Description
root_actor_oid yes Actor OID that initiated the chain
steps yes Ordered array of DelegationStep, maximum 10
capability_name yes The capability name being delegated through the chain
final_invocation_oid yes OID of the terminal CapabilityInvocation CDRO

Each DelegationStep:

Table 24
Field Required Description
step_index yes Zero-based position of this hop in the chain
delegator_actor_oid yes Actor OID performing the delegation
delegatee_actor_oid yes Actor OID receiving authority
grant_oid yes OID of the grant authorizing this hop
prior_receipt_oid no OID of the receipt from the prior hop (absent for step_index 0)
delegated_at_ms yes Unix epoch ms of delegation
step_signature yes Signature over canonical(priorreceiptoid + invocation_body), base64url
step_signature_alg yes Algorithm used for step_signature

7.2. Constraints

MUST: the steps array MUST NOT exceed 10 entries. A gateway MUST return HTTP 400 with error code delegation_depth_exceeded when this limit is breached.

MUST: signing keys for each hop MUST be declared at grant issuance. The gateway MUST verify each step's signature before allowing the terminal invocation.

MUST: at invocation time, the gateway MUST re-validate that all grants referenced in the chain are non-expired and non-revoked. Partial chain validation is insufficient.

A CapabilityInvocationBody MAY carry delegation_chain (array of DelegationStep) as a shorthand when the full gap:orchestration_chain CDRO is not yet materialized. The same maximum-10-hop and per-step signature verification rules apply.

9. Offline Execution Profile

An Offline Execution Profile (OEP) enables a device or agent to evaluate grants and issue provisional receipts without network connectivity to the gateway.

9.1. OEP Bundle

A gap:offline_bundle is a gateway-signed CDRO containing everything needed for offline grant evaluation:

Table 26
Field Required Description
grant yes Full CapabilityGrant CDRO (inline, not by reference)
declaration yes Full CapabilityDeclaration CDRO for the granted actor
keyring yes KeyringExport CDRO (see Offline Key Distribution)
revocation_snapshot yes RevocationSnapshot CDRO (see Offline Revocation)
offline_policy yes OfflinePolicy block

OfflinePolicy:

Table 27
Field Required Description
max_offline_duration_ms yes Maximum duration the bundle is valid for offline use
max_offline_invocations yes Maximum invocations permitted during the offline period
offline_capability_filter no Array of capability patterns permitted offline; absent = all granted capabilities permitted
offline_allowed no Boolean. For safetyclass C with physicalsafety: true, offline operation MUST NOT proceed unless this is explicitly true

OEP bundles are fetched via GET /v1/gap/offline-bundle?grant_oid=<oid> and expire at expires_at_ms.

9.2. Offline Evaluation

When operating offline, the device:

  1. Verifies the OEP bundle signature against the locally-held root public key

  2. Checks grant.expires_at_ms against local clock

  3. Checks offline_policy.max_offline_duration_ms from bundle issuance

  4. Evaluates scope_narrowing against invocation args

  5. Increments local invocation counter; denies if max_offline_invocations is reached

  6. Issues a provisional receipt signed with the device-local key pre-provisioned at enrollment

Provisional receipts carry status: 'ok:offline' and include oep_bundle_oid referencing the OEP bundle.

For safetyclass C capabilities with physicalsafety: true, offline operation MUST NOT proceed unless the OEP bundle's offline_policy.offline_allowed is explicitly true.

9.3. Reconciliation

On reconnection, the device submits accumulated provisional receipts via POST /v1/gap/offline-receipts (array of provisional receipt CDROs).

The gateway: 1. Validates each provisional receipt against the referenced OEP bundle 2. Issues an authoritative receipt (status: ok) or rejection receipt (status: denied) 3. Rejection does not retroactively void the physical action but establishes the audit record

9.4. Sovereign Mode

For fully self-hosted deployments with no external connectivity, operators MAY run a locally-operated gateway with a locally-generated root-of-trust keypair. In sovereign mode: - All CDRO types are valid with locally-generated OIDs - The root public key is distributed at device provisioning time - No SynOI infrastructure is required at any point in the lifecycle

10. Workflows

10.1. Purpose

A WorkflowDefinition describes a multi-stage Human-in-the-Loop (HITL) process triggered by a specific capability invocation. When a grant's pending_workflow field references a workflow definition, the gateway instantiates a WorkflowInstance instead of immediately producing a terminal receipt.

10.2. Workflow Lifecycle

  1. On invocation, the gateway emits a receipt with status: pending.

  2. The workflow proceeds through stages. Each stage may listen for channel events (SMS, push notification, webhook, overlay approval).

  3. When the workflow reaches a terminal stage, the gateway emits a NEW receipt with the terminal status. The pending receipt MUST NOT be modified in place.

  4. The terminal receipt's capability_grant_oids MUST include the grant OIDs from the triggering invocation.

10.3. Signal Sender Validation

A gateway MUST validate the sender of every channel event used as a workflow signal:

  • If a WorkflowStage's StageListen specifies required_from_binding, the gateway MUST verify the event's from field matches before accepting the event as a valid stage signal.

  • For every channel kind on physical_safety: true stages, the gateway MUST verify the channel event's authenticity using the mechanism appropriate to the channel adapter (e.g. webhook HMAC, push token binding, local credential verification). No single channel is normatively required for this check; the Channel Adapter interface defines the verification contract.

  • The same actor_oid MUST NOT be counted as two approvals for the same stage (no self-counting).

10.3.1. Channel Adapters

GAP workflow stages deliver signals and receive responses through Channel Adapters. A Channel Adapter is an implementation of the following interface:

  • kind: a ChannelKind string identifying the adapter

  • performAction(stage, context): executes the stage action (sends alert, invokes tool, etc.)

  • armListen(stage, context): arms the adapter to receive inbound signals (reply, button tap, etc.)

  • health(): returns whether the adapter is currently operational

The gateway MUST support at minimum the sms channel kind. All other channel kinds are OPTIONAL but MUST conform to this interface when implemented.

10.3.1.1. Defined Channel Kinds
Table 28
Kind Description Connectivity
sms SMS message via any provider (Twilio, AWS SNS, etc.) Internet
voice Outbound voice call with IVR response Internet
email Email with approve/deny link Internet
slack Slack message with Block Kit interactive components Internet
mobile_push APNs / FCM push notification Internet
sse Server-Sent Events to a connected dashboard LAN/Internet
webhook HTTP POST to a configured endpoint LAN/Internet
in_app In-app overlay or notification Local
game_engine Game engine event (Unity, Unreal, Godot hook) Local
local_terminal Operator console at the local enforcement point; requires hardware token or biometric Local/Air-gapped
hmi_panel HMI touchscreen or physical operator panel at the device Local/Air-gapped
opc_ua_ack OPC-UA operator acknowledgement signal from a process historian or SCADA terminal Local/Air-gapped
local_signed_token Physical signed token (QR code, smart card, NFC) scanned at the device Local/Air-gapped

Custom channel kinds MAY be used by implementing the Channel Adapter interface. The kind field accepts any string value; non-standard kinds SHOULD use a reverse-domain prefix (e.g. com.example.pager).

10.3.1.2. Air-Gapped HITL

For deployments without external connectivity, air-gapped channel types (local_terminal, hmi_panel, opc_ua_ack, local_signed_token) produce stage transition CDROs signed by the local gateway instance. These are synchronized to the cloud gateway at next connectivity to establish the complete receipt chain.

Local channel adapters authenticate the operator using locally-held credentials: - local_terminal: smart card, hardware token, or biometric; verified against locally-registered public key - hmi_panel: operator badge scan or PIN verified against local roster CDRO - opc_ua_ack: OPC-UA NodeId-scoped acknowledgement from an authorized operator session - local_signed_token: scanned CDRO (QR or NFC) verified against locally-held root public key

The signal sender validation rule (required_from_binding) applies to all channel kinds. For local channels, required_from_binding references the operator's local actor OID rather than a phone number or Slack user ID.

10.4. Two-Person Integrity

A stage MAY specify authorized_approvers (array of actor OIDs). When set:

  • The gateway MUST verify the signal sender resolves to one of the listed OIDs.

  • The workflow actor (the actor who triggered the invocation) MUST NOT serve as an approver (no self-approval).

10.5. Safety Constraints on Workflow Definitions

For any stage triggered by a physical_safety: true or safety_class: C capability, a gateway MUST reject the workflow definition registration if any on_timeout path leads to a terminal stage with terminal_outcome: approved. Timeout MUST NOT produce approval for physical-safety capabilities.

The minimum duration_seconds for any stage triggered by a safety_class: C capability is 30 seconds.

11. Revocation

11.1. Revocation Kinds

Table 29
Kind Effect Use case
Immediate (L1) Grant denied from effective_at_ms forward Contractor access removal
Scheduled (L2) Grant denied after effective_at_ms Expiry enforcement
Provisional block Capability temporarily blocked pending L3 quorum Anomaly detection
L3 quorum Block becomes permanent on quorum; may auto-renew High-stakes revocation

11.2. Provisional Block Policy

A RevocationEvent with revocation_kind: provisional_block MAY carry provisional_block_policy:

Table 30
Field Description
on_expiry_without_quorum renew or revert
min_approvers Minimum approver count for quorum
provisional_block_ttl_ms Operator override for the block TTL. Defaults to 72 hours. Minimum: 1 hour.

The provisional block TTL defaults to 72 hours. Operators MAY set provisional_block_ttl_ms on the RevocationEventBody to override. Minimum: 1 hour. For safety_class C capabilities with on_expiry_without_quorum: renew, the renewal cycle period equals provisional_block_ttl_ms.

When the provisional block TTL expires and quorum has not been reached:

  • If on_expiry_without_quorum is renew (or the field is absent on a physical_safety: true grant): the block MUST auto-renew. The renewal MUST produce a receipt with subject_kind: provisional_block and status: pending.

  • If on_expiry_without_quorum is revert: the block is lifted (capability re-enabled). The revert value MUST NOT be used for physical_safety: true capabilities regardless of the field value.

11.3. Offline Revocation Bundle

A gap:revocation_bundle enables offline devices to check grant revocation status without contacting the gateway.

RevocationBundleBody:

Table 31
Field Required Description
revocations yes Array of RevocationEntry
snapshot_at_ms yes Unix epoch ms when the snapshot was taken
expires_at_ms yes Maximum age of this bundle; devices MUST NOT use it after this time
tenant_id yes Tenant this bundle applies to

Each RevocationEntry:

Table 32
Field Required Description
grant_oid yes OID of the revoked grant
effective_at_ms yes When the revocation became effective
kind yes immediate, scheduled, or provisional_block

Export via GET /v1/gap/revocations/bundle?since_ms=<ms>. The bundle is gateway-signed.

Fail-safe rules for offline devices:

  • Devices operating with physical_safety: true capabilities MUST require a valid (not-expired) revocation bundle as a precondition for any invocation

  • If the device cannot obtain a fresh-enough bundle before expires_at_ms, the fail-safe action for physical_safety: true and safety_class C capabilities MUST be DENY

  • The maximum acceptable bundle age is set by max_revocation_bundle_age_ms on the grant; if absent, the gateway default applies (RECOMMENDED: 24 hours for class C, 7 days for class A/B)

11.4. Break-Glass Grants

A break-glass grant pre-authorizes a defined set of emergency capabilities with an offline-verifiable signed token, for use when the gateway is unreachable and immediate action is required for safety or clinical reasons.

11.4.1. Break-Glass Grant Fields

A grant with break_glass: true additionally carries:

Table 33
Field Required Description
break_glass yes Boolean true. Marks this grant as a break-glass grant.
break_glass_token yes A signed CDRO of type gap:break_glass_token pre-issued by the gateway and stored securely on the device.
break_glass_ttl_ms yes TTL of the break-glass token in milliseconds from issuance. RECOMMENDED: 4 hours.
break_glass_max_invocations no Maximum invocations allowed under this token before it is exhausted. Defaults to 1 for safety_class C.
break_glass_requires_reason no Boolean. When true, the invoker MUST supply a break_glass_reason string in invocation args.

11.4.2. Break-Glass Token

A gap:break_glass_token CDRO body:

Table 34
Field Required Description
grant_oid yes OID of the break-glass grant this token activates
actor_oid yes OID of the authorized invoker
valid_from_ms yes Token validity start (Unix epoch ms)
expires_at_ms yes Token validity end (Unix epoch ms)
permitted_capabilities yes Array of capability patterns this token authorizes (subset of the grant)
max_invocations yes Maximum invocations before token is exhausted

The token is gateway-signed at provisioning time. The device verifies the signature using the locally-held public key (from the key bundle, per Offline Key Distribution) before activating break-glass operation.

11.4.3. Break-Glass Invocation

A break-glass invocation MUST carry break_glass_token_oid in the invocation args. The enforcement point verifies:

  1. The token OID matches a locally-held break-glass token CDRO

  2. The token signature is valid

  3. The token has not expired (expires_at_ms > current local clock)

  4. The invoked capability matches permitted_capabilities

  5. The token invocation counter has not reached max_invocations

  6. If break_glass_requires_reason: true, break_glass_reason is non-empty

Break-glass invocations produce a mandatory provisional receipt with compliance_tags: ['break_glass']. The device MUST submit these receipts to POST /v1/gap/offline-receipts at next connectivity.

11.4.4. Local Override Credential

For lifting a provisional block when the gateway is unreachable, an operator MAY provision a Local Override Credential (LOC) at grant issuance time.

A gap:local_override_credential CDRO body:

Table 35
Field Required Description
grant_oid yes OID of the provisionally-blocked grant
actor_oid yes OID of the authorized override operator
expires_at_ms yes Credential expiry
single_use yes Always true. An LOC MUST be invalidated after first use.
override_reason_required yes Boolean. When true, operator MUST supply a reason string.

The LOC is signed by the operator's key at grant issuance time and stored physically at the installation site (QR code, USB token, or printed secure storage). When presented at the local enforcement point, it lifts the provisional block exactly once and produces a local override receipt synchronized at next uplink.

Neither break-glass nor LOC mechanism allows indefinite ungoverned operation. Both produce mandatory audit trails submitted to the gateway at next connectivity.

11.5. L3 Quorum Approval

A revocation event may collect approvals via POST /v1/gap/revoke/approve. Each approval MUST:

  • Identify a unique approver_actor_oid (no duplicate approvers).

  • Not be made by the same actor who created the revocation event (no self-approval).

When min_approvers is reached, the gateway MUST set effective_at_ms on the revocation event and enforce the revocation.

12. HTTP API Surface

A GAP-conformant gateway MUST expose the following endpoints under a base path (conventionally /v1/gap). All endpoints require Bearer token authentication. Tenant isolation MUST be enforced: every CDRO fetch endpoint MUST verify stored_object.tenant_id === authenticated_tenant_id. On mismatch, the gateway MUST return HTTP 404 (never HTTP 403, which would confirm cross-tenant OID existence).

12.1. Core Endpoints

Table 36
Method Path Description
POST /declarations Register a CapabilityDeclaration
GET /declarations/:oid Fetch a declaration by OID
POST /grants Issue a CapabilityGrant
GET /grants List grants (query: actor_oid, capability, status)
GET /grants/:oid Fetch a grant by OID
POST /invoke Invoke a capability
POST /invoke/batch Submit up to 1000 invocations sharing a tenant in a single request (L2 OPTIONAL). Returns array of receipts in submission order. Grant evaluation is independent per invocation.
GET /receipts List receipts (query: actor_oid, grant_oid, capability, from_ms, to_ms, status, limit, cursor)
GET /receipts/:oid Fetch a receipt by OID
GET /limits/:pool_id Returns current rolling sum and remaining headroom for a named aggregate limit pool.

12.2. Revocation Endpoints

Table 37
Method Path Description
POST /revoke Immediate or scheduled revocation
POST /revoke/provisional-block Provisional block (anomaly response)
POST /revoke/approve Submit L3 quorum approval
GET /revocations/:oid Fetch a revocation event by OID
GET /revocations List revocation events (query: grant_oid, target_kind, since_ms)

12.3. Workflow Endpoints

Table 38
Method Path Description
POST /workflows/definitions Register a WorkflowDefinition
GET /workflows/definitions/:oid Fetch a definition by OID
GET /workflows/instances/:oid Fetch a workflow instance
GET /workflows/instances/:oid/transitions List stage transitions
POST /workflows/signal Deliver a channel event signal

12.4. Key Endpoints

Table 39
Method Path Description
GET /keys/current Fetch the current signing key (public)
GET /keys/:key_id Fetch a signing key by ID (public)
GET /keys/bundle Fetch a signed keyring export bundle for offline verifiers

12.5. Offline Endpoints

Table 40
Method Path Description
GET /offline-bundle Fetch an OEP bundle for offline grant evaluation (query: grant_oid)
POST /offline-receipts Submit accumulated provisional receipts for reconciliation
GET /revocations/bundle Fetch a signed revocation snapshot bundle (query: since_ms)

13. Conformance

GAP implementations declare a conformance tier. A higher tier is a superset of all lower tiers.

13.1. Tier L1: Object Layer

An L1 implementation:

  • MUST validate CDRO envelopes per {{object-model}}.

  • MUST compute and verify content-addressed OIDs per {{oid-computation}}.

  • MUST produce GapDecisionReceipts for every gate decision.

  • MUST enforce tenant isolation on all CDRO fetch operations.

  • Does NOT enforce scope_narrowing evaluation (steps 1-3 of grant evaluation only: expiry, revocation, grantee match).

  • A grant containing any GrantedCapabilityScope with non-empty scope_narrowing MUST be rejected by an L1-conformant gateway at grant issuance time with HTTP 400 and error type tier_insufficient.

13.2. Tier L2: Grant Evaluation Layer

An L2 implementation satisfies L1 and additionally:

  • MUST enforce scope_narrowing per {{scope-narrowing}}.

  • MUST enforce delegation subset rules per {{delegation}}.

  • MUST enforce invocation timestamp validation.

  • MUST implement idempotency per {{idempotency}}.

  • MUST enforce aggregate_limits rolling windows.

13.3. Tier L3: Workflow Layer

An L3 implementation satisfies L2 and additionally:

  • MUST implement workflow instantiation, stage transitions, and HITL channel signaling.

  • MUST implement optional_effects evaluation.

  • MUST implement provisional block with quorum semantics.

  • MUST emit the complete pending + terminal receipt chain for every workflow.

13.4. Tier L4: Federation Layer

An L4 implementation satisfies L3 and additionally:

  • MUST implement L3 quorum revocation with multi-party approval.

  • MUST implement cross-tenant receipt verification.

  • MUST implement federation handshakes.

13.4.1. L3-PQ and L4-PQ (Post-Quantum Only)

For deployments under CNSA 2.0 (NSS policy) or equivalent post-quantum mandates where Ed25519 is not an approved algorithm:

Table 41
Tier Signing Use case
L3-PQ ML-DSA-65 only (no Ed25519) Classified / NSS deployments requiring CNSA 2.0 compliance
L4-PQ ML-DSA-65 only + authorized axis Classified / regulated deployments requiring PQ + full audit chain

The existing L4 hybrid mode (Ed25519 + ML-DSA-65) remains for interoperability with non-classified systems. Pure ML-DSA-65 operation without Ed25519 is the conformant path for CNSA 2.0 deployments. The signature_algorithm field in the CDRO envelope (value: ML-DSA-65) identifies PQ-only receipts.

Security Considerations: Ed25519 is not approved for National Security Systems under CNSSP-15 and CNSA 2.0. New NSS deployments MUST use L3-PQ or L4-PQ minimum.

13.5. Conformance by Sector

Table 42
Sector Minimum Tier Rationale
MCP tool governance L2 Scope enforcement mandatory
AI agent pipelines L2 Delegation chains required
Smart home (consumer) L2 User-issued grants with scope
Industrial / OT L3 HITL + provisional block required
Medical device / clinical (21 CFR Part 11) L4 MINIMUM HITL + signed receipts + authorized axis required. L3 without signed receipts does not satisfy 21 CFR Part 11 Section 11.50 electronic signature requirements. An L3 deployment for medical devices is non-conformant to Part 11.
Physical security L3 HITL + two-person integrity
Cross-tenant / federated L4 Federation semantics

14. Security Considerations

14.1. OID Integrity

The security of GAP receipts depends on the integrity of OID computation. An implementation MUST compute OIDs using the canonical JSON form defined in {{canonical-json}}. Divergence in canonical form between the issuing gateway and a verifier produces OID mismatch, which is detectable but MUST be investigated; it indicates either a bug or an active tampering attempt.

Implementations across different languages MUST independently verify that their canonical JSON output matches the test vectors in the @synoi/gap TypeScript package and the Python synoi-gap package. The pinned test vectors in test/conformance.test.ts serve as cross-implementation reference points.

14.2. Signature Verification

14.2.1. When signing is required

A gateway MUST sign a receipt with Ed25519 [RFC8032] when any of the following are true:

  1. The effective require_signed_receipt for the capability is true. The effective value is determined by evaluating in precedence order: (a) GrantedCapabilityScope.require_signed_receipt if present; (b) CapabilitySpec.require_signed_receipt if present; (c) the gateway's configured default signing policy.

  2. The gateway is configured to sign all receipts by default and neither the grant nor the declaration has suppressed signing with require_signed_receipt: false.

  3. The capability's effective privacy_classification is financial or matches financial.*, OR the deployment asserts 21 CFR Part 11 compliance. For capabilities in these categories, require_signed_receipt MUST default to true and MUST NOT be overridable to false by operator grant override.

A gateway MAY be configured with a safety_class_signing_floor that sets the minimum safety_class for which signatures are mandatory regardless of require_signed_receipt settings (e.g. B means all class B and C receipts are signed). Gateway configuration SHOULD document whether safety_class_signing_floor is set, as it affects the signing behavior for every tenant on that deployment.

A gateway MUST NOT sign a receipt when the effective require_signed_receipt is false, regardless of the server's default policy. This allows actors to opt specific high-frequency capabilities out of signing cost.

When signing is not required, the signature and signature_key_id fields MUST be omitted from the receipt envelope.

14.2.2. Verifier obligations

Verifiers MUST:

  • Fetch the signing key from the gateway's /keys/:key_id endpoint using the signature_key_id from the receipt.

  • Verify that the key's expires_at_ms has not passed.

  • Verify the signature over the canonical envelope (with the same field exclusions used for OID computation).

A verifier that receives a receipt without a signature field for a capability where require_signed_receipt is true MUST treat the receipt as invalid.

Key rotation: a gateway MUST publish a new key before retiring an old one. Old keys remain valid for verifying receipts signed under them. The current key is always the one returned by GET /keys/current.

14.2.3. Offline Key Distribution

A gap:keyring_export CDRO enables verifiers to operate without a live /keys endpoint.

KeyringExportBody:

Table 43
Field Required Description
keys yes Array of KeyEntry
exported_at_ms yes Unix epoch ms when the bundle was generated
expires_at_ms yes Bundle validity window

Each KeyEntry:

Table 44
Field Required Description
key_id yes Key identifier matching signature_key_id on receipts
public_key_base64 yes Base64url-encoded public key bytes
algorithm yes Ed25519, ML-DSA-65, or Ed25519+ML-DSA-65
valid_from_ms yes Key validity start
expires_at_ms yes Key validity end

The bundle is signed by the gateway's current root key so verifiers can authenticate it. Export via GET /v1/gap/keys/bundle. The bundle is distributable as a file, QR code, or USB for air-gapped deployments.

Offline verifier rules: 1. Load the bundle at provisioning time; verify bundle signature against locally-installed root public key 2. On receipt verification, look up key_id in local bundle only 3. If key_id is not in the bundle, treat receipt as UNVERIFIABLE (not INVALID); the key may exist but has not been delivered yet 4. If the bundle is expired, treat all receipts issued after expires_at_ms as UNVERIFIABLE until a fresh bundle is obtained

A signature_algorithm field MUST be added to the CDRO envelope (alongside signature and signature_key_id): values: Ed25519, ML-DSA-65, Ed25519+ML-DSA-65. Verifiers MUST NOT guess the algorithm from key length.

14.3. Tenant Isolation

A gateway MUST NOT return a CDRO belonging to one tenant in response to a request authenticated as another tenant. The response for cross-tenant or not-found OIDs MUST be HTTP 404 to avoid confirming cross-tenant OID existence.

14.4. Delegation Chain Security

A gateway MUST evaluate the full delegation chain on every invocation. A child grant whose parent has been revoked or expired MUST be denied, even if the child grant itself has not expired. Partial chain validation is insufficient.

14.5. Provisional Block Fail-Closed

For physical_safety: true grants, a gateway MUST treat the absence of on_expiry_without_quorum as renew. A gateway MUST NOT allow revert semantics for physical-safety capabilities regardless of the field value. This ensures that a gateway with misconfigured or absent provisional block policy defaults to the safer state (block maintained).

14.6. Workflow Signal Injection

A gateway MUST validate the sender identity of every channel event before advancing a workflow stage. Failure to validate enables unauthorized parties to advance or terminate HITL workflows by injecting signals. The required_from_binding field provides the expected sender identity; its enforcement is REQUIRED for physical_safety: true stages.

14.7. Idempotency Cache Staleness

An idempotency cache hit MUST NOT return a cached approval receipt without re-validating that the underlying grant is still active. A grant may be revoked after the original approval. Returning a cached receipt for a revoked grant enables unauthorized capability exercise after revocation.

14.8. Negative Numeric Scope Values

For physical_safety: true capabilities, a gateway MUST reject invocation arguments that contain negative numeric values for scope-constrained keys, even when those values would satisfy an upper-bound check. A negative value for a physical parameter (e.g. max_delta_units: -5.0) is almost always a sign of argument manipulation rather than a legitimate invocation.

14.9. Bearer Token Security

GAP does not define the authentication mechanism for the Bearer tokens used to authenticate gateway API calls. Implementors MUST use short-lived tokens, rotate signing keys, and follow [RFC6750] for Bearer token handling. Bearer token theft is not mitigated by this protocol; it is a deployment concern.

14.9.1. Self-Sovereign Credential Mode

For deployments without external connectivity or without a SynOI-operated token issuer, a Self-Sovereign Credential (SSC) mode provides an alternative authentication path.

In SSC mode: 1. The operator bootstraps a Local Credential Authority (LCA) by generating a local root signing keypair and publishing a gap:lca_root CDRO signed by the root key. 2. Actor credentials are short-lived signed tokens bound to actor_oid and tenant_id, issued by the LCA and signed with ML-DSA-65 (or Ed25519 for non-NSS deployments). 3. The gateway verifies actor credentials using the locally-held LCA public key only. 4. Credential rotation requires only local key material.

The existing synoi-sk- Bearer token remains valid for cloud-connected deployments. SSC mode is a normative alternative, not a deployment footnote.

The gap:lca_root CDRO type is defined with body: { root_public_key_base64: string, algorithm: string, tenant_id: string, valid_from_ms: number, expires_at_ms: number }.

15. IANA Considerations

This document makes no requests to the Internet Assigned Numbers Authority (IANA) at this time.

Future revisions of this specification MAY request registration of:

16. Normative References

[RFC2119]
Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, DOI 10.17487/RFC2119, , <https://www.rfc-editor.org/info/rfc2119>.
[RFC8174]
Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174, , <https://www.rfc-editor.org/info/rfc8174>.

17. Informative References

[RFC6750]
Jones, M. and D. Hardt, "The OAuth 2.0 Authorization Framework: Bearer Token Usage", RFC 6750, DOI 10.17487/RFC6750, , <https://www.rfc-editor.org/info/rfc6750>.
[RFC8259]
Bray, T., Ed., "The JavaScript Object Notation (JSON) Data Interchange Format", STD 90, RFC 8259, DOI 10.17487/RFC8259, , <https://www.rfc-editor.org/info/rfc8259>.
[RFC7517]
Jones, M., "JSON Web Key (JWK)", RFC 7517, DOI 10.17487/RFC7517, , <https://www.rfc-editor.org/info/rfc7517>.
[RFC8785]
Rundgren, A., Jordan, B., and S. Erdtman, "JSON Canonicalization Scheme (JCS)", RFC 8785, DOI 10.17487/RFC8785, , <https://www.rfc-editor.org/info/rfc8785>.
[RFC8032]
Josefsson, S. and I. Liusvaara, "Edwards-Curve Digital Signature Algorithm (EdDSA)", RFC 8032, DOI 10.17487/RFC8032, , <https://www.rfc-editor.org/info/rfc8032>.

Appendix A. Normative References

[RFC2119]:

Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, March 1997.

[RFC8174]:

Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words", BCP 14, RFC 8174, May 2017.

[RFC8032]:

Josefsson, S. and I. Liusvaara, "Edwards-Curve Digital Signature Algorithm (EdDSA)", RFC 8032, January 2017.

[RFC6750]:

Jones, M. and D. Hardt, "The OAuth 2.0 Authorization Framework: Bearer Token Usage", RFC 6750, October 2012.

Appendix B. Informative References

[RFC8259]:

Bray, T., Ed., "The JavaScript Object Notation (JSON) Data Interchange Format", STD 90, RFC 8259, December 2017.

[RFC8785]:

Rundgren, A., Jordan, B., and S. Erdtman, "JSON Canonicalization Scheme (JCS)", RFC 8785, June 2020.

[RFC7517]:

Jones, M., "JSON Web Key (JWK)", RFC 7517, May 2015.

Appendix C. Acknowledgments

The editors thank the SynOI protocol team and the early implementors who provided feedback on the wire format and grant evaluation algorithms during the cross-sector safety review that produced the ADR_006 safety protocol.

Author's Address

Joshua Shovan
SynOI Inc
United States of America