Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,100 @@ export const schemas: Record<string, unknown>;
*/
export function getSchema(id: string): unknown;

// ---------------------------------------------------------------------------
// AEP record type (mirrors schemas/aep/aep-record.schema.json).
//
// The schema is additive/evolving: it does not set additionalProperties: false,
// so validated records may carry fields not listed here. The interfaces below
// model the documented fields; retrieve the authoritative shape with
// `getSchema('aep-record')`.
// ---------------------------------------------------------------------------

/** Allowed `schema_version` values for an AEP record. */
export type AepSchemaVersion = 'aep/v0.1' | 'aep/v0.2' | 'aep/v0.3';

/** A recorded capability decision (allow / deny / ask_user / dry_run). */
export interface AepCapabilityDecision {
capability: string;
subject: string;
resource: string;
decision: 'allow' | 'deny' | 'ask_user' | 'dry_run';
reason_code?: string;
}

/** A single runtime action captured as evidence. */
export interface AepAction {
action_id: string;
tool_name: string;
state_changing: boolean;
timestamp_ms: number;
precondition_digest?: string;
result_digest?: string;
evidence_refs?: string[];
parent_action_id?: string;
causal_chain_id?: string;
tool_descriptor_digest?: string;
server_card_digest?: string;
scope_lease_id?: string;
approval_context_hash?: string;
input_taint_labels?: string[];
output_taint_labels?: string[];
memory_read_refs?: string[];
memory_write_refs?: string[];
pre_state_digest?: string;
post_state_digest?: string;
}

/** Result of a verifier run over the record. */
export interface AepVerifierResult {
verifier_id: string;
passed: boolean;
score?: number;
claim_ids?: string[];
}

/** Optional tamper-evident signature over the record. */
export interface AepSignature {
alg: string;
key_id: string;
sig: string;
bundle?: unknown;
transparency_log_ref?: string;
}

/**
* Agent Evidence Protocol record — runtime action evidence and run provenance.
* Mirrors `schemas/aep/aep-record.schema.json` (`aep/v0.3`). Only
* `schema_version`, `run_id`, and `created_at_ms` are required; every other
* field is optional and additive.
*/
export interface AepRecord {
schema_version: AepSchemaVersion;
run_id: string;
created_at_ms: number;
trace_id?: string;
parent_trace_id?: string | null;
repo_commit?: string;
runtime_version?: string;
model_provider?: string;
model_id?: string;
policy_bundle_digest?: string;
tool_manifest_digest?: string;
mcp_server_card_digest?: string | null;
capability_decisions?: AepCapabilityDecision[];
actions?: AepAction[];
verifier_results?: AepVerifierResult[];
budget_ledger?: Record<string, unknown>;
run_context?: Record<string, unknown>;
user_id?: string;
subject_id?: string;
side_effect_class?: 'read' | 'mutate-local' | 'mutate-external' | 'network-egress' | 'unknown';
run_side_effect_class_max?: 'read' | 'mutate-local' | 'mutate-external' | 'network-egress' | 'unknown';
recording_mode?: 'full' | 'delta' | 'validation';
argument_drift?: Record<string, unknown>;
signature?: AepSignature;
}

