Skip to content

Conformance suite

Every Ratify SDK ships with a test that loads the canonical fixtures from testvectors/v1/ and runs them through every protocol operation. The current conformance corpus is:

  • 59 wire-format / verifier fixtures — each is one JSON file. Cover delegation signing, challenge signing, full bundle verification (positive and negative), revocation, scope intersection, and all seven built-in constraint types.
  • 10 cross-SDK byte-equivalence vectors (bundled in cross_sdk_vectors.json, added in alpha.7) — pin canonical bytes for VerificationReceipt, TransactionReceipt, PolicyVerdict, SessionToken, and the canonical VerifierContext hash so a verifier in one language can byte-for-byte verify a primitive signed in another.

If all 59 wire fixtures + the 10 cross-SDK vectors pass in your SDK, your SDK is byte-for-byte interoperable with every other Ratify SDK on the planet. That is the contract.

testvectors/v1/
├── happy_path_depth_{1,2,3}.json (positive verify, chain depth 1–3)
├── reject_*.json (negative verify — every rejection branch)
├── constraint_geo_*.json (geo_circle / polygon / bbox, inside / outside / antimeridian)
├── constraint_time_window_*.json (non-wrap / wrapping windows)
├── constraint_max_{amount,rate,speed_mps}_*.json
├── constraint_unknown_denied.json (fail-closed on unknown constraint type)
├── key_rotation_valid.json (KeyRotationStatement)
├── session_token_*.json (v1.1 fast path)
├── stream_bundle_*.json (v1.1 stream binding)
├── verification_receipt_*.json (alpha.7)
├── transaction_receipt_*.json (alpha.7)
└── cross_sdk_vectors.json (10 alpha.7 byte-equivalence vectors)

Each fixture is a deterministic JSON file with:

  1. Inputs. Keys (seeded so they’re reproducible), cert/bundle/list shape, verifier context.
  2. Expected canonical bytes. What delegationSignBytes or challengeSignBytes should produce (hex-encoded). Any SDK that produces different bytes here has a non-byte-identical canonicalizer and is rejected.
  3. Expected verification outcome. For verify fixtures: the exact VerifyResult struct the verifier should return, including identity_status, granted_scope, and error_reason.

The fixtures are generated by cmd/ratify-testvectors in the protocol repo. The Go reference is authoritative; every other SDK must produce identical output.

Terminal window
# Go
cd ratify-protocol
go test ./...
# TypeScript
cd sdks/typescript
npm run test:conformance
# Python
cd sdks/python
pytest
# Rust
cd sdks/rust
cargo test

Expected output: all fixtures pass. Any failure means the SDK has drifted — file a bug.

Half the value is in the rejection paths. The reject_* and constraint_*_denied fixtures exercise every branch of the verifier:

  • Tampered cert bodies (sig stops verifying) → identity_status: invalid, error_reason: bad_*_sig: …
  • Expired / not-yet-valid certs → expired
  • Out-of-scope request → scope_denied
  • Broken chain (subject ≠ next issuer) → invalid (chain_broken)
  • Chain depth exceeding MaxDelegationChainDepthinvalid (chain_too_deep)
  • Sub-delegation without identity:delegatedelegation_not_authorized
  • Stale or future-dated challenge → invalid (stale_challenge / negative-age)
  • SessionContext mismatch (cross-verifier forwarding) → invalid
  • Constraint violations → constraint_denied, constraint_unverifiable, constraint_unknown
  • Sensitive scope introduced by wildcard → scope_denied

Each rejection is checked for the exact identity_status value AND the leading error_reason prefix. The strictness is what makes integrators able to write deterministic error-handling code: switch on identity_status for the enum, then match the error_reason: prefix when you need the specific sub-cause.

Dedicated reject_ed25519_only_corrupted.json and reject_mldsa65_only_corrupted.json fixtures pin the defense-in-depth posture:

Ed25519 valid + ML-DSA-65 valid → ✓ authorized_agent
Ed25519 valid + ML-DSA-65 corrupted → ✗ invalid (bad_*_sig)
Ed25519 corrupted + ML-DSA-65 valid → ✗ invalid (bad_*_sig)

Both components must verify. There is no degradation path that accepts one signature and silently ignores the other.

Terminal window
cd ratify-protocol
go run ./cmd/ratify-testvectors -out /tmp/regen
diff -rq testvectors/v1/ /tmp/regen/
# expected: identical

The CI workflow runs this exact diff. If a contributor’s change causes the regenerated fixtures to differ from the committed ones, the change is non-deterministic (map iteration, RNG without a seed, time.Now() without a fixed clock) — and the PR fails.

This is how the project guarantees that fixture bytes are stable across rebuilds, across runners, across machines. Anyone can reproduce the bytes from the deterministic seeds.

To add a new language SDK (Swift, Java, C, etc.):

  1. Open a new-SDK coordination issue.
  2. Implement the SDK following SPEC.md.
  3. Implement a conformance test that loads testvectors/v1/*.json and runs every fixture.
  4. Every fixture must pass before the SDK is merged. No exceptions, no negotiated drift.

See docs/SDKS.md in the protocol repo for the full conformance contract.