# ADR-026: Protocol Adapters (Adapter-First Strategy)

## Status

Accepted (February 2026; ACP + A2A + UCP adapter rollout and E0-E4 stabilization merged on `main`)

## Context

Assay's open-core value is protocol-agnostic governance with deterministic, verifiable evidence. External agent protocols such as ACP, A2A, and related ecosystem formats emit protocol-specific payloads and lifecycle models, not canonical Assay `EvidenceEvent` records.

Without a controlled adapter layer, protocol adoption creates two problems:
- Protocol-specific logic leaks into the evidence core.
- Fast-moving protocol revisions create drift between raw protocol payloads and Assay's canonical evidence model.

A dedicated adapter layer allows Assay to ingest protocol payloads, preserve audit-grade provenance, and keep the evidence core stable.

## Decision

We adopt an adapter-first strategy:
- Introduce a shared adapter API in a central crate such as `assay-adapter-api`.
- Implement protocol-specific adapters as separate crates such as `assay-adapter-acp` and later `assay-adapter-a2a`.
- Keep adapter crates in open core because they are interoperability infrastructure, not enterprise workflow features.
- Preserve raw protocol payloads via host-provided attachment writing and stable references instead of direct filesystem writes from adapters.

### v1 execution model

Adapters are native Rust crates linked into the workspace.

### v2 execution model (deferred)

The same API may later be hosted in sandboxed Wasm modules, but Wasm/plugin transport is explicitly deferred until the native contract is proven stable.

## Adapter API Contract (v1)

Each adapter must implement the following contract surface:
- `protocol() -> ProtocolDescriptor`
- `capabilities() -> AdapterCapabilities`
- `convert(input, options) -> Result<Vec<EvidenceEvent>, AdapterError>`
- `lossiness() -> LossinessReport` on each emitted output unit or batch result

### Required protocol metadata

`protocol()` must expose:
- `name`
- `spec_version`
- `schema_id`
- `spec_url`

### Required lossiness metadata

Each conversion result must make loss explicit via:
- `lossiness_level`: `none | low | high`
- `unmapped_fields_count`
- `raw_payload_ref`: digest-backed reference to preserved source payload

## Strictness Modes

Adapters must support at least two conversion modes via `ConvertOptions`:
- `strict`: fail on malformed or unmappable critical protocol data
- `lenient`: emit evidence plus explicit lossiness metadata and preserved raw payload reference

Normative behavior:
- `strict` contract failures exit with measurement/contract failure semantics (`exit 2` in CLI/script harnesses).
- `lenient` mode must not silently drop critical data; it must surface lossiness and retain a raw payload reference.

## Raw Payload Preservation

Raw protocol payloads are preserved hash-first for auditability.

Normative rules:
- Adapters must not write directly to arbitrary filesystem paths.
- Raw payload persistence is performed through a host-provided attachment writer from the evidence/core layer.
- Emitted evidence carries only `raw_payload_ref { sha256, size, media_type }` plus any allowed redaction metadata.
- Canonicalization for payload hashing must be deterministic and stable for the adapter contract version.

## Versioning and Conformance

Each active adapter must ship with a strict conformance suite.

### Definition of Done

Conformance suites must include:
- at least `N` golden happy-path fixtures for the supported protocol version range
- at least one negative fixture per supported protocol version
- explicit expected outcome per fixture
- a determinism check proving identical input fixture -> identical output digest

Negative fixtures must cover at least one of:
- malformed packet
- missing required fields
- oversize payload
- invalid enum or discriminant

Expected outcomes must be explicit:
- `strict` mode: measurement/contract fail (`exit 2`)
- `lenient` mode: emit lossiness plus `raw_payload_ref`

### Upgrade policy

Each adapter crate must declare an explicit supported version range, for example `>=2.11 <3.0`, and document deprecation policy when upstream protocol revisions break mappings.

## Security Posture and Invariants

Adapters process untrusted protocol payloads. The following invariants are mandatory:
- payload size caps before deep parsing
- schema/shape validation before semantic mapping
- no implicit network access from adapter conversion paths
- no direct adapter-managed filesystem writes
- `unsafe` remains disallowed under workspace policy unless a future ADR explicitly carves out an exception

## Non-Goals

This ADR does not introduce:
- protocol-specific business policy enforcement in the evidence core
- dynamic plugin loading or Wasm adapter execution
- remote adapter registries
- workflow changes or CI rollout lanes
- enterprise-only middleware surfaces

## MVP Scope

The first implementation target is `assay-adapter-acp`.

Rationale:
- ACP is a high-value governance wedge for commerce/intent evidence.
- ACP creates immediate pressure for deterministic audit trails around checkout, intent, and authorization flows.
- A2A remains the strategic follow-up once the adapter API and conformance harness are proven.

## Execution Plan

### PR-A (freeze)
- freeze adapter API contract
- freeze conformance harness contract
- freeze ACP as initial implementation target
- no runtime mapping logic

### PR-B (implement)
- add `assay-adapter-acp`
- add happy-path and negative fixtures
- add strict/lenient determinism tests

### PR-C (closure)
- add checklist and review pack
- add index/runbook references for adapter entrypoints
- close the rollout loop with reviewer gates

## Acceptance Criteria

- ADR defines adapter API v1 contract and strictness semantics
- ADR defines hash-first raw payload preservation using host-provided attachment writing
- ADR includes explicit negative-fixture conformance requirements
- ADR freezes ACP as MVP and A2A as follow-up
- No workflow or runtime behavior changes are introduced in this slice

## Implementation Status

Current status on `main`:
- `assay-adapter-api` exists as the shared adapter contract surface
- `assay-adapter-acp` is implemented with strict/lenient conversion, fixtures, and conformance tests
- `assay-adapter-a2a` is implemented with strict/lenient conversion, fixtures, and conformance tests
- `assay-adapter-ucp` is implemented with strict/lenient conversion, fixtures, and conformance tests
- Stabilization through E4 is merged:
  - E0: adapter identity metadata
  - E1: ACP lossiness preservation
  - E2: host-side `AttachmentWriter` policy
  - E3: canonical event digests with bytes-exact raw payload hashes
  - E4: parser caps and property-based hardening

## Consequences

### Positive
- keeps the evidence core protocol-agnostic
- makes protocol drift explicit through conformance fixtures
- preserves audit-grade provenance for unmapped data
- opens protocol interoperability as open-core infrastructure

### Negative
- adds maintenance burden for evolving upstream protocols
- lossiness handling requires strong reviewer discipline
- adapter version ranges and fixtures become part of the compatibility surface

### Mitigations
- strict conformance suites
- explicit lossiness reporting
- host-controlled attachment writing
- freeze-first A/B/C rollout discipline
