Skip to content
Merged
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
65 changes: 65 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,71 @@ and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.ht

## [Unreleased]

## [0.39.2] - 2026-05-27

**Theme: SEP-2787 envelope v2 shape, full wire round-trip, versioned
audit-event schema, and Qi-survey coverage mapping.**

The four mechanical alignments Vaara committed to in
`modelcontextprotocol/modelcontextprotocol#2787` after the
trust-surface grouping was incorporated into the SEP draft on
soup-oss commit `dd030d5b` ship as the v2 envelope shape:

1. `toolCalls` lives under `payloadDerived`, not `plannerDeclared`.
Tool bindings (name, server fingerprint, args commitment) are
facts derived from the request payload, not planner declarations.
2. `argsProjection` serialises with a JSON-stringified `projection`
field carrying the JCS-canonical encoding of the projection
object. The digest is taken over those bytes.
3. The v1 `kind`-discriminated union is dropped. `ArgsRef` (ref +
digest) and `ArgsProjection` (projection + projectionDigest)
self-discriminate by which fields are present.
4. Commitment-only audit composes on `ArgsProjection` as a
hash-only-identity projection of the form `{"digest":
"sha256:..."}`. No separate `ArgsDigest` type ships in the spec.

### Added
- `parse_attestation(d)` (and `sep2787_parse_attestation` from the
package root): inverse of `Attestation.to_dict()`. Reconstructs the
Python dataclass tree from a wire JSON dict so third-party
consumers of the v0 test vectors can parse, verify, and re-emit
envelopes byte-identically. Strict field-presence validation and
alg allowlisting on the boundary.
- `docs/audit_event_schema.md`: AUDIT-EVENT-SCHEMA-1.0, versioned
wire/storage contract for the audit events Vaara emits.
Independent of code version so third-party consumers can pin
without coupling to a Python runtime version.
- `docs/qi_survey_mapping.md`: Vaara surface coverage against the
taxonomy in Qi et al., *Towards Trustworthy Agentic AI*
(arXiv:2605.23989, 2026-05-17). Direct, partial, and
out-of-scope rows by Perceive / Plan / Act / Reflect / Learn /
Multi-agent / Long-horizon stage under both top-level dimensions.
- `tests/test_attestation_sep2787_wire.py`: 13 wire round-trip tests
covering `emit -> JCS bytes -> parse -> verify` across HS256,
ES256, RS256 for both `ArgsRef` and `ArgsProjection`, plus parse
rejection on missing-field and unsupported-alg inputs and a
byte-identical re-emit check.

### Changed
- `vaara.attestation.sep2787` emits the v2 envelope shape.
- `docs/sep2787-overt-mapping.md` field table updated to v2.
- `COMPLIANCE.md` "Position relative to open runtime-attestation
standards" gains a SEP-2787 v2 subsection alongside the OVERT 1.0
positioning.
- `vaara.attestation` package docstring covers both OVERT 1.0 and
SEP-2787 v2 surfaces (previously OVERT-only by omission).

### SEP-2787 reference implementation tag
- `sep2787-ref-v2`: v2 envelope shape with the four post-soup-oss
alignments and full wire round-trip. Pinned for cross-repo
provenance citation against
`modelcontextprotocol/modelcontextprotocol#2787` and the v0 test
vector PR (`#2789`).
- `sep2787-ref-v1` (preserved at commit `a61e87c`): camelCase
envelope, the prior proposed-shape artefact.
- `sep2787-ref-v0` (preserved at commit `3d7af54`): snake_case
envelope, the historical proposed-shape artefact.

## [0.39.1] - 2026-05-27

**Theme: SEP-2787 reference impl follows the spec into camelCase.**
Expand Down
26 changes: 26 additions & 0 deletions COMPLIANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,32 @@ Operators who need AAL-4 should pair Vaara with an independent
attestation provider. The Vaara-emitted evidence is the input to
that provider, not a replacement for it.

### SEP-2787 v2 tool-call attestation (v0.39.2)

`vaara.attestation.sep2787` ships a reference implementation of
SEP-2787, a per-tool-call JSON attestation envelope carried in MCP
`_meta`. The v2 envelope shape groups envelope fields under three
trust-surface blocks (`plannerDeclared`, `issuerAsserted`,
`payloadDerived`) with `toolCalls` as a payload-derived fact, not a
planner declaration. Signing modes are HS256 (HMAC-SHA256), ES256
(ECDSA P-256 raw r||s), and RS256 (RSASSA-PKCS1-v1_5). The signature
is computed over the JCS-canonical encoding of the four envelope
blocks `{version, alg, plannerDeclared, issuerAsserted,
payloadDerived}` and is excluded from its own input.

The two envelopes coexist. OVERT 1.0 is the operator-side attestation
kernel emitting per-action CBOR Base Envelopes. SEP-2787 is the
per-tool-call JSON envelope carried inside MCP transport. A
deployment can run both: the OVERT envelope binds the action chain
while the SEP-2787 envelope binds the specific tool-call payload.
Field-level mapping between the two lives in
[`docs/sep2787-overt-mapping.md`](docs/sep2787-overt-mapping.md).

