Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions CURRENT_STATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ criticals raise OS notifications live (#60, ADR-0011) — all **in-runner**
publishes both to a GitHub release (see [RELEASING.md](RELEASING.md)). The
automation smoke now **drives** the GUI (#71) — clicking the replay transport,
a filter facet, and the Live tab, asserting the semantics tree after each and
screenshotting the inspector + live surfaces. Remaining: the frozen conformance
contract (#72), release-ready docs (#73), and the v0.5.0 cut (#74). The menu-bar
monitor and notarization are post-0.5; see [ROADMAP.md](ROADMAP.md).
screenshotting the inspector + live surfaces. The conformance contract is frozen
as `contract-v1` (#72, [docs/CONTRACT.md](docs/CONTRACT.md), ADR-0012). Remaining:
release-ready docs (#73) and the v0.5.0 cut (#74). The menu-bar monitor and
notarization are post-0.5; see [ROADMAP.md](ROADMAP.md).

## What's done

Expand Down Expand Up @@ -193,4 +194,4 @@ live inspector surface with notifications (#54–#60) — is landed.
| `ui` (native views) | ✅ S3 inspector (#27–#32) + replay transport (#44) + live-capture view (#59) + live notifications (#60) |
| `capture` (live proxy) | ✅ S5: WS transport (#54) + frame decode (#55) + MITM proxy (#56) + live notifications (#60) |
| `cli` (headless) | ✅ inspect/report/diff/anonymize/ci/scenario (#45) + live `capture` (#57) |
| `conformance` | ✅ done for S2 (15/15, `contract-v1`) |
| `conformance` | ✅ S2 (15/15) + frozen `contract-v1` (#72, docs/CONTRACT.md) |
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Studio is one of two independent products under the OCPP DebugKit umbrella:
| [`@ocpp-debugkit/toolkit`](https://github.com/ocpp-debugkit/toolkit) | TypeScript | npm library · CLI · web app | The library and CI brain — parse, analyze, and report OCPP traces anywhere |
| **`ocpp-debugkit/studio`** (this repo) | Zig | native desktop app | The instrument on the bench — live capture, native performance, OS integration |

The two share **no code**. They meet only at a *conformance contract*: the same trace format, the same normalized event model, the same failure taxonomy, and the same scenario fixtures. A trace captured in Studio opens in the toolkit's web inspector, and vice versa — two independent implementations, one format. See [ADR-0001](docs/adr/0001-independent-implementation.md).
The two share **no code**. They meet only at a *conformance contract*: the same trace format, the same normalized event model, the same failure taxonomy, and the same scenario fixtures. A trace captured in Studio opens in the toolkit's web inspector, and vice versa — two independent implementations, one format, checked in CI on every change. See **[the conformance contract](docs/CONTRACT.md)** (`contract-v1`) and [ADR-0001](docs/adr/0001-independent-implementation.md).

## What it will do

Expand Down
63 changes: 63 additions & 0 deletions docs/CONTRACT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# The conformance contract (`contract-v1`)

OCPP DebugKit Studio and the TypeScript [toolkit](https://github.com/ocpp-debugkit/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.

## What the contract covers

- **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 an `events` array,
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](adr/0005-engine-value-representation.md).
- **Session correlation.** Events correlate into `Session`s by `transactionId`,
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 in [`src/ocpp/detection.zig`](../src/ocpp/detection.zig).
- **Scenarios.** **15 shared scenario traces** exercising the rules end to end,
each pinned to the exact de-duplicated, sorted `FailureCode` set the reference
implementation detects.

## How conformance is enforced

The 15 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. **15/15 must match; a release
is blocked otherwise.** `studio ci` runs the same check from the command line.

The vendored fixtures, goldens, the harness, and the exact regeneration recipe
live in [`src/ocpp/conformance/`](../src/ocpp/conformance/README.md).

## Freeze & versioning

For the 0.5.0 release the contract is **frozen as `contract-v1`** (OCPP 1.6J):
the vendored fixtures and goldens are pinned, and the harness gates every build
against them. The freeze rationale is recorded in
[ADR-0012](adr/0012-freeze-contract-v1.md).

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-v2`, …), regenerated against the
matching toolkit release and re-tagged, never an in-place edit of `contract-v1`.
This keeps "two implementations, one format" a checkable claim rather than a hope.

See also [ADR-0001](adr/0001-independent-implementation.md) (independent
implementation + shared contract) and [docs/cli-parity.md](cli-parity.md)
(Studio ⇄ toolkit CLI parity).
41 changes: 41 additions & 0 deletions docs/adr/0012-freeze-contract-v1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# ADR-0012 — Freeze `contract-v1` for the 0.5.0 release

- **Status:** Accepted
- **Date:** 2026-07-12

## Context

Studio's correctness claim rests on the conformance contract (ADR-0001): it and
the TypeScript toolkit are independent implementations that must produce the same
analysis of the same trace, checked by the vendored `contract-v1` fixtures and
goldens under `src/ocpp/conformance/`. Through S2–S5 that harness has gated every
change at **15/15**. For the first public release (0.5.0) the contract needs to
be an explicit, stable baseline — a fixed reference point a user or the toolkit
can rely on — not an implicitly-current snapshot.

## Decision

**Freeze the vendored contract as `contract-v1` (OCPP 1.6J) for 0.5.0.** The
fixtures and goldens are pinned; the conformance harness gates every build
against them, and a release is blocked unless 15/15 match. The public contract is
documented in [CONTRACT.md](../CONTRACT.md); the vendored mechanics and the
regeneration recipe stay in `src/ocpp/conformance/README.md`.

The contract is **immutable within a version.** Any change that alters detected
output — a new or changed detection rule, a new scenario, a threshold change, or
OCPP 2.0.1 — is a **new version** (`contract-v2`, …), regenerated against the
matching toolkit release and re-tagged. `contract-v1` is never edited in place.

## Consequences

- 0.5.0 ships against a named, frozen baseline; "two independent implementations,
one format" is a checkable, CI-enforced claim, not a hope.
- The freeze is a documentation + policy act, not a code change — the harness and
goldens already exist; this pins them and states the change rule.
- Evolving the analysis (the O(n) detection rewrite #36, or 2.0.1) does not edit
`contract-v1`; it introduces a new contract version, keeping every released
Studio pinned to a reproducible reference.
- This ADR refines the versioning half of ADR-0001 (which established the
contract and the `contract-vN` scheme) by declaring the v1 freeze; it does not
supersede it. Graduating the contract to a standalone `spec` repo remains the
deferred option ADR-0001 described, not adopted here.
1 change: 1 addition & 0 deletions docs/adr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ supersedes the old one rather than editing history.
| [0009](0009-live-capture-effects-channel.md) | Live-capture effects channel (`update_fx` + `fx.spawn`) | Accepted |
| [0010](0010-live-proxy-transport-concurrency.md) | Live-proxy transport & concurrency | Accepted |
| [0011](0011-live-notification-services.md) | Live-capture notifications via the effects-bound platform services | Accepted |
| [0012](0012-freeze-contract-v1.md) | Freeze `contract-v1` for the 0.5.0 release | Accepted |
3 changes: 2 additions & 1 deletion docs/cli-parity.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ intentional difference.
- **`ci` runs the embedded contract**, not a user directory. Studio vendors the
15 `contract-v1` fixtures + goldens under `src/ocpp/conformance/` (ADR-0004),
so `ci` is a self-contained conformance gate — the same logic the
`native test` conformance harness runs.
`native test` conformance harness runs. See [the conformance
contract](CONTRACT.md).
- **One binary, two faces.** The CLI shares the exact pure engine the GUI uses
(`src/ocpp/`); there is no separate code path to drift. A bare trace path opens
the GUI: `studio path/to/trace.json`.
Expand Down
Loading