# ADR-0005: Independent SDK Implementations (Not Code Generation)

## Status

Accepted

## Context

The Agent Receipts protocol ships SDKs in three languages: Go, Python, and TypeScript. Each SDK independently implements the same specification — receipt creation, Ed25519 signing, RFC 8785 canonicalization, SHA-256 hashing, chain verification, SQLite storage, and action taxonomy classification.

A common alternative is to define the protocol once in a shared schema (OpenAPI, protobuf, JSON Schema, Smithy) and generate SDK code from that definition. Code generation reduces duplication and guarantees structural consistency across languages. We evaluated this approach and decided against it.

Key factors in the evaluation:

- **The protocol is cryptographic, not just structural.** The critical contract between SDKs is not the shape of types — it is byte-identical canonicalization, signing, hashing, and verification. No code generator produces correct RFC 8785 canonicalization or Ed25519 signing logic. The hard parts must be hand-written regardless.
- **Generated code is a poor fit for idiomatic SDKs.** Each language ecosystem has strong conventions: Go uses struct tags and stdlib crypto, Python uses Pydantic models and the `cryptography` library, TypeScript uses interfaces and `node:crypto`. Generated code tends toward a lowest-common-denominator style that feels foreign in every language.
- **Three implementations validate the spec.** When three independent teams (or contributors) implement the same specification and produce byte-identical output, it provides strong evidence that the spec is unambiguous and implementable. A single implementation generated three ways only validates the generator.
- **The surface area is small.** Each SDK is roughly 500-800 lines of core logic (creation, signing, hashing, chain verification) plus storage and taxonomy. This is well within the range where hand-written implementations are maintainable without duplication becoming a burden.
- **Cross-language test vectors already enforce consistency.** The `cross-sdk-tests/` directory contains shared test vectors (canonicalization inputs/outputs, hashes, signed receipts) generated by one SDK and verified by all three. This provides the consistency guarantee that code generation would offer, while preserving implementation freedom.

Related: #20 (parent issue), #42.

## Decision

Maintain Go, Python, and TypeScript SDKs as independent, hand-written implementations of the Agent Receipts specification. Enforce cross-language consistency through shared test vectors rather than shared code or code generation.

Key reasons:

- **Cryptographic correctness cannot be generated.** RFC 8785 canonicalization, Ed25519 signing, multibase encoding, and chain hash verification require hand-written, language-specific implementations. These are the most important parts of the SDK and the most likely sources of cross-language divergence.
- **Idiomatic APIs improve adoption.** Go developers expect exported types and stdlib crypto. Python developers expect Pydantic models and pip-installable packages. TypeScript developers expect ESM, interfaces, and zero runtime dependencies. Hand-written SDKs can follow each ecosystem's conventions naturally.
- **Independent implementations are a spec validation mechanism.** If three implementations produce identical output from the same inputs, the specification is precise enough to implement correctly. Bugs found in one SDK that do not appear in others reveal spec ambiguities. This feedback loop has already caught canonicalization edge cases during development.
- **Shared test vectors are a stronger contract than shared types.** A generated type definition guarantees structural compatibility but says nothing about behavioral compatibility. A test vector that asserts byte-identical canonicalization, hashing, and signature output across all three languages proves end-to-end interoperability.
- **The maintenance cost is bounded.** The spec is stable (v0.1), the surface area is small, and changes propagate through a well-defined process: update the spec, update one SDK, regenerate test vectors, update the remaining SDKs.

## Consequences

- Each SDK must independently implement all protocol logic — there is no shared runtime or generated scaffolding.
- Spec changes require updating three codebases. The cross-language test vectors serve as the acceptance criteria: a change is not complete until all three SDKs pass the updated vectors.
- Contributors working in one language do not need toolchains for the other two languages, lowering the barrier to contribution.
- The `cross-sdk-tests/` directory and its test vector generation tooling (`sdk/go/cmd/generate-vectors/`, TypeScript fixture generation) are critical infrastructure and must be maintained alongside the SDKs.
- If the protocol surface area grows significantly (e.g., dozens of new message types for algorithm agility per #32), this decision should be revisited — at some point the maintenance cost of three independent implementations may outweigh the benefits. The threshold is when structural boilerplate dominates over behavioral logic.
- New SDK languages (e.g., Rust, Java) follow the same pattern: implement the spec independently and verify against the shared test vectors.
