feat(M9): recognise OCPI 3.0 at the version layer — recognition-only forward-scaffold - #223
Merged
Merged
Conversation
…orward-scaffold) Seed the first M9 slice: teach the SDK to recognise OCPI `3.0` without standing up a `v3_0` type surface — the top-of-range mirror of the resolved recognition-only 2.0 slice (#182), and exactly what the repo's own 3.0 contract (specs/ocpi/3.0/README.md — "defer logic, not schema") prescribes. - `VersionNumber::V3_0` with `#[serde(rename = "3.0")]`, `as_str`/`FromStr` arms, and a doc note marking it recognition-only / blocked-upstream. - Order it highest: `V2_3_0 < V3_0`; ordering fence updated. - `version.rs` parse test now recognises "3.0" (was a hard parse error) plus a dedicated serde round-trip; the unknown-version test still rejects genuinely unknown versions (9.9 / 2.4 / ""). - Negotiation fences in ocpi-client: a `[3.0]`-only partner vs a `<= V2_3_0` supported set degrades to `None` (-> UnsupportedVersion), while `[3.0, 2.2.1]` still negotiates the highest mutual `2.2.1` — recognition never becomes selection. - No `v3_0` type module, no ModuleID/endpoint change, no new deps, no unsafe. - README: M9 note that the 3.0 recognition slice landed (no support-matrix column, same convention as 2.0). Closes #219 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PXeAhuPi1utnyuwqCtv1R7
This was referenced Jul 14, 2026
duyhuynh-vn
marked this pull request as ready for review
July 17, 2026 05:02
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #219
Summary
Seeds the first M9 slice: teaches the SDK to recognise OCPI
3.0at the version layer — parse/order it and degrade a 3.0-only partner to an explicitUnsupportedVersionstatus_code— without standing up av3_0type module. This is the direct top-of-range mirror of the resolved recognition-only 2.0 slice (#182) and exactly what the repo's own 3.0 contract prescribes (specs/ocpi/3.0/README.md— "defer logic, not schema").Before this change,
"3.0"was a hard parse error inVersionNumber::from_str, so a partner/versionscatalogue that merely listed3.0alongside a mutual version risked failing to deserialize as a whole. Now3.0is a recognised-but-unsupported version: the catalogue parses, negotiation lands on the highest mutual version, and only a partner offering nothing but3.0degrades toUnsupportedVersion.What changed (≤ ~80 LOC of real change):
VersionNumber::V3_0with#[serde(rename = "3.0")], itsas_str()/FromStrarms, and a doc note marking it recognition-only /blocked-upstream.V2_3_0 < V3_0); ordering fence updated.version.rsparse test now recognises"3.0"(wasis_err()) + a dedicated serde round-trip; the unknown-version test still hard-rejects genuinely unknown versions (9.9/2.4/"").ocpi-client:[3.0]-only vs a≤ V2_3_0supported set →None;[3.0, 2.2.1]→Some(V2_2_1).v3_0type module, noModuleID/endpoint change, no new dependencies, nounsafe.Real use case — why this PR is needed
ocpi-rsis consumed by evlinked/charge-hub (a hosted OCPI roaming Hub) and evlinked/stationlink. A Hub is the party most likely to meet a partner one version ahead of it: as CPOs/eMSPs begin advertising3.0in their/versionscatalogues during the 3.0 rollout, charge-hub fetches those catalogues while negotiating.Today, because
"3.0"is a hard parse error, a partner catalogue that merely lists3.0alongside2.2.1risks failing to deserialize as a whole — so a3.0-aware partner could break negotiation for the2.2.1/2.3.0version the hub and partner actually share. Recognising3.0as a known-but-unsupported version turns that into a clean outcome: the hub parses the catalogue, negotiates the highest mutual version, and only degrades toUnsupportedVersionfor a partner that offers nothing but3.0.That is the difference between "the hub keeps roaming with a forward-looking partner" and "one 3.0 entry in a version list breaks an otherwise-working handshake" — the same graceful-recognition guarantee the 2.0 slice already gives at the bottom of the range, now applied at the top. The trust boundary holds: recognition never becomes support — because no shipped
supportedset includesV3_0, negotiation can never select 3.0, so a 3.0-only partner degrades explicitly rather than falling into a half-implemented 3.0 path.Spec reference
specs/ocpi/3.0/README.md— the upstream-restricted / recognition-only forward-scaffold contract ("version negotiation will recognise 3.0 … logic deferred").specs/ocpi/2.0/README.md— the resolved 2.0 recognition-only precedent this mirrors;negotiate_disjoint_returns_noneis the fence template.crates/ocpi-types/src/version.rs—VersionNumber, itsas_str/FromStr, the ordering fence, and the"3.0" … is_err()test this slice flips.v1.3.0+).Test plan
cargo fmt --all -- --checkcargo clippy --all-targets --all-features -- -D warnings— cleancargo test --workspace --all-features— 577 passed, 0 failed (+3 new:version_number_v3_0_serde_round_trips,negotiate_disjoint_returns_none_for_3_0_only_partner,negotiate_ignores_forward_3_0_and_picks_highest_mutual)Notes
v3_0type surface: 3.0 is developed in a separate, access-restricted repository and is not vendored, so minting a type module would be exactly the "guess when the spec is ambiguous" failure mode the charter forbids. Schema is kept forward-compatible; logic staysblocked-upstreamuntil the 3.0 spec is public.cargo deny checkrun locally (not installed in this environment); it runs in CI.🤖 Generated with Claude Code
Claude session: https://claude.ai/code/session_01PXeAhuPi1utnyuwqCtv1R7
Generated by Claude Code