OCPP DebugKit Studio and the TypeScript toolkit are two independent implementations of the same OCPP analysis behavior — one native (Zig), one for the browser and CI (TypeScript). They share no code. They meet only here: a machine-checked conformance contract that guarantees both produce the same analysis of the same trace.
That is the trust story. A trace captured in Studio opens in the toolkit's web inspector and vice versa; a failure Studio flags is a failure the toolkit flags. Two implementations agreeing is a stronger correctness signal than either alone — and it is enforced in CI on every change, not asserted in prose.
- Trace format. OCPP-J array messages —
[2, id, action, payload](Call),[3, id, payload](CallResult),[4, id, errorCode, errorDescription, details](CallError) — in any of three containers: a JSON object with aneventsarray, JSONL (one message per line), or a bare array. - Event model. Each message normalizes to a canonical
Event(unique id, message id, timestamp, direction, message type, action, payload, error fields, and the raw array). Direction inference and timestamp normalization (ISO 8601 or epoch) are part of the contract. See ADR-0005. - Session correlation. Events correlate into
Sessions bytransactionId, with connector- and time-based attribution of un-keyed events. - Failure taxonomy. The full OCPP 1.6J failure model — 16 detection rules,
each with a stable
FailureCode, a fixed severity (4 critical, 10 warning, 2 info), and the same thresholds as the reference (heartbeat 60 s, slow response 10 s, ±50 % heartbeat deviation, session bounds, the 5-minute boot window). The rules live insrc/ocpp/detection.zig. - Scenarios. 18 shared scenario traces exercising the rules end to end,
each pinned to the exact de-duplicated, sorted
FailureCodeset the reference implementation detects.
The 18 scenarios and their expected failure-code sets are vendored from the
toolkit — the source of truth — never hand-authored, so a golden can't drift
from what the reference actually detects. On every change, native test runs
Studio's full engine (parseTrace → buildSessionTimeline → detectFailures) over
each fixture and asserts its detected code set equals the golden — the same
comparison the toolkit's evaluateScenario makes. 18/18 must match; a release
is blocked otherwise. studio ci runs the same check from the command line.
The corpus is a sample, not a proof: two implementations can both match every
golden and still disagree on inputs no fixture reaches. That is not theoretical.
Three rules disagreed with the specification here for as long as they did in the
toolkit while the harness stayed green, because no fixture exercised a firmware
status, a non-Invalid idTagInfo, or the connector-status transitions involved
(#93). When a rule changes, widen the corpus rather than trusting the count.
The vendored fixtures, goldens, the harness, and the exact regeneration recipe
live in src/ocpp/conformance/.
The current contract is contract-v2 (OCPP 1.6J), 18 scenarios, regenerated
against toolkit 0.4.5. The vendored fixtures and goldens are pinned, and the
harness gates every build against them. The cut is recorded in
ADR-0013; contract-v1 (15 scenarios, frozen
for 0.5.0) is ADR-0012.
contract-v2 is a strict superset of v1: regenerating all 18 against toolkit
0.4.5 reproduces v1's 15 fixtures and goldens byte for byte, so every scenario v1
pinned, v2 pins identically.
The contract is immutable within a version. A change that alters detected
output — a new or changed rule, a new scenario, a threshold change, or OCPP
2.0.1 — is a new contract version (contract-v3, …), regenerated against the
matching toolkit release and re-tagged, never an in-place edit of a released
version. Earlier versions stay reachable through the release tags: v0.5.4's tree
carries contract-v1, so every released Studio remains pinned to a reproducible
reference. This keeps "two implementations, one format" a checkable claim rather
than a hope.
One question this policy leaves open, tracked in #94: whether a rule correction that leaves every golden matching needs a version bump at all, given that the contract's artifacts are the fixtures and the goldens. The corpus expansion above is not that case, since "a new scenario" is named explicitly, so it does not settle it.
See also ADR-0001 (independent implementation + shared contract) and docs/cli-parity.md (Studio ⇄ toolkit CLI parity).