Releases: dcp-ai-protocol/dcp-ai
DCP-AI v2.8.1 — wire canonicalization_profile across the 4 SDKs
Summary
v2.8.0 declared the canonicalization_profile field in the bundle manifest schema and described it normatively in spec/CANONICALIZATION_PROFILE.md § 4, but no SDK actually wrote or read the field — the typed BundleManifest structures shipped without it, and verifiers ignored it. v2.8.1 closes that gap end to end in TypeScript, Python, Go, and Rust, and fills the three remaining holes in the spec documents.
SDK wiring
-
TypeScript (
@dcp-ai/sdk2.1.0 → 2.1.1) —BundleManifestinterface insdks/typescript/src/types/v2.tsgainscanonicalization_profile?: 'dcp-jcs-v1'.BundleBuilderV2.build()insdks/typescript/src/bundle/builder-v2.tsnow emitscanonicalization_profile: \"dcp-jcs-v1\"on every produced manifest.verifyV2Bundleinsdks/typescript/src/core/verify-v2.tsaccepts an absent field (assumesdcp-jcs-v1), accepts\"dcp-jcs-v1\"explicitly, and rejects any other value with\"Unknown canonicalization_profile: <value>\". -
Python (
dcp-ai2.8.0 → 2.8.1) —BundleManifestinsdks/python/dcp_ai/v2/models.pygainscanonicalization_profile: Literal[\"dcp-jcs-v1\"] | None = \"dcp-jcs-v1\". Pydantic enforces unknown-value rejection at parse time; absence triggers the default value. -
Go (
sdks/go/v2.8.0→v2.8.1) —BundleManifeststruct insdks/go/dcp/v2/types.gogainsCanonicalizationProfile stringwithjson:\"canonicalization_profile,omitempty\".BuildBundleV2insdks/go/dcp/v2/bundle_builder.gosets it to\"dcp-jcs-v1\"on every produced manifest.VerifySignedBundleV2insdks/go/dcp/v2/verify_bundle.gorejects bundles whose manifest carries any other value. -
Rust (
dcp-aicrates.io 2.8.0 → 2.8.1) —BundleManifestinsdks/rust/src/v2/types.rsgainspub canonicalization_profile: Option<String>with#[serde(skip_serializing_if = \"Option::is_none\")].wasm_build_bundleinsdks/rust/src/lib.rssets\"canonicalization_profile\": \"dcp-jcs-v1\"on the manifest it emits.
Spec & profile document
-
spec/DCP-AI-v2.0.md§ 9 (Bundle Manifest) now listscanonicalization_profilealongside the other manifest fields, with an inline note that an absent field MUST be assumed equivalent to\"dcp-jcs-v1\". -
spec/CANONICALIZATION_PROFILE.md§ 2 — Rule 6 (undefined / null handling) is rewritten as a cross-language table that explicitly maps TypeScript, Python, Go, and Rust host values onto the JSON wire format for both object slots and array slots. The previous prose-only rule left PythonNone, Gonil, and RustOption::Noneunderspecified for verifiers cross-checking foreign producers. -
spec/CANONICALIZATION_PROFILE.md§ 2 — new Rule 9 makes Unicode normalization (NFC, NFD, NFKC, NFKD) explicitly out of scope: the canonicalizer emits strings byte-for-byte as the host parser delivers them, and a future profile (dcp-jcs-v2or later) MAY pin a normalization form. RFC 8259 § 8.1 is referenced as the conventional choice (NFC) for application-layer normalisation. -
Rule 9 also pins the rationale for not shipping
NaN/±Infinityinterop fixtures: those values are not part of RFC 8259 wire format, so most parsers reject them before the canonicalizer is reached. Rejection is verified by per-SDK unit tests rather than shared fixtures.
Behavioural compatibility
The new field is additive and optional in serialization:
- A
v2.8.1-produced bundle carriescanonicalization_profile: \"dcp-jcs-v1\"on its manifest. - A bundle produced by
v2.8.0or earlier has nocanonicalization_profilefield.v2.8.1verifiers accept it: perspec/CANONICALIZATION_PROFILE.md§ 4, an absent field MUST be assumed equivalent to\"dcp-jcs-v1\". - A bundle that carries
canonicalization_profilewith an unknown value (e.g.\"dcp-jcs-v2\") is rejected by everyv2.8.1SDK. This is the forward-compatibility hook for future profiles: a verifier that has not been updated to a new profile MUST refuse it rather than silently accept a manifest it cannot validate.
The bundle-level signature covers canonical(manifest), so adding the field changes the manifest hash of any newly-produced bundle relative to one that would have been produced under v2.8.0. Bundles produced under v2.8.0 and earlier, and their stored signatures, remain valid under v2.8.1 verifiers.
Tests
- Conformance (root): `DCP-AI CONFORMANCE PASS (V1 + V2)`
- TypeScript: 461 / 461
- Python: 236 / 236
- Go: 4 packages OK
- Rust: 181 tests across 10 binaries
Versions bumped
- `dcp-ai` (PyPI) 2.8.0 → 2.8.1
- `dcp-ai` (crates.io) 2.8.0 → 2.8.1
- `github.com/dcp-ai-protocol/dcp-ai/sdks/go/v2` v2.8.0 → v2.8.1
- `@dcp-ai/sdk` (npm) 2.1.0 → 2.1.1 — first TypeScript code change since 2.1.0 (the 2.8.0 release was a docstring-only edit)
Registries
After publishing the artefacts (PyPI, crates.io, npm) and the Go module proxy picks up the tag, install with:
```
pip install dcp-ai==2.8.1
cargo add dcp-ai@2.8.1
go get github.com/dcp-ai-protocol/dcp-ai/sdks/go/v2@v2.8.1
npm install @dcp-ai/sdk@2.1.1
```
DCP-AI v2.8.0 — canonicalization profile dcp-jcs-v1 (spec)
What shipped
- New normative document
spec/CANONICALIZATION_PROFILE.md— formalises profiledcp-jcs-v1with eight numbered rules, an edge-case acceptance table, an undefined-handling contract documenting the TypeScript-only asymmetry (omit in objects, serialize asnullin arrays), and a versioning policy for future profiles. - Schema field declared in
schemas/v2/bundle_manifest.schema.json: optionalcanonicalization_profile: "dcp-jcs-v1"on bundle manifests. Per the spec, verifiers that encounter a bundle without the field MUST assumedcp-jcs-v1. Future profiles will make the field required. - Spec § 15 rewritten to reference the full profile document.
- 14 interop edge-case vectors added to
tests/interop/v2/interop_vectors.jsonundercanonicalization.edge_cases. Each SDK's interop test loads the same block and asserts byte-identical output (or matching error) for every vector.
SDK changes
- Python
dcp-ai2.7.1 → 2.8.0 —assert_no_floatsnow accepts floats whose value is a finite integer (1.0,1e2,1.00); they are normalised to integer form beforejson.dumps. Non-integer floats (0.1),NaN, and infinities remain rejected. - Rust
dcp-ai2.7.0 → 2.8.0 —assert_no_floatsnow acceptsNumber(f64)whosefract()is zero. Newformat_numberhelper emits integer-valued floats without decimal point or exponent, matching the other SDKs byte-for-byte. - Go
sdks/go/v2.7.0→v2.8.0— behaviour already aligned (v != math.Trunc(v)check). Version-bump only. - TypeScript
@dcp-ai/sdkstays 2.1.0 — behaviour already aligned. Docstring insdks/typescript/src/core/canonicalize.tsformalises the undefined-vs-null asymmetry.
What is NOT in this release
The schema field is declared but the typed BundleManifest structures in the four SDKs do not expose it, the BundleBuilder types do not emit it by default, and verifiers do not route on it. Code-side wiring of the field across the four SDKs lands in v2.8.1 (see release notes there).
Behavioural compatibility
Bundles produced by previous releases remain valid. The change is additive for SDKs that previously rejected 1.0 / 1.00 / 1e2: inputs that used to throw TypeError / Err now succeed and produce the same canonical output the other SDKs were already producing. No input that previously canonicalised successfully canonicalises differently in 2.8.0.
Registries
- PyPI: https://pypi.org/project/dcp-ai/2.8.0/
- crates.io: https://crates.io/crates/dcp-ai/2.8.0
- Go: `go get github.com/dcp-ai-protocol/dcp-ai/sdks/go/v2@v2.8.0`
- npm `@dcp-ai/sdk`: stays at 2.1.0 (no functional change)
DCP-AI Python 2.7.1
Fix: add cryptography as required dep for A2A session encryption. Supersedes the broken 2.7.0 Python wheel. Rust and Go unaffected — they stay at 2.7.0.
DCP-AI v2.7.0
Cross-SDK behavioral parity across TS, Python, Rust, Go. See CHANGELOG.md for details.
v2.6.0
Blinded RPR, multi-party auth, advisory helpers. See CHANGELOG.md.
v2.5.0
A2A protocol parity. See CHANGELOG.md.
v2.4.0
Production hardening primitives. See CHANGELOG.md.
v2.3.0
Dispute + Rights + Delegation parity. See CHANGELOG.md.
v2.2.0
Lifecycle + Succession parity. See CHANGELOG.md.
DCP-AI v2.1.0 — observability release
DCP-AI v2.1.0 — observability release (all four SDKs)
This release completes the OpenTelemetry integration across the four core DCP-AI SDKs. All four now speak OTLP directly — no hand-rolled bridges, same metric names, same counter names, same span shapes.
What shipped
| SDK | Package | Version | OTLP enable-switch |
|---|---|---|---|
| TypeScript | @dcp-ai/sdk |
2.1.0 |
exporterType: 'otlp' + install @opentelemetry/* peers |
| Python | dcp-ai |
2.1.0 |
pip install 'dcp-ai[otlp]' |
| Rust | dcp-ai |
2.1.0 |
features = ["otlp"] |
| Go | sdks/go/v2 |
sdks/go/v2.1.0 |
go build -tags otlp |
Metric surface (identical across all four SDKs)
- Histograms:
dcp.sign.latency_ms,dcp.verify.latency_ms,dcp.kem.latency_ms,dcp.checkpoint.latency_ms,dcp.bundle_verify.latency_ms - Counters:
dcp.signatures.created,dcp.signatures.verified,dcp.bundles.verified,dcp.a2a.sessions,dcp.a2a.messages,dcp.errors - Span lifecycle:
dcp.sign,dcp.verify, plus any custom spans you start via the SDK API - Resource attributes:
service.name,service.version,sdk.language
How to enable (pick any backend — Jaeger, Grafana Cloud, Honeycomb, Datadog, self-hosted Collector)
See docs/OBSERVABILITY.md for the three copy-paste recipes (local Jaeger via Docker, Grafana Cloud OTLP, Honeycomb) and one-page install + wire-in instructions for each SDK.
Backward compatibility
- Fully backward compatible. Default installs are unchanged.
- Telemetry is disabled by default in every SDK — the recorders short-circuit and zero memory is consumed until you call
init(enabled=True). - If you opt into
ExporterType::Otlp/exporterType: 'otlp'/exporter_type="otlp"/ExporterOTLPwithout the optional OTel dependencies installed, every SDK surfaces the missing-deps message as an error event viaonEvent/on_event/OnEventinstead of crashing the application. - The WASM SDK remains at 2.0.x; browser telemetry has different transport requirements and will ship in a later minor.
Behind the scenes (for future contributors)
Every SDK implements the same primitives:
- Disabled-by-default
Telemetrysingleton with a thread-safe event bus. - Recorder methods (
record_sign_latency,record_verify_latency, etc.) that append to in-memory histograms and emitmetric/counterevents. on_event(listener)fan-out so custom dashboards and CLI tools can subscribe without needing OTel.get_metrics_summary()for synchronous p50/p95/p99 snapshots.- Lazy OTLP bridge loaded only when the exporter is enabled and its optional peer deps are present.
Test coverage for the observability layer:
- TypeScript: 460/460 vitest cases pass
- Python: 15 new pytest cases (145/145 total)
- Rust: 10 unit tests (
cargo testgreen with and without--features otlp) - Go: 10 unit + 2 integration tests (
go test ./...andgo test -tags otlp ./...both green)