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
38 changes: 38 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,43 @@
# Changelog

## 3.0.0

This release enforces the approval and execution boundaries across complete success,
edited-action, refusal, expiry, and recovery lifecycles.

### Changed

- Revalidate original and edited payloads against trusted local action schemas,
including action type, released version, schema ID, and exact file digest.
- Require consumed request state before provider invocation, with causal decision,
terminal, consumption, and dispatch times and exclusive expiry boundaries.
- Represent rejection, expiry, cancellation, and failure without inventing approvals
or authority checks. Refused dispatches cannot invoke a provider or allow retries.
- Link dispatch history with `previous_dispatch_id`, stable action and idempotency
identities, fixed attempt ceilings, and contiguous retry attempt numbers.
- Require `policy_evaluation_id` on every decision and bind it to the request policy.
Authority evidence requires an explicit `valid_until` boundary.
- Verify typed audit witnesses against actual records, actors, outcomes, times, and
identities; reject foreign references and captures that precede included evidence.
- Count the complete dispatch quorum only from approvals for one effective action.
- Enforce JSON Patch target existence, exact array bounds, pointer escapes, and JSON
value equality without conflating booleans and numbers.
- Reject duplicate JSON keys at every depth and non-finite numbers at ingress.

### Added

- Complete edited-action, rejected, expired, and reconciled-retry examples plus
portable refusal vectors and focused lifecycle regressions.
- n8n reference endpoint contracts for `/v3/` integrations.

### Migration

Continue validating stored v2 records with their original schemas. For new v3 records,
add the request policy evaluation ID to every approval and a finite authority validity
window. Link retry and reconciliation records to the preceding dispatch record; retain
the initial consumed dispatch identity and stable idempotency key. Recompute hashes
and signatures after adopting the v3 schema IDs. Do not relabel historical records.

## 2.1.0

This release adds an optional WebAuthn proof profile for independently verifiable
Expand Down
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Nine boundaries make the gate real:
If the agent still has the side-effect credential, the approval path remains optional
from the agent's point of view.

## Contracts in v2.1.0
## Contracts in v3.0.0

| Contract | Purpose |
|---|---|
Expand All @@ -71,7 +71,7 @@ from the agent's point of view.
| [`approval-envelope.schema.json`](schemas/approval-envelope.schema.json) | Portable bundle plus cross-contract profile for a completed lifecycle |
| [`actions/email.send.schema.json`](schemas/actions/email.send.schema.json) | Strict reference for composing action-specific payload contracts |

Schema IDs are pinned to the `v2.1.0` release. Pin a released ID in production; do not
Schema IDs are pinned to the `v3.0.0` release. Pin a released ID in production; do not
resolve schemas from the mutable default branch.

## Quick start
Expand Down Expand Up @@ -100,6 +100,14 @@ The complete synthetic lifecycle is in
checks IDs, hashes, quorum, expiry, sequence linkage, and attempt ceilings that JSON
Schema alone cannot compare across documents.

Version 3 also supplies complete [edited](examples/edited-envelope.json),
[rejected](examples/rejected-envelope.json), [expired](examples/expired-envelope.json),
and [recovered](examples/recovered-envelope.json) lifecycles. Refusal is a normal
terminal result; it requires neither an approving decision nor a provider invocation.
Recovery retains the unknown attempt, a linked reconciliation record, and a bounded
retry with fresh authority. The portable vectors in
[`tests/hardening.json`](tests/hardening.json) cover these boundaries.

[`tests/canonicalization.json`](tests/canonicalization.json) is a language-neutral
RFC 8785 corpus with exact canonical UTF-8 bytes and SHA-256 results. Use it to prove
that implementations in different languages bind the same document to the same hash.
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.1.0
3.0.0
7 changes: 7 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,13 @@ release examples and negative corpus.
Production systems should perform the same checks in their implementation language at
each boundary, not invoke the repository's development script as a network service.

Version 3 requires strict JSON parsing, trusted local payload schema bytes, an exact
policy evaluation on every decision, and a consumed request before provider invocation.
Quorum is evaluated for the cited effective action, not across unrelated edits.
Unknown outcomes and retry history remain append-only; refusal is a complete terminal
path without a fabricated approval or invocation. See the version 3 boundary rules in
[`contracts.md`](contracts.md).

