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
```