# FablePool Conformance Suite (`fpcf`) Reference tooling for the FablePool wire format and signed operation log (milestone #2). It provides: - a **canonical encoder/decoder** for the wire format, - **signing, identifier, schema, and log verification** routines, - a deterministic **test-vector generator** and a **conformance runner**, - a CLI tying these together, - a pytest suite that doubles as the executable API contract. The normative protocol text lives in `spec/02-wire-format/`. This package is *a* reference implementation; a second implementation should be built from the spec plus `vectors/README.md` alone, then checked with the vectors and runner here. ## Install Requires **Python ≥ 3.10**. ```sh cd conformance python3 -m pip install -e ".[dev]" # or: make install ``` ### Dependencies and build hygiene Runtime dependencies (declared in `pyproject.toml` with flexible constraints): | Package | Targeted version | Used for | |----------------|------------------|-------------------------------------------| | `jsonschema` | `>=4.21` | JSON Schema (2020-12) validation | | `cryptography` | `>=42` | Ed25519 key generation, signing, verifying | Dev-only: `pytest >= 8`. No lockfile is committed — a hand-written lockfile would be fabricated. To pin an environment, generate one on first build, e.g.: ```sh python3 -m pip install pip-tools pip-compile pyproject.toml --extra dev -o requirements.lock ``` ## Quickstart ```sh # Generate the deterministic vector suite python -m fpcf.cli generate --out vectors/generated \ --seed 5eed5eed5eed5eed5eed5eed5eed5eed5eed5eed5eed5eed5eed5eed5eed5eed # Run the conformance runner against it (exit 0 iff every vector passes) python -m fpcf.cli run --vectors vectors/generated # Verify a single operation file (exit 0 iff fully valid) python -m fpcf.cli verify vectors/generated/ops/valid/op-evidence-ingest-minimal.json ``` Or simply: ```sh make check # pytest + generate + run ``` Generation is byte-for-byte deterministic for a given seed (keys are derived from the seed; timestamps are fixed), so regenerated suites can be diffed and committed reproducibly. ## Tests ```sh make test # == python -m pytest -q tests ``` The suite covers canonicalization rules, signing/op-id self-consistency, tamper detection (body, signature, op_id, chain links, dropped entries), generator determinism, manifest integrity and coverage, runner pass/fail accounting, and the CLI end-to-end including failure exit codes. ## Module API contract The pytest suite under `tests/` is the executable form of this table; the two are kept in lock-step. | Symbol | Contract | |--------|----------| | `fpcf.errors.ConformanceError` | Base class for all suite errors; carries a machine-readable `.code` (an `ERR_*` string from spec §05). Subclasses: `CanonicalizationError`, `SchemaError`, `SignatureError`, `IdentifierError`, `ChainError`, `VectorError`. | | `fpcf.canonical.canonical_encode(value) -> bytes` | Canonical wire encoding. Rejects floats (incl. NaN/Inf) and non-string keys with `ConformanceError`. Sorted keys, no whitespace, JCS-style escaping, literal UTF-8 for non-ASCII. | | `fpcf.canonical.canonical_decode(data: bytes) -> value` | Strict decode: rejects duplicate keys and invalid UTF-8. `canonical_encode(canonical_decode(b)) == b` for canonical input. | | `fpcf.ids.compute_op_id(envelope: dict) -> str` | Recomputes the operation identifier over the canonical bytes per spec §01. | | `fpcf.signing.generate_keypair(seed: bytes \| None = None) -> Keypair` | Ed25519 keypair; deterministic for a 32-byte seed. `Keypair.key_id` is the spec key identifier. | | `fpcf.signing.sign_envelope(env: dict, keypair) -> dict` | Returns a completed envelope: binds the author/key fields to `keypair`, computes `sig` and `op_id`. Deterministic (Ed25519). | | `fpcf.schemas.validate_operation(op: dict) -> None` | JSON Schema validation (envelope + per-type body); raises on failure. | | `fpcf.verify.verify_operation(op: dict) -> None` | Full single-operation check: schema + op_id recomputation + signature; raises `ConformanceError` subclass on any failure. | | `fpcf.log.verify_log(ops: list[dict]) -> None` | Verifies every operation plus append-only rules: per-author `prev` hash chains, monotonic sequence numbers, derivation references. | | `fpcf.log.merge_ops(a: list[dict], b: list[dict]) -> list[dict]` | Deterministic, commutative merge; deduplicates by `op_id`; output order is the spec §03 total order. | | `fpcf.vectors.generate_vectors(out_dir, seed: str) -> dict` | Writes an `fpcf-vectors/1` suite (see `vectors/README.md`); returns the manifest dict, identical to the `manifest.json` written. | | `fpcf.runner.run_vectors(dir) -> report` | Runs a suite; report exposes `total`, `passed`, `failed`, and `results` (one entry per vector, each carrying its manifest `name`). Raises `ConformanceError` if the suite itself is structurally broken (e.g. missing manifest). | | `fpcf.cli.main(argv: list[str] \| None = None) -> int` | Subcommands `generate`, `run`, `verify` as in Quickstart; returns the process exit code. `fpcf/cli.py` is runnable via `python -m fpcf.cli`. | ## For second implementers 1. Build from `spec/02-wire-format/` (start with `00-overview.md`, then the interop checklist in `07-interop-checklist.md`). 2. Read `vectors/README.md` for the vector format. 3. Generate (or vendor) a suite and run every vector through your implementation; you must accept all `expect: "valid"` vectors and reject all `expect: "invalid"` vectors, ideally with matching `ERR_*` codes. ## Layout ``` conformance/ ├── Makefile ├── README.md <- this file ├── pyproject.toml ├── fpcf/ <- reference implementation package ├── tests/ <- pytest suite (executable API contract) └── vectors/ ├── README.md <- vector format spec for second implementations └── generated/ <- deterministic suite (make vectors) ```