## Channel choice

| Channel | Trust | Latency | Notes |
Expand Down
80 changes: 77 additions & 3 deletions docs/contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

## Versioning

The release version and every schema's `schema_version` are `2.1.0`. Schema `$id`
values resolve through the immutable `v2.1.0` tag. A consumer may cache schemas by
The release version and every schema's `schema_version` are `3.0.0`. Schema `$id`
values resolve through the immutable `v3.0.0` tag. A consumer may cache schemas by
`$id`; it must not replace a cached release schema with content from `main`.

- Patch releases may clarify descriptions and add compatible examples or tests.
Expand Down Expand Up @@ -71,6 +71,73 @@ The envelope validator enforces the rules JSON Schema cannot express:

Treat a semantic validation error as a hard refusal with a redacted audit reason.

## Version 3 boundaries

Parse JSON before schema validation with duplicate-property detection at every depth.
Reject `NaN`, infinities, and overflowing numeric literals. Do not silently choose the
last duplicate property or normalize an ambiguous document before hashing it.

Use a deployment-owned local catalog of released payload schema bytes. The reference
validator ships `email.send`; `validate_envelope` and `payload_errors` also accept a
trusted schema-ID-to-file mapping for other action types. Custom schemas identify
their action with `x-action-type` and use an immutable `/v<version>/` ID. A payload
schema can have its own version independent of the envelope version. Do not fetch an
agent-selected schema URL. Verify the ID, version, action type, and raw-file digest,
then validate both the original payload and every effective edited payload.

Every approval requires the request's exact `policy_evaluation_id`, including human
decisions. A resolution ID has one terminal approval identity; delivery retries update
that receipt's delivery state rather than minting another terminal receipt.

Provider invocation requires a consumed request. Its terminal decision must be
recorded before consumption, and consumption must precede invocation. The consumed
revision must include both decision and consumption transitions after the revision
observed by the approver. Decision, consumption, and invocation must occur strictly
before request expiry; authority is usable only before its required `valid_until`.
The initial dispatch must include the consumed decision and a complete quorum whose
every cited approval binds the same effective action. Votes for different edits cannot
be combined into a quorum.

### Terminal refusals

An envelope covers an admitted request. Rejected, expired, cancelled, and failed
requests can have empty approval, resolution, authority, and dispatch arrays where
those steps never occurred. A rejected request still cites its rejecting decision;
every decision retains its durable resolution. Expiry cannot precede the deadline.

A `not_dispatched` record may have no approval IDs or authority snapshot and must
have `retry_allowed: false`. It cannot carry provider evidence. If no dispatch step
occurred, an expiry or cancellation event, or the recorded rejection, describes the
terminal path without a synthetic invocation. Pending requests are not complete
envelopes. A pre-admission rejection remains a validation record and audit stream,
without an admitted-request envelope.

### Dispatch history and recovery

Order dispatch records by recorded history. Each has a unique `dispatch_id`; the
request retains the first ID as its consumed execution identity. Later records name
the immediately preceding record with `previous_dispatch_id`. They preserve the
effective action hash, approval set, stable provider idempotency key, and attempt
ceiling. A new attempt increments `attempt` by exactly one and cannot begin before
the preceding result was recorded.

Only an authoritative `failed` result permitting retry, or a linked
`reconciled_not_applied` result permitting retry, allows another invocation. Unknown
outcomes require read-only reconciliation. Its record keeps the unknown attempt
number and lookup identity, and cites evidence checked after the unknown result.
Success, unresolved uncertainty, and an exhausted attempt ceiling never grant retry.
Every new invocation requires fresh, active authority; reconciliation does not grant
execution authority.

### Audit witnesses

A hash-valid stream must also describe the actual lifecycle. Every lifecycle record
needs a witness on its matching event type. All audit references resolve inside the
envelope, and supplied decision, action, attempt, policy, and result details agree
with the record. Decision actor and time agree with the approval; witness times agree
with the facts they record. Capturing the envelope cannot precede its contained
evidence. Rehashing a false event never makes it a valid witness.

