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
116 changes: 116 additions & 0 deletions .github/workflows/conformance.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
name: conformance

# Executable conformance:
# - vectors: reproduce vectors/ and check schema/example/index/openapi consistency (examples/conformance)
# - openapi: lint openapi.yaml with Spectral
# - black-box: drive the full wire contract against the Go and TypeScript reference servers (harness/)

on:
push:
paths:
- "examples/**"
- "harness/**"
- "schema/**"
- "vectors/**"
- "openapi.yaml"
- ".spectral.yaml"
- ".github/workflows/conformance.yml"
pull_request:
paths:
- "examples/**"
- "harness/**"
- "schema/**"
- "vectors/**"
- "openapi.yaml"
- ".spectral.yaml"
- ".github/workflows/conformance.yml"

permissions:
contents: read

jobs:
vectors:
name: vectors + schema consistency
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.10"
- name: Install
run: bun install --frozen-lockfile --cwd examples/conformance
- name: Typecheck
run: bun run --cwd examples/conformance typecheck
- name: Reproduce vectors and check artifact consistency
run: bun run --cwd examples/conformance test

openapi:
name: openapi lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.10"
- name: Spectral lint
run: bunx @stoplight/spectral-cli@6 lint openapi.yaml --ruleset .spectral.yaml --fail-severity error

black-box:
name: black-box (${{ matrix.server }} server)
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
server: [go, typescript]
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

# The harness runs under Bun (no install: only node:crypto + global fetch).
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.3.10"

- uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0
if: matrix.server == 'go'
with:
go-version: "1.22"

- name: Install TypeScript server deps
if: matrix.server == 'typescript'
run: bun install --frozen-lockfile --cwd examples/typescript/server

- name: Run the harness against the ${{ matrix.server }} reference server
env:
SERVER: ${{ matrix.server }}
run: |
set -e
PORT=19101

case "$SERVER" in
go)
(cd examples/go/server && PORT=$PORT go run .) &
SRV_PID=$!
;;
typescript)
(cd examples/typescript/server && PORT=$PORT bun run start) &
SRV_PID=$!
;;
esac

# Poll until the server accepts connections (up to 20 s).
for i in $(seq 1 100); do
curl -fsS -X POST "http://localhost:$PORT/api/auth/keypair/challenge" \
-H 'content-type: application/json' -d '{}' >/dev/null 2>&1 && break
sleep 0.2
done
curl -fsS -X POST "http://localhost:$PORT/api/auth/keypair/challenge" \
-H 'content-type: application/json' -d '{}' >/dev/null 2>&1 \
|| { echo "server did not start within 20s"; exit 1; }

bun harness/conformance.ts --server "http://localhost:$PORT"
HARNESS_EXIT=$?

kill $SRV_PID 2>/dev/null || true
wait $SRV_PID 2>/dev/null || true

exit $HARNESS_EXIT
8 changes: 8 additions & 0 deletions .spectral.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Spectral ruleset for linting openapi.yaml (the HTTP/JSON profile description).
# Extends the built-in OpenAPI rules; a couple of doc-style rules are relaxed because the
# normative descriptions live in SPEC.md, not in per-operation prose here.
extends: ["spectral:oas"]
rules:
oas3-unused-component: off # message schemas live in schema/avp.schema.json, referenced externally
info-contact: off # reporting channel is SECURITY.md / the issue tracker, not a contact block
operation-tag-defined: warn
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,31 @@ implementation's version.
- Clarified that an issuer MAY require out-of-band authentication and embed deployment-specific claims
on the `token` grant, and that a server MAY return implementation-defined resource or policy errors
such as quota limits; both non-normative (SPEC sections 3 and 6).
- Defined the HTTP/JSON error body `{ error, code?, detail? }` and a status→code table, and stated that
every non-2xx is terminal while a stale-version `push` is a `200` with `conflict: true` (SPEC section
6); added `$defs/Error` to the JSON Schema.
- Reworded the conformance section (SPEC section 11) to map each requirement to the vector that actually
exists: the Ed25519 primitive anchors the challenge signature and the payload-AEAD epoch-tamper case
anchors rotation correctness; dedicated challenge/token and rotation vectors are noted as welcome
additions rather than implied to already exist.

### Repository