`parse_attestation(d)` provides full wire round-trip: a third-party
consumer of the published v0 test vectors can parse JSON bytes,
verify the signature, and re-emit byte-identically. The reference
implementation is pinned at tag `sep2787-ref-v2`.

### Hardware TEE attestation hook (experimental, v0.18.0)

Beyond the software-signed attestation chain described above, Vaara
Expand Down
2 changes: 1 addition & 1 deletion clients/ts/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@vaara/client",
"version": "0.39.1",
"version": "0.39.2",
"description": "TypeScript client for the Vaara HTTP API. Conformal risk scoring, hash-chained audit, policy reload, named detectors.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
189 changes: 189 additions & 0 deletions docs/audit_event_schema.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
# Vaara Audit Event Schema, v1.0

Versioned wire/storage contract for the audit events that flow through
the Vaara execution layer. This document is the schema; the
implementation in `src/vaara/audit/trail.py` is one conforming emitter.
The schema is versioned independently of the Vaara Python package so
downstream consumers (compliance combiners, regulatory exports,
third-party verifiers) can pin to a schema version without coupling to
a runtime version.

## Status and scope

- Schema version: **1.0**
- Status: stable
- Applies to: events appended to `trail.jsonl` and to the JSON shape
returned by the audit HTTP API.
- Out of scope: the combiner that assembles per-Article evidence
reports, the signer that wraps an exported trail, the verifier that
walks the hash chain. Those consume conforming events and have their
own contracts.

A conforming emitter MUST produce events that satisfy the field
requirements below. A conforming consumer MUST tolerate optional
fields and unknown additive fields under the rules in
[§ Forward compatibility](#forward-compatibility).

## Event envelope

Every audit event is a JSON object with the following fields.

| Field | Type | Required | Description |
|---|---|---|---|
| `record_id` | UUIDv4 string | yes | Identifier unique to this single event. |
| `action_id` | UUIDv4 string | yes | Groups every event that belongs to one action lifecycle (request, score, decision, execute/block, outcome). |
| `event_type` | string enum | yes | Lifecycle stage. Closed set in [§ Event types](#event-types). |
| `timestamp` | number | yes | Unix epoch seconds (UTC), IEEE-754 double. Finite (no NaN, no ±∞). |
| `agent_id` | string | yes | Identity of the agent that submitted the action. Free-form, ≤ 256 bytes. |
| `tool_name` | string | yes | Name of the tool or action under interception. ≤ 512 bytes. |
| `data` | object | no | Event-specific payload. Schema by `event_type`, see [§ Data payloads](#data-payloads). Default `{}`. |
| `regulatory_articles` | array of objects | no | Regulatory provenance of this event. See [§ Regulatory article objects](#regulatory-article-objects). Default `[]`. |
| `previous_hash` | hex string | yes | SHA-256 of the predecessor record's `record_hash`. Empty string for the first record. |
| `record_hash` | hex string | yes | SHA-256 over the canonical encoding of the hashed-fields subset of this record. See [§ Hash chain](#hash-chain). |
| `system_operation` | string | no | prEN ISO/IEC 12792 transparency axis: how the AI system operated at this event. Metadata, not hashed. |
| `data_usage` | string | no | prEN ISO/IEC 12792 transparency axis: what data was consumed. Metadata, not hashed. |
| `decision_making` | string | no | prEN ISO/IEC 12792 transparency axis: how the conclusion was reached. Metadata, not hashed. |
| `limitations` | string | no | prEN ISO/IEC 12792 transparency axis: known constraints. Usually carried out-of-band. Metadata, not hashed. |

## Event types

`event_type` is a closed enum at schema 1.0. Additive values may
appear in a minor version bump; see [§ Forward compatibility](#forward-compatibility).

| Value | Lifecycle position |
|---|---|
| `action_requested` | Agent submitted an action; recorded before processing. |
| `risk_scored` | Scorer produced a risk assessment with conformal prediction interval. |
| `decision_made` | Allow / escalate / deny decided. |
| `action_executed` | Action was actually executed downstream. |
| `action_blocked` | Action was blocked before execution. |
| `escalation_sent` | Action routed to human reviewer. |
| `escalation_resolved` | Human reviewer responded. |
| `outcome_recorded` | Post-execution outcome observed and recorded. |
| `policy_override` | Manual override of a prior automated decision. |

Each event for one action references a single shared `action_id`. The
canonical lifecycle is `action_requested` → `risk_scored` →
`decision_made` → (`action_executed` | `action_blocked` |
`escalation_sent` → `escalation_resolved`) → `outcome_recorded`.
`policy_override` may appear at any point after `decision_made`.

## Hash chain

The chain is SHA-256 over a canonical JSON encoding of a strict subset
of the record. Encoding: `json.dumps(content, sort_keys=True,
separators=(",", ":"), allow_nan=False)`.

Fields included in `content`: `record_id`, `action_id`, `event_type`
(as string), `timestamp`, `agent_id`, `tool_name`, `data`,
`regulatory_articles`, `previous_hash`.

Fields excluded: `system_operation`, `data_usage`, `decision_making`,
`limitations`. Rationale: the four prEN ISO/IEC 12792 transparency
annotations may evolve with the WG4 draft. Excluding them keeps
records hash-stable under re-emission and avoids coupling chain
integrity to a moving annotation schema. A future major schema may
introduce a separate signed-bundle mechanism for transparency tagging.

`previous_hash` of the first record is the empty string `""`.
`record_hash` is computed at append time and stable across
re-serialization.

## Regulatory article objects

`regulatory_articles` is an array of objects. Each object has:

| Field | Type | Required | Description |
|---|---|---|---|
| `domain` | string | yes | Regulatory regime: `EU_AI_ACT`, `DORA`, `NIS2`, `MiFID_II`, `GDPR`. |
| `article` | string | yes | Article reference, e.g. `Article 12(1)` or `Article 9(2)(a)`. |
| `requirement` | string | yes | What the article requires. |
| `how_satisfied` | string | yes | How this event satisfies the requirement. |

The combiner uses `regulatory_articles` to assemble per-Article
evidence reports. Including the regulatory provenance in the
hash-covered fields means that tampering with article attribution
after the fact breaks chain verification.

## Data payloads

`data` carries event-specific structured fields. The schema by
`event_type` is non-exhaustive at 1.0 (consumers MUST accept unknown
keys). Reserved top-level keys:

- `action_requested`: `action_request` (object), `context` (object, optional).
- `risk_scored`: `risk_score` (number in [0, 1]), `interval` (`[low, high]`), `classifier_version` (string), `contributing_signals` (array).
- `decision_made`: `decision` (`allow` | `escalate` | `deny`), `threshold_set` (string), `verdict_inputs` (array).
- `action_executed`: `executor` (string), `duration_ms` (number).
- `action_blocked`: `reason` (string), `blocking_policy` (string).
- `escalation_sent`: `reviewer_queue` (string), `priority` (string).
- `escalation_resolved`: `reviewer_id` (string), `decision` (enum), `reason` (string).
- `outcome_recorded`: `outcome` (`success` | `failure` | `partial` | `unknown`), `feedback` (object, optional).
- `policy_override`: `overrider_id` (string), `prior_decision` (enum), `new_decision` (enum), `reason` (string).

Caller-controlled strings in `data` (`agent_id`, `reason`,
`override_reason`) MUST be treated as untrusted at narrative-rendering
time. The hash chain still covers original values; the renderer
sanitizes for display only.

## Numeric and string discipline

- `timestamp` is IEEE-754 double Unix epoch seconds, UTC. NaN, +∞, -∞
are rejected at the canonical-JSON boundary.
- Strings are UTF-8. ≤ 256 bytes for `agent_id`, ≤ 512 bytes for
`tool_name`. `record_id` and `action_id` are UUIDv4 in canonical form.

## Wire and storage encodings

JSONL on disk: one event per line, sorted keys, no trailing
whitespace. The hash chain is computed and verified against this
encoding. JSON over HTTP: the audit API returns events as JSON
objects, optionally wrapped in a pagination envelope; the event
object itself is byte-identical to the JSONL line modulo whitespace.
Other encodings (CBOR, Protobuf) may be defined in sibling specs
without bumping this version, provided they round-trip to the
canonical JSON form.

## Signing and export

A trail is exported as a zip bundle: `trail.jsonl`, `manifest.json`,
`trail.sig`, `signer_pubkey`. Signed message:
`SHA-256(trail.jsonl || manifest.json)`. Reference signing algorithms:
Ed25519 (default), ML-DSA-65 (FIPS 204). This schema does not
constrain the signing algorithm beyond requiring that the export
bundle carry the public key in a form the verifier can consume.

## Forward compatibility

- **Minor (1.x).** Additive only: new `event_type` values, new
optional fields, new entries inside `data`. Consumers MUST tolerate
unknown fields.
- **Major (2.0+).** Breaking changes: field removal, renames, change
of hash-input set, change of canonical encoding. Major bumps ship
with a migration note and the prior schema remains a valid emission
target for one release cycle.

Producers SHOULD include a schema version tag in the export manifest.
Events themselves do not carry a per-record schema field, since the
chain integrity guarantee covers re-emission only under the same
schema version.

## Relation to OVERT 1.0 and SEP-2787

OVERT 1.0 Base Envelopes (`vaara.attestation.overt`) are per-action
attestations encoded as deterministic CBOR. SEP-2787 v2 envelopes
(`vaara.attestation.sep2787`) are per-tool-call JSON envelopes carried
in MCP `_meta`. This audit-event schema is the full per-event
lifecycle log. The three coexist: an OVERT or SEP-2787 envelope can
back-link to the audit event that recorded the same action via
`record_id`. See `docs/sep2787-overt-mapping.md` for the
OVERT ↔ SEP-2787 field mapping.

## Reference implementation

- `src/vaara/audit/trail.py`, `signer.py`, `verify.py`, `export.py`.
- `src/vaara/server/schemas.py` (pydantic HTTP wire models).

The reference implementation pins this schema at version 1.0. A
conforming third-party emitter or consumer may target this document
without coupling to the Python implementation.
Loading
Loading