// ---------------------------------------------------------------------------
// Cross-repo schema drift detection.
// ---------------------------------------------------------------------------
Expand Down
17 changes: 17 additions & 0 deletions schemas/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,23 @@
"open-agent-audit"
],
"summary": "Trust Passport \u2014 signed, expiring, verifiable trust-state artifact for AI agents."
},
{
"id": "canonical-event",
"title": "CanonicalEvent",
"path": "schemas/v0.1/canonical-event.schema.json",
"canonical_id": "https://wasmagent.dev/schemas/v0.1/canonical-event.schema.json",
"version": "canonical-event/v0.1",
"stability": "evolving",
"owners": [
"open-agent-audit"
],
"consumers": [
"open-agent-audit",
"wasmagent-js",
"trace-pipeline"
],
"summary": "A single observable audit event in an agent's runtime trace \u2014 the canonical event shape that producers (open-agent-audit) emit and that maps onto aep-record."
}
]
}
142 changes: 142 additions & 0 deletions schemas/v0.1/canonical-event.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://wasmagent.dev/schemas/v0.1/canonical-event.schema.json",
"title": "CanonicalEvent",
"description": "A single observable event in an AI agent's runtime trace — the canonical audit-event shape emitted by open-agent-audit and other producers. Cross-repository contract: downstream repos consume this via @wasmagent/protocol and map it onto aep-record (see open-agent-audit adapter). The schema_version field identifies the event format and is independent of aep-record's schema_version.",
"type": "object",
"required": ["schema_version", "run_id", "event_id", "timestamp", "type", "actor", "agent_id", "model_id"],
"additionalProperties": false,
"properties": {
"schema_version": {
"const": "open-agent-audit/v0.1"
},
"run_id": { "type": "string", "minLength": 1 },
"session_id": { "type": "string" },
"agent_id": { "type": "string", "minLength": 1 },
"model_id": { "type": "string", "minLength": 1 },
"event_id": { "type": "string", "minLength": 1 },
"timestamp": {
"type": "string",
"format": "date-time",
"description": "RFC 3339 timestamp with timezone offset"
},
"type": {
"type": "string",
"enum": [
"tool_call",
"policy_decision",
"human_approval",
"observation",
"model_output",
"final_answer",
"error"
]
},
"actor": {
"type": "string",
"enum": ["agent", "user", "system", "tool", "human_reviewer"]
},
"tool": {
"type": "object",
"additionalProperties": false,
"required": ["name"],
"properties": {
"name": { "type": "string", "minLength": 1 },
"capability": { "type": "string" },
"args_hash": { "type": "string", "pattern": "^[A-Fa-f0-9]{64}$" },
"result_hash": { "type": "string", "pattern": "^[A-Fa-f0-9]{64}$" },
"risk_tags": {
"type": "array",
"items": { "type": "string" },
"uniqueItems": true
}
}
},
"policy": {
"type": "object",
"additionalProperties": false,
"required": ["decision", "reason"],
"properties": {
"decision": {
"type": "string",
"enum": ["allow", "deny", "ask_user"]
},
"reason": { "type": "string" },
"rule_id": { "type": "string" }
}
},
"human": {
"type": "object",
"additionalProperties": false,
"required": ["reviewer_id", "decision"],
"properties": {
"reviewer_id": { "type": "string" },
"decision": {
"type": "string",
"enum": ["approve", "deny", "escalate"]
},
"justification": { "type": "string" }
}
},
"error": {
"type": "object",
"additionalProperties": false,
"required": ["kind", "message"],
"properties": {
"kind": { "type": "string" },
"message": { "type": "string" }
}
},
"model_output": {
"type": "object",
"additionalProperties": false,
"properties": {
"content_hash": { "type": "string", "pattern": "^[A-Fa-f0-9]{64}$" },
"token_count": { "type": "integer", "minimum": 0 },
"finish_reason": { "type": "string" }
}
},
"observation": {
"type": "object",
"additionalProperties": false,
"properties": {
"source": { "type": "string" },
"content_hash": { "type": "string", "pattern": "^[A-Fa-f0-9]{64}$" },
"byte_size": { "type": "integer", "minimum": 0 }
}
},
"evidence": {
"type": "object",
"additionalProperties": false,
"properties": {
"evidence_id": { "type": "string" },
"hash": { "type": "string", "pattern": "^[A-Fa-f0-9]{64}$" },
"prev_hash": { "type": "string", "pattern": "^[A-Fa-f0-9]{64}$" },
"signature": { "type": "string" },
"signature_algorithm": {
"type": "string",
"enum": ["ed25519", "ecdsa-p256"]
},
"signer_key_id": { "type": "string" }
}
}
},
"allOf": [
{
"if": { "properties": { "type": { "const": "tool_call" } }, "required": ["type"] },
"then": { "required": ["tool"] }
},
{
"if": { "properties": { "type": { "const": "policy_decision" } }, "required": ["type"] },
"then": { "required": ["policy"] }
},
{
"if": { "properties": { "type": { "const": "human_approval" } }, "required": ["type"] },
"then": { "required": ["human"] }
},
{
"if": { "properties": { "type": { "const": "error" } }, "required": ["type"] },
"then": { "required": ["error"] }
}
]
}
65 changes: 63 additions & 2 deletions src/wasmagent_protocol/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,70 @@
from functools import lru_cache
from importlib import resources
from pathlib import Path
from typing import Any
from typing import Any, Literal, TypedDict

__all__ = ["INDEX", "get_schema", "schema_path", "schema_ids"]
__all__ = [
"AepRecord",
"INDEX",
"get_schema",
"schema_path",
"schema_ids",
]