- Added a black-box conformance harness in `harness/`: point it at any running server and it drives the
full wire contract, asserting the normative MUSTs (auth failure modes, optimistic-concurrency
conflict, membership authorization, key rotation, and zero-knowledge: the plaintext never surfaces in
what the server stores). Reuses the vector-tested reference crypto.
- Added a `conformance` CI workflow: reproduces the vectors, lints `openapi.yaml` with Spectral, checks
that the schema / example bodies / vector index / OpenAPI `$refs` stay consistent, and runs the
black-box harness against the Go and TypeScript reference servers. The vector runner now also runs in
CI for the first time.
- Added `openapi.yaml`, an OpenAPI 3.1 description of the HTTP/JSON profile (paths, status codes, error
responses) that references the JSON Schema shapes, so the route surface is machine-readable and
stub-generatable.
- Added `vectors/index.json`, a machine-readable index of the conformance vectors (file, kind, spec
section, RFC anchors) so a harness can enumerate them without hardcoding filenames.
- Added a root `Taskfile.yml` (go-task): `task test` runs the conformance suite and every language's
example tests in one command, mirroring the CI jobs.
- Added a Go reference client and server, and upgraded the TypeScript, Python, Java, and Rust reference
clients to implement the real envelope and key-wrap crypto (previously opaque placeholders). Every
example is verified byte-for-byte against the conformance vectors.
Expand Down
40 changes: 31 additions & 9 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,31 @@ repo with Jekyll. The README is the home page.
- `proto/avp.proto` is the **canonical** message definition. Field numbers and
names are stable; add fields, never renumber or rename them.
- `schema/avp.schema.json` mirrors the proto for the HTTP/JSON profile: the same
field names in `camelCase`. Keep it in sync with the proto and SPEC.
field names in `camelCase`. Keep it in sync with the proto and SPEC. It also
holds the HTTP error body (`$defs/Error` = `{ error, code?, detail? }`).
- `openapi.yaml` is the OpenAPI 3.1 description of the HTTP/JSON profile (paths,
methods, status codes, error responses). It does not redefine message shapes:
it `$ref`s them from `schema/avp.schema.json`, which stays the single source of
truth. Keep its routes and status set aligned with SPEC §6.
- `vectors/` are conformance test vectors, RFC-anchored and three-way verified
(published RFCs + a reference implementation + the Node runner all agree
byte-for-byte). **Never edit a vector value without re-verifying all three.**
See `vectors/README.md`.
- `examples/` are illustrative reference clients and servers (TypeScript, Rust,
Python, Java) plus a Node conformance runner. They are not production code.
`vectors/index.json` is a machine-readable index of the files (kind, spec
section, RFC anchors); keep it in sync when adding or removing a vector. See
`vectors/README.md`.
- `examples/` are illustrative reference clients and servers (Go, TypeScript,
Rust, Python, Java) plus a Node conformance runner that also checks
schema/example/index/openapi consistency. They are not production code.
- `harness/` is the black-box conformance suite: point it at any running server
(`bun harness/conformance.ts --server URL`) and it drives the full wire
contract, asserting the SPEC §3/§6/§10 MUSTs (auth failure modes,
optimistic-concurrency conflict, membership authorization, key rotation,
zero-knowledge). It reuses the vector-tested example crypto; it is the
executable check for a *server*, the vectors are the check for the *crypto*.

When you change one of `SPEC.md` / `proto/avp.proto` / `schema/avp.schema.json`,
check whether the other two need the matching change. They describe one protocol.
When you change one of `SPEC.md` / `proto/avp.proto` / `schema/avp.schema.json` /
`openapi.yaml`, check whether the others need the matching change. They describe
one protocol.

## Hard rules

Expand Down Expand Up @@ -61,9 +76,16 @@ AI). The committer is the responsible author.

## Build and test

- Conformance runner (checks the vectors): `cd examples/conformance && bun install && bun run test`.
- Per-language example CIs live in `.github/workflows/examples-{node,rust,java,python}.yml`;
each builds and tests its example client/server.
- Conformance runner (vectors + schema/example/index/openapi consistency):
`cd examples/conformance && bun install && bun run test`.
- Black-box a server: start one, then `bun harness/conformance.ts --server http://localhost:PORT`.
- One-command local run: `Taskfile.yml` (go-task). `task test` runs the conformance
runner and every language's example tests; `task harness` / `task openapi-lint` run
the server harness and the Spectral lint. Each target mirrors a CI job.
- CI workflows in `.github/workflows/`: per-language `examples-{go,node,rust,java,python}.yml`,
the cross-language `examples-interop.yml`, and `conformance.yml` (vector runner +
Spectral lint of `openapi.yaml` + schema-consistency + the black-box harness against
the Go and TypeScript reference servers). `no-leak.yml` is the vendor-neutrality gate.
- The Pages site builds on push (no local step required). If you want to preview
it locally and have Ruby, `bundle exec jekyll serve` renders `_layouts/default.html`
with `assets/css/style.css`.
Expand Down
16 changes: 15 additions & 1 deletion IMPLEMENTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,9 @@ data key to that member entry.
### Routes (HTTP/JSON profile)

All bodies are JSON with proto field names in `camelCase`. Routes other than the two auth routes
require `Authorization: Bearer <token>`.
require `Authorization: Bearer <token>`. The machine-readable route description, with every status code
and the error body, is [`openapi.yaml`](openapi.yaml) (OpenAPI 3.1); generate a client or server stub
from it if your stack supports it.

