Skip to content

feat(M9): recognise OCPI 3.0 at the version layer — recognition-only forward-scaffold - #223

Merged
duyhuynh-vn merged 1 commit into
mainfrom
claude/compassionate-euler-aq8w3h
Jul 17, 2026
Merged

duyhuynh-vn merged 1 commit into
mainfrom
claude/compassionate-euler-aq8w3h

Conversation

@duyhuynh-vn

Copy link
Copy Markdown
Contributor

Closes #219

Summary

Seeds the first M9 slice: teaches the SDK to recognise OCPI 3.0 at the version layer — parse/order it and degrade a 3.0-only partner to an explicit UnsupportedVersion status_codewithout standing up a v3_0 type 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 in VersionNumber::from_str, so a partner /versions catalogue that merely listed 3.0 alongside a mutual version risked failing to deserialize as a whole. Now 3.0 is a recognised-but-unsupported version: the catalogue parses, negotiation lands on the highest mutual version, and only a partner offering nothing but 3.0 degrades to UnsupportedVersion.

What changed (≤ ~80 LOC of real change):

  • VersionNumber::V3_0 with #[serde(rename = "3.0")], its as_str() / FromStr arms, and a doc note marking it recognition-only / blocked-upstream.
  • Orders highest (V2_3_0 < V3_0); ordering fence updated.
  • version.rs parse test now recognises "3.0" (was is_err()) + a dedicated serde round-trip; the unknown-version test still hard-rejects genuinely unknown versions (9.9 / 2.4 / "").
  • Two negotiation fences in ocpi-client: [3.0]-only vs a ≤ V2_3_0 supported set → None; [3.0, 2.2.1]Some(V2_2_1).
  • No v3_0 type module, no ModuleID/endpoint change, no new dependencies, no unsafe.
  • README M9 note that the recognition slice landed (no 3.0 support-matrix column — same convention as 2.0).

Real use case — why this PR is needed

ocpi-rs is 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 advertising 3.0 in their /versions catalogues 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 lists 3.0 alongside 2.2.1 risks failing to deserialize as a whole — so a 3.0-aware partner could break negotiation for the 2.2.1/2.3.0 version the hub and partner actually share. Recognising 3.0 as a known-but-unsupported version turns that into a clean outcome: the hub parses the catalogue, negotiates the highest mutual version, and only degrades to UnsupportedVersion for a partner that offers nothing but 3.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 supported set includes V3_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

Test plan

  • cargo fmt --all -- --check
  • cargo clippy --all-targets --all-features -- -D warnings — clean
  • cargo test --workspace --all-features577 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)
  • Added/updated tests (serde round-trip + negotiation fences)

Notes

  • Deliberately no v3_0 type 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 stays blocked-upstream until the 3.0 spec is public.
  • No cargo deny check run locally (not installed in this environment); it runs in CI.
  • This is the first landed M9 slice; the M9 tracker is otherwise thin — grooming follow-ups (conformance test-vector harness, fuzzing) filed separately.

🤖 Generated with Claude Code

Claude session: https://claude.ai/code/session_01PXeAhuPi1utnyuwqCtv1R7


Generated by Claude Code

…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
duyhuynh-vn marked this pull request as ready for review July 17, 2026 05:02
@duyhuynh-vn
duyhuynh-vn merged commit bc50995 into main Jul 17, 2026
13 checks passed
@duyhuynh-vn
duyhuynh-vn deleted the claude/compassionate-euler-aq8w3h branch July 17, 2026 05:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

M9: OCPI 3.0 forward-scaffold — recognise VersionNumber::V3_0 (recognition-only, no type surface), mirroring the resolved 2.0 slice (#182)

2 participants