# ---------------------------------------------------------------------------
# AEP record type (mirrors schemas/aep/aep-record.schema.json).
#
# The schema is additive/evolving and does not forbid additional properties,
# so validated records may carry fields not modelled here. These TypedDicts
# describe the documented top-level shape; retrieve the authoritative schema
# with ``get_schema("aep-record")``. Required keys live on the base class and
# the remainder are optional via ``total=False`` (Python 3.9-compatible).
# ---------------------------------------------------------------------------

_AepSideEffectClass = Literal[
"read", "mutate-local", "mutate-external", "network-egress", "unknown"
]


class _AepRecordRequired(TypedDict):
schema_version: Literal["aep/v0.1", "aep/v0.2", "aep/v0.3"]
run_id: str
created_at_ms: float


class AepRecord(_AepRecordRequired, total=False):
"""Agent Evidence Protocol record — runtime action evidence and run provenance.

Mirrors ``schemas/aep/aep-record.schema.json`` (``aep/v0.3``). Nested arrays
and objects (``actions``, ``capability_decisions``, ``budget_ledger`` …) are
typed as ``list[dict[str, Any]]`` / ``dict[str, Any]``; consult the schema
for their inner structure.
"""

trace_id: str
parent_trace_id: "str | None"
repo_commit: str
runtime_version: str
model_provider: str
model_id: str
policy_bundle_digest: str
tool_manifest_digest: str
mcp_server_card_digest: "str | None"
input_refs: "list[dict[str, Any]]"
output_refs: "list[dict[str, Any]]"
capability_decisions: "list[dict[str, Any]]"
actions: "list[dict[str, Any]]"
verifier_results: "list[dict[str, Any]]"
budget_ledger: "dict[str, Any]"
run_context: "dict[str, Any]"
user_id: str
subject_id: str
side_effect_class: _AepSideEffectClass
run_side_effect_class_max: _AepSideEffectClass
recording_mode: Literal["full", "delta", "validation"]
argument_drift: "dict[str, Any]"
signature: "dict[str, Any]"

_SCHEMAS_PKG = "wasmagent_protocol.schemas"
# Editable / source-checkout fallback: the canonical schemas live at the repo
Expand Down
47 changes: 47 additions & 0 deletions tests/aep-record.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Sample AEP record instantiation + conformance (wasmagent-protocol #138).
//
// The `AepRecord` TypeScript interface in index.d.ts documents the record
// shape; this runtime test validates a conforming sample against the canonical
// `aep-record` schema loaded through the package. Full JSON-Schema validation
// lives in the Python suite + tests/conformance.py; here we assert the sample
// carries every schema-required field and uses an accepted schema_version.
import assert from 'node:assert/strict';
import { test } from 'node:test';
import { getSchema } from '../index.js';

/** Sample record typed against the exported AepRecord interface. */
const sampleAepRecord = /** @type {import('../index.d.ts').AepRecord} */ ({
schema_version: 'aep/v0.3',
run_id: 'run-abc123',
trace_id: 'trace-001',
runtime_version: 'wasmagent-js@1.20.0',
created_at_ms: 1737600000000,
capability_decisions: [
{ capability: 'fs.write', subject: 'agent-1', resource: '/tmp/out.txt', decision: 'allow' },
],
actions: [
{ action_id: 'a1', tool_name: 'write_file', state_changing: true, timestamp_ms: 1737600000100 },
],
});

test('sample AEP record satisfies every schema-required field', () => {
const schema = getSchema('aep-record');
for (const key of schema.required) {
assert.ok(key in sampleAepRecord, `sample missing required field: ${key}`);
}
});

test('sample schema_version is accepted by the aep-record schema', () => {
const schema = getSchema('aep-record');
assert.deepEqual(
schema.properties.schema_version.enum,
['aep/v0.1', 'aep/v0.2', 'aep/v0.3'],
);
assert.ok(schema.properties.schema_version.enum.includes(sampleAepRecord.schema_version));
});

test('aep-record schema is the canonical wasmagent.dev record', () => {
const schema = getSchema('aep-record');
assert.equal(schema.$id, 'https://wasmagent.dev/schemas/aep/aep-record.schema.json');
assert.equal(schema.title, 'AEPRecord');
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"artifact_uri": "pkg:npm/example@1.0.0"
}
Loading
Loading