| Method + path | Request -> Response |
|---|---|
Expand All @@ -177,6 +179,12 @@ Note: `memberId` in the path is the base64 Ed25519 public key. Because it contai
| `404` | Repo or member not found |
| `409` | Duplicate `repoId` on create |

Every non-2xx response body is an error object: `{ "error": "<message>", "code": "<machine code>",
"detail": "<optional>" }`. `error` is human-readable (do not parse it); `code` is an optional stable
token (`unauthorized`, `forbidden`, `not_found`, `duplicate_repo`, `quota_exceeded`, `policy_denied`,
`bad_request`) you may switch on. Treat all of them as **terminal**; see the concurrency note below for
the one outcome that is *not* an error.

Optimistic concurrency: `push` returns `{ "accepted": false, "conflict": true }` with the
current version when `expectedPayloadVersion` does not match. A client must pull, re-apply, and
retry; this is not a `4xx` status code.
Expand Down Expand Up @@ -210,6 +218,12 @@ operation the server performs is verifying the Ed25519 challenge signature in th
[`.github/workflows/examples-interop.yml`](.github/workflows/examples-interop.yml) workflow
does this for all five reference examples and is a useful model.

5. **Black-box your server.** Point the conformance harness in [`harness/`](harness/) at your running
server (`bun harness/conformance.ts --server http://localhost:PORT`). It drives the whole flow and
asserts the normative MUSTs: the auth failure modes, optimistic-concurrency `conflict`, membership
authorization, key rotation, and zero-knowledge (the plaintext never surfaces in what the server
stores). CI runs it against the Go and TypeScript reference servers.

## 7. Reference implementations

All five examples implement the real envelope and key-wrap crypto, each verified byte-for-byte
Expand Down
28 changes: 24 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ stack:
- **gRPC profile.** [`proto/avp.proto`](proto/avp.proto) is canonical. Field numbers and names are
stable.
- **HTTP/JSON profile.** One HTTP path per operation, JSON bodies whose field names are the proto field
names in `camelCase`, and a Bearer token in `Authorization`. See [`schema/avp.schema.json`](schema/avp.schema.json).
names in `camelCase`, and a Bearer token in `Authorization`. Message shapes are
[`schema/avp.schema.json`](schema/avp.schema.json); the full route surface (paths, status codes, error
bodies) is [`openapi.yaml`](openapi.yaml).

A browser or Node client can implement AVP with no gRPC toolchain at all.

Expand All @@ -51,9 +53,11 @@ A browser or Node client can implement AVP with no gRPC toolchain at all.
|------|----------|
| [`SPEC.md`](SPEC.md) | The normative protocol specification. |
| [`proto/avp.proto`](proto/avp.proto) | Canonical Protocol Buffers schema (gRPC profile). |
| [`schema/avp.schema.json`](schema/avp.schema.json) | JSON Schema (HTTP/JSON profile). |
| [`vectors/`](vectors/) | Conformance test vectors. |
| [`examples/`](examples/) | A worked end-to-end flow, representative message bodies, and a runnable reference server. |
| [`schema/avp.schema.json`](schema/avp.schema.json) | JSON Schema message shapes (HTTP/JSON profile). |
| [`openapi.yaml`](openapi.yaml) | OpenAPI 3.1 route description for the HTTP/JSON profile. |
| [`vectors/`](vectors/) | Conformance test vectors (indexed by [`vectors/index.json`](vectors/index.json)). |
| [`examples/`](examples/) | A worked end-to-end flow, representative message bodies, and runnable reference implementations. |
| [`Taskfile.yml`](Taskfile.yml) | One-command local runner: `task test` runs the conformance suite and every example. |
| [`CONTRIBUTING.md`](CONTRIBUTING.md) | How to help. |
| [`SECURITY.md`](SECURITY.md) | How to report a vulnerability. |
| [`llms.txt`](llms.txt) | Structured entry point for LLMs and AI agents. |
Expand Down Expand Up @@ -89,6 +93,22 @@ It reproduces the deterministic and RFC-anchored vectors exactly and round-trips
(decrypting and unwrapping what a peer produced). Reproduce the same in your language and you are
conformant.

### Check your server

The vectors prove your crypto; the black-box harness in [`harness/`](harness/) proves your *server*.
Point it at any running AVP server and it drives the full wire contract, asserting the normative MUSTs:
the auth failure modes, optimistic-concurrency `conflict`, membership authorization, key rotation, and
zero-knowledge:

```sh
bun harness/conformance.ts --server http://localhost:8787
```

It prints `PASS`/`FAIL` per check and exits non-zero on any failure. CI
([`.github/workflows/conformance.yml`](.github/workflows/conformance.yml)) runs it against the Go and
TypeScript reference servers, lints [`openapi.yaml`](openapi.yaml), and checks that the schema, example
bodies, and vector index stay consistent.

## For AI agents

If you are an AI coding agent asked to implement, review, or extend AVP, start from
Expand Down
Loading
Loading