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
18 changes: 18 additions & 0 deletions .github/workflows/conformance.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: Conformance
on:
push:
branches: [main]
pull_request:
permissions:
contents: read
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm test
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules/
.DS_Store
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

All notable changes to the Agent Payment Event (APE) schema. Versions follow `MAJOR.MINOR`; minor versions only add optional fields. Until v1.0, a minor version may still make breaking changes, and the changelog will say so.

## Unreleased

Clarifications to v0.1, plus one tightening. The JSON Schema is unchanged.

- **Tightening:** the free-text fields checked for card numbers are now listed exactly in `docs/FIELDS.md`, and include `payee.website`, `payee.resource_url` and `labels` keys. Receivers adopting this reject events with a card number in those fields (`pan_detected`). Identifiers and `evidence[].value` are never checked.
- `rail_ref` keys are closed per rail, as the schema already says. The README and `docs/FIELDS.md` no longer say receivers ignore unknown `rail_ref` keys. `labels` and `evidence[].value` stay open.
- x402 mapping: `payment_id` is `x402:<network>:<nonce>` on every stage after the challenge, never the transaction hash. `payment.requested` uses `x402:<network>:requested:<event id>`, and the answering `payment.authorized` links back with `related.previous_event_id`.
- New `conformance/` folder: events with their expected verdicts, a signing vector and canonical-JSON vectors, checked in CI by `npm test`.

## 0.1.0

First public draft.
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,17 @@ const validate = ajv.compile(schema);
if (!validate(event)) console.error(validate.errors);
```

JSON Schema cannot express every rule. Receivers should also reject free-text fields that contain a Luhn-valid 13 to 19 digit sequence (a possible card number).
JSON Schema cannot express every rule. Receivers must also reject an event whose free-text fields contain a possible card number (a Luhn-valid run of 13 to 19 digits). [`docs/FIELDS.md`](docs/FIELDS.md#cross-field-rules) lists exactly which fields are free text.

[`conformance/`](conformance/) holds events every implementation must accept or reject, plus signing and canonical-JSON test vectors. SDKs and receivers should run them in their own tests.

## Signing

An envelope is `{ "event": { ... }, "sig": "<detached JWS>" }`. The JWS header is `{"alg":"EdDSA","kid":"<source id>","typ":"ape+jws","ape":"0.1"}` and the payload is the RFC 8785 (JCS) canonical form of `event`, detached per RFC 7515 Appendix F.

## Versioning

`ape_version` is `MAJOR.MINOR`. Minor versions only add optional fields. Receivers must ignore unknown keys in `labels`, `rail_ref` and `evidence[].value`, and reject unknown top-level fields. v0.x may change between minors until v1.0.
`ape_version` is `MAJOR.MINOR`. Minor versions only add optional fields, and receivers pick the schema by `ape_version`. Within a version, unknown top-level fields and unknown `rail_ref` keys are rejected; keys inside `labels` and `evidence[].value` are open, and receivers ignore the ones they do not use. v0.x may change between minors until v1.0.

The `sha256` values in the examples are placeholders; real events carry the digest of their evidence value.

Expand Down
21 changes: 21 additions & 0 deletions conformance/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Conformance

Test data every APE implementation (SDKs and receivers) should run in its own tests, so they all accept and reject the same events. `npm test` at the repository root checks these files against [`ape/v0.1.json`](../ape/v0.1.json) and the card-number rule in [`docs/FIELDS.md`](../docs/FIELDS.md#cross-field-rules).

| File | What it holds |
| --- | --- |
| [`events.json`](events.json) | Events with the verdict an implementation must give. `expect` is `valid` or `invalid`; for an invalid event, `rule` says why: `schema` (the JSON Schema rejects it) or `pan` (it passes the schema, but a free-text field holds a possible card number). |
| [`sign-vector.json`](sign-vector.json) | One event, its RFC 8785 canonical form, a key pair and the detached JWS. Re-signing the event with `private_jwk` must give exactly `sig`; Ed25519 is deterministic. |
| [`jcs-vectors.json`](jcs-vectors.json) | Canonical JSON for numbers (as IEEE 754 bit patterns, including RFC 8785 appendix B) and for objects with tricky keys and strings. Generated from the TypeScript SDK's `canonicalize`. |

A producer SDK that redacts before validating should run `events.json` through its validator directly, not through its emit path: redaction would remove the card numbers that the `pan` cases test for.

## Status of the SDKs

Run on 2026-10-23 against this revision.

| Implementation | `events.json` | Differences |
| --- | --- | --- |
| `halvorai_ingest` (Dart) | 40 / 40 | none |
| `@halvorai/ingest` (Node.js, zod model) | 37 / 40 | Not yet checking `payee.website`, `payee.resource_url` and label keys for card numbers |
| `halvorai-ingest` (Python) | 36 / 40 | The same three, and it accepts `null` for optional fields (`null-optional-field`) |
Loading
Loading