diff --git a/CURRENT_STATE.md b/CURRENT_STATE.md index aabd4f3..7b86622 100644 --- a/CURRENT_STATE.md +++ b/CURRENT_STATE.md @@ -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 @@ -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) | diff --git a/README.md b/README.md index 5b65bb0..b025e7d 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/docs/CONTRACT.md b/docs/CONTRACT.md new file mode 100644 index 0000000..4e31eed --- /dev/null +++ b/docs/CONTRACT.md @@ -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). diff --git a/docs/adr/0012-freeze-contract-v1.md b/docs/adr/0012-freeze-contract-v1.md new file mode 100644 index 0000000..8869a29 --- /dev/null +++ b/docs/adr/0012-freeze-contract-v1.md @@ -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. diff --git a/docs/adr/README.md b/docs/adr/README.md index 7996834..a3736e0 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -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 | diff --git a/docs/cli-parity.md b/docs/cli-parity.md index 7f70e99..090176c 100644 --- a/docs/cli-parity.md +++ b/docs/cli-parity.md @@ -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`.