## WebAuthn approval proof

`webauthn-es256` is an optional signature profile for authenticated web approvals.
Expand Down Expand Up @@ -144,11 +211,18 @@ approvals that satisfy quorum.

## Modification profile

Version 2 uses RFC 6902 operations restricted to `/payload`. Allowed operations are `add`,
Version 3 uses RFC 6902 operations restricted to `/payload`. Allowed operations are `add`,
`remove`, `replace`, and `test`. `move` and `copy` are deliberately absent: review
interfaces should show explicit before/after values instead of making an approver
mentally follow pointer movement.

Array indices are non-negative decimal tokens without leading zeroes. `add` may use
the array length or `-` to append; other operations require an existing index.
`replace`, `remove`, and `test` require an existing member. Reject malformed pointer
escapes and missing parent containers. `test` compares JSON values recursively:
numbers compare numerically, while booleans remain distinct from numbers. Apply the
whole patch to a copy; any failure refuses the approval without altering the proposal.

After applying modifications:

1. validate the complete proposal and action-specific payload again;
Expand Down
27 changes: 17 additions & 10 deletions docs/n8n-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ deployment-owned endpoints below.

### Validator

`POST $APPROVAL_VALIDATOR_URL/v2/validate`
`POST $APPROVAL_VALIDATOR_URL/v3/validate`

Input: `{ "schema": "proposed-action.schema.json", "instance": { ... } }`

Expand All @@ -20,30 +20,37 @@ the pinned release IDs. The returned validation object is a complete

### Gate

- `POST /v2/validations` durably records the validator's result for the exact action.
- `POST /v2/requests` persists policy and request state before notification.
- `POST /v2/review-snapshots` renders and persists the exact decision surface.
- `POST /v2/requests/{id}/resolve` verifies callback signature, nonce, tenant,
- `POST /v3/validations` durably records the validator's result for the exact action.
- `POST /v3/requests` persists policy and request state before notification.
- `POST /v3/review-snapshots` renders and persists the exact decision surface.
- `POST /v3/requests/{id}/resolve` verifies callback signature, nonce, tenant,
action hash, review content hash, expiry, status, and expected revision in one
transaction.
- `POST /v2/resolutions` stores and delivers the terminal decision receipt. Delivery
- `POST /v3/resolutions` stores and delivers the terminal decision receipt. Delivery
retries reuse the same receipt and target hash.
- `POST /v2/requests/{id}/consume` changes one approved request to consumed and returns
- `POST /v3/requests/{id}/consume` changes one approved request to consumed and returns
one short-lived dispatch grant. Duplicate consumption returns the original terminal
identity or a conflict, never a new grant.
- `POST /v2/authority-snapshots` rechecks the approver, policy, workspace, and
- `POST /v3/authority-snapshots` rechecks the approver, policy, workspace, and
dispatcher immediately before provider invocation.

The raw callback nonce appears only in the signed review link. The durable request
stores its hash.

Version 3 endpoints emit `policy_evaluation_id` on every decision and `valid_until`
on authority snapshots. The dispatcher checks the consumed decision set and counts
quorum only over the exact effective action. Refusal endpoints may return no approving
records or authority snapshot; they never return a usable dispatch grant. Store each
retry or reconciliation with its `previous_dispatch_id`, retaining the initial consumed
dispatch identity. The workflow does not perform automatic provider retries.

### Audit sink

`POST $APPROVAL_AUDIT_URL/v2/events` accepts a redacted event intent. The sink assigns
`POST $APPROVAL_AUDIT_URL/v3/events` accepts a redacted event intent. The sink assigns
sequence, previous hash, event hash, ingestion time, classification, and retention
under an append-only transaction. Do not let n8n clients choose sequence or hashes.

`POST $APPROVAL_AUDIT_URL/v2/snapshots` returns a complete export manifest after a
`POST $APPROVAL_AUDIT_URL/v3/snapshots` returns a complete export manifest after a
terminal event. Verify its event count, sequence bounds, event-set digest, and terminal
chain hash before archiving the run.

Expand Down
Loading
Loading