Living document — updated inside every PR before merge.
Since #151 the release facts (the Release Log and the Package Status Table version) are maintained automatically by the release workflow. Everything else here is hand-written.
0.4.4, firmware-update-failure scenario (published 2026-07-30). The v0.4.x
interop and correctness milestone is complete; the line continues to take patch
releases for scenario additions. 0.4.3 and 0.4.4 each carry a
good-first-issue completed by an outside contributor: after
REPEATED_BOOT_NOTIFICATION (Issue #105, PR #114) in 0.3.1, that is three
external good-first contributions in total.
The version and the entries below are maintained automatically by the release
workflow (scripts/update-current-state.mjs, run in the changesets version
step). Do not edit between the markers by hand. Full history lives in
packages/toolkit/CHANGELOG.md and the release
sections further down.
0.4.5(2026-07-30): Report every refusingAuthorizationStatusinFAILED_AUTHORIZATION, not justInvalid(#156). The OCPP 1.6 enumeration (edition 2, section 7.2) has five values and onlyAcceptedpermits charging, soBlocked,ExpiredandConcurrentTxend a driver's session exactly asInvaliddoes. The rule fired onInvalidalone, which meant a blocked or expired token produced a clean report, and silence from a detector reads as "this is not the problem". All four refusals now report under the existing code, with the status named in the description, and the suggested steps mention theConcurrentTxcase. Adds arefused-authorizationscenario covering the three newly reported statuses, bringing the corpus to 18.; MatchFIRMWARE_UPDATE_FAILUREto the OCPP 1.6FirmwareStatusenumeration (#154, edition 2 section 7.25). The rule matchedDownloadPaused,InstallFailedandInstallRebootingFailed, none of which are 1.6 status values, and did not matchInstallationFailed, which is one of the two failure values the enumeration defines. A conformant station reporting a failed installation, the more consequential of the two firmware outcomes, went undetected. The rule now matches exactlyDownloadFailedandInstallationFailed, and thefirmware-update-failurescenario reportsInstallationFailedinstead of the non-specInstallFailedit used before.; Transcribe theSTATUS_TRANSITION_VIOLATIONmatrix from the OCPP 1.6 status transition table (#155, edition 2 section 4.9). The matrix disagreed with the table in both directions: it flagged 22 transitions the table permits and permitted 2 it does not list. The false positives were concentrated in the recovery rows, where the table allows a connector to return fromFaultedto any pre-fault state and fromUnavailablestraight into an operative state, so any station that faulted mid-session and resumed charging, or that took a scheduled availability change during a session (Charging -> Unavailableand its siblings), produced a spurious warning.Preparing -> UnavailableandFinishing -> Reservedare absent from the table and are now flagged. The rule's matrix is now the table cell by cell, with the spec's own cell labels alongside it, and a test transcribes the table independently so the two have to agree.0.4.4(2026-07-30): feat(scenarios): add firmware-update-failure scenario
v0.5.0 (OCPP 2.0.1), after a complete v0.4.x
v0.4.0 shipped the Open OCPP Trace interop: the toolkit reads and writes the
shared v1.1 interchange format (input adapter #121, exporter + convert CLI
#122), checked against the specification's conformance fixtures. Both
false-positive bugs shiv3 found from the simulator integration are fixed and
released: METER_VALUE_ANOMALY measurand/connector flattening (#127, in 0.4.1)
and STATUS_TRANSITION_VIOLATION global status tracking (#128, in 0.4.2). Next
milestone is v0.5.0 (OCPP 2.0.1), where the per-connector / EVSE model
these fixes introduce is required across detection.
- ✅ Rule 14 now buckets readings by
(connectorId, measurand, phase, unit, location)and applies the monotonic + non-negative checks only to cumulativeEnergy.*.Registermeasurands (absentmeasuranddefaults toEnergy.Active.Import.Register); other measurands are ignored - ✅ Eliminates false positives on multi-measurand samples and multi-connector stations; genuine energy-register anomalies still detected (meter-anomaly scenario and conformance contract unchanged); 3 regression tests added
- Sibling connector-blindness in
STATUS_TRANSITION_VIOLATIONfixed under #128
- ✅ Rule 8 now tracks the previous status per
connectorId(connectorId 0, the whole charge point, forms its own series) and validates transitions only within one connector's series, instead of one global sequence - ✅ Eliminates false violations on multi-connector stations; genuine per-connector violations still detected (status-transition-violation scenario and conformance contract unchanged); 3 regression tests added
- ✅ Rule 10 now matches exactly the two failure values in the OCPP 1.6
FirmwareStatusenumeration (edition 2, section 7.25):DownloadFailedandInstallationFailed - ✅ Drops
DownloadPaused,InstallFailedandInstallRebootingFailed, none of which are 1.6 status values (DownloadPausedis an OCPP 2.0.1 value, and an intermediate state there rather than a failure); addsInstallationFailed, which the rule was missing, so a conformant station reporting a failed install is now detected - ✅
firmware-update-failurescenario reportsInstallationFailedin place of the non-specInstallFailed; its expected failure set is unchanged, so the conformance contract is unchanged - Studio carries an independent copy of this rule and needs the same narrowing
to stay equivalent under
contract-v1
- ✅
VALID_TRANSITIONSis now the OCPP 1.6 edition 2 section 4.9 transition table cell by cell, 53 permitted transitions, with the spec's own cell labels in comments so a row can be checked against the table - ✅ Removes 22 false positives, most of them in the recovery rows: the table
allows a connector to return from
Faultedto any pre-fault state (I1-I8) and fromUnavailableinto any operative state, and allows a scheduled availability change during a session (C8,D8,E8,F8) - ✅ Adds the 2 transitions the table omits but the rule permitted:
Preparing -> UnavailableandFinishing -> Reserved - ✅ A test transcribes the table independently and checks all 72 ordered pairs, so the matrix cannot drift from the spec again without a named failure
- ✅ status-transition-violation scenario (
Available -> Finishing) still detected, so the scenario corpus and the conformance contract are unchanged - Left deliberately unchanged, each worth its own issue: a repeated identical
status is still a violation (the table has no diagonal, but a
TriggerMessage-drivenStatusNotificationlegitimately repeats), and connectorId 0 is not checked against its narrower applicable set - Studio carries an independent copy of this matrix and needs the same
transcription to stay equivalent under
contract-v1
- ✅ Rule 1 now reports every refusing value of the OCPP 1.6
AuthorizationStatusenumeration (edition 2, section 7.2):Blocked,Expired,InvalidandConcurrentTx. It previously fired onInvalidalone, so a blocked or expired token produced a clean report - ✅ One code rather than four: the operator-facing question is the same in every
case, the status is named in the description, and severity stays
warning. Adding codes grows the publishedFailureCodeunion and thecontract-v1surface, so it is worth doing only when a consumer would act differently - ✅ New
refused-authorizationscenario (18 in the corpus) covering the three newly reported statuses; counts updated in the registry test, the external fixture test, and both READMEs - ✅ No existing scenario's detected code set changes, so the conformance contract is unchanged
- Out of scope, worth its own issue: the rule only inspects
Authorizeresponses, whileStartTransaction.confandStopTransaction.confalso carryidTagInfo(section 4.8 re-verifies the identifier onStartTransaction, so a session can start and then be deauthorized) - Studio's independent copy of this rule needs the same widening to stay
equivalent under
contract-v1
- ✅ GitHub milestones created (M0, M0.5, v0.1.0, v0.2.0, v0.3.0, v1.0.0)
- ✅ GitHub labels created (type, package, priority, workflow)
- ✅ Tracking issues created for M0, M0.5, and v0.1.0 (#20–#32)
- ✅ Root
package.jsonwith pnpm workspace - ✅
pnpm-workspace.yaml(packages/*,apps/*) - ✅
tsconfig.base.json(strict TypeScript config) - ✅ ESLint flat config (typescript-eslint strict)
- ✅ Prettier config +
.editorconfig - ✅ Vitest config + example passing test
- ✅ Turborepo config (build, test, lint, typecheck, clean)
- ✅
NOTICEfile (Apache 2.0) - ✅
AGENTS.md+CURRENT_STATE.md(initial versions)
- ✅
.github/workflows/ci.yml— lint, format check, typecheck, test, build on PR + push - ✅
.github/workflows/release.yml— Changesets version PR, npm publish, ecosystem tag + GitHub release - ✅
.changeset/config.json— public access, base branch main
- ✅
.github/PULL_REQUEST_TEMPLATE.md - ✅
.github/ISSUE_TEMPLATE/bug_report.md - ✅
.github/ISSUE_TEMPLATE/feature_request.md - ✅
.github/ISSUE_TEMPLATE/scenario_request.md
- ✅
CONTRIBUTING.md(setup, conventions, PR process, AI-assisted dev section) - ✅
CODE_OF_CONDUCT.md(Contributor Covenant 2.1) - ✅
ROADMAP.md(milestone summary) - ✅
README.md(description, badges, architecture, quickstart, support, links) - ✅
SECURITY.md(vulnerability reporting, security principles)
- ✅ 9 ADRs covering all design decisions
- ✅
docs/trace-format-spec.md— full trace format specification - ✅ 3 synthetic trace fixtures in
packages/core/src/__fixtures__/ - ✅ Proposed canonical types and fixture validation tests
- ✅
packages/core/src/schemas.ts— Zod schemas for all input types - ✅
packages/core/src/normalizer.ts—normalizeEvents(), direction inference (ADR-0004), timestamp normalization (ADR-0005) - ✅
packages/core/src/parser.ts—parseTrace()accepting JSON Object, JSONL, bare array - ✅
packages/core/src/types.ts— updated withFailure,Scenario,SessionSummary,ValidationResulttypes - ✅ 78 unit tests (46 normalizer + 32 parser)
- ✅
packages/core/src/timeline.ts—buildSessionTimeline()correlating events bytransactionId(ADR-0006) - ✅
packages/core/src/detection.ts—detectFailures()with 3 rules:FAILED_AUTHORIZATION,CONNECTOR_FAULT,STATION_OFFLINE_DURING_SESSION - ✅
packages/core/src/summarizer.ts—summarizeSession()/summarizeSessions()producing overview stats - ✅
packages/core/src/validator.ts—validateMessage()/validateMessages()checking OCPP 1.6 JSON structural compliance - ✅ 40 additional tests (10 timeline + 11 detection + 5 summarizer + 14 validator)
- ✅ Barrel export complete (types, schemas, parser, normalizer, timeline, detection, summarizer, validator, fixtures)
- ✅
sideEffects: falsefor tree-shaking - ✅
filesfield limits published content todist/+ docs - ✅
keywords,repository,homepage,bugsfields for npm discoverability - ✅ Package is npm-publish-ready (
access: public)
- ✅ Bumped
actions/checkoutandactions/setup-nodeto v5 (Node.js 20 deprecation) - ✅ Pinned
changesets/actiontov1.9.0 - ✅ Fixed root
changesetscript:"changeset add"→"changeset"(was causing release workflow failures)
- ✅
packages/scenarios/— new package - ✅ 5 scenarios: normal-session, failed-auth, connector-fault, station-offline, unexpected-stop-reason
- ✅ Scenario registry with
getScenario()lookup - ✅ Each scenario's
expectedFailuresaligns with v0.1 detection rules - ✅ 21 tests (registry, engine integration, synthetic data policy)
- ✅
packages/reporter/— new package - ✅
generateMarkdownReport()— session overview, timeline summary, failures, suggested steps, event appendix - ✅
AnalysisResultinput type - ✅ 11 tests (structure, failure inclusion, readability, metadata, severity)
- ✅
packages/cli/— new package - ✅
ocpp-debugkit inspect <file>— parse + analyze + output - ✅
ocpp-debugkit report <file>— generate Markdown report (stdout or file) - ✅
ocpp-debugkit scenario list— list all 5 scenarios - ✅
ocpp-debugkit scenario run <name>— run scenario through analysis engine, compare detected vs expected - ✅ Path safety: validated file paths, size limits
- ✅ Input validation: safe parsing, non-sensitive errors
- ✅ 17 integration tests (execa-based)
- ✅ Converted JSON fixtures to TS modules (fixes Node.js ESM JSON import issue)
- ✅
apps/web/— single Next.js app (App Router) - ✅ Tailwind CSS initialized
- ✅ Routes:
/(placeholder),/inspector(placeholder),/docs(placeholder) - ✅ Workspace dependencies on
@ocpp-debugkit/core,scenarios,reporter - ✅
"private": true(never publishable to npm)
- ✅ Landing page: hero, features, what-it's-not, architecture, quick start, footer
- ✅ Inspector: trace paste textarea + file upload + sample scenario selector
- ✅ Inspector: session timeline (click events to inspect)
- ✅ Inspector: message inspector panel (raw + normalized fields)
- ✅ Inspector: failure summary (severity, description, suggested steps)
- ✅ Inspector: Markdown report export (download)
- ✅ Loading state with spinner ("Parsing trace…")
- ✅ Error state improvements (non-sensitive messages, empty input guidance)
- ✅ Responsive layout (mobile-friendly: flex-wrap, smaller text on small screens)
- ✅ Keyboard navigation (arrow up/down to move through events)
- ✅ Sticky header for better UX on long traces
- ✅ Analyze button shows "Analyzing…" and disables during parsing
- ✅
playwright.config.ts— chromium, auto-start dev server - ✅ Landing page tests: page loads, hero, CTA links, features, footer
- ✅ Navigation tests: landing → inspector, landing → docs
- ✅ Inspector tests: empty state, sample scenario → timeline, failures, event click → message inspector, export button, invalid input error
- ✅ CI workflow updated: install browsers + run E2E after unit tests
- ✅
/docsindex with navigation sidebar - ✅
/docs/quickstart,/docs/glossary,/docs/architecture,/docs/trace-format,/docs/cli,/docs/scenarios
- ✅
packages/toolkit/— single@ocpp-debugkit/toolkitpackage created (PR #55) - ✅ Core, scenarios, reporter, CLI code moved into
src/core/,src/scenarios/,src/reporter/,src/cli/ - ✅
src/replay/andsrc/react/modules added - ✅ Subpath exports configured (
/core,/scenarios,/reporter,/replay,/react,/cli,/fixtures) - ✅ CLI binary
ocpp-debugkitviapackage.json#bin - ✅ Old package directories removed
- ✅ Web app updated to consume
@ocpp-debugkit/toolkit - ✅ All 196 existing tests pass from new locations
- ✅ Build, lint, typecheck, format all green
- ✅
private: trueset to prevent premature auto-publish (PR #67) - ✅ External fixture test + CI (PR #69)
- ✅ Migration docs + ADR-0010 (PR #71)
- ✅ Release workflow simplified for single package (PR #72)
- ✅ Expanded failure detection — 7 new rules (PR #70): TIMEOUT_NO_HEARTBEAT, METER_VALUE_GAP, INVALID_STOP_REASON, UNEXPECTED_START, STATUS_TRANSITION_VIOLATION, DIAGNOSTICS_FAILURE, FIRMWARE_UPDATE_FAILURE
- ✅ 10 total scenarios + CLI external scenario files (PR #75)
- ✅ Replay engine implementation + tests (PR #74)
- ✅ HTML report format + CLI --format html (PR #73)
- ✅ React components extracted from inspector (PR #76)
- ✅ App replay UI + report viewer + react refactor (PR #77)
- ✅
@ocpp-debugkit/toolkit@0.2.1published to npm (PR #79) - ✅ Old v0.1.1 packages (
@ocpp-debugkit/core,scenarios,reporter,cli) deprecated on npm - ✅ Git tag
v0.2.1+ GitHub releasev0.2.1created - ✅ Docs updated for v0.2 content (PR #78)
- ✅ Web app fixes — npm links, inspector nav, docs prose styling (PR #80, #81)
- ✅ Toolkit package README improved (PR #93, Issue #82)
- ✅ Post-v0.2.1 milestone boundary audit (PR #94, Issue #83)
- ✅ v0.3 failure detection rules — 5 new rules (Issue #84):
SUSPICIOUS_SESSION_DURATION,SLOW_RESPONSE,HEARTBEAT_INTERVAL_VIOLATION,METER_VALUE_ANOMALY,UNRESPONSIVE_CSMS(15 total rules) - ✅ Trace diffing —
diffTraces()API (Issue #85) - ✅ Rich scenario assertions — 8 assertion types,
evaluateScenario()(Issue #86) - ✅ Assert-based scenarios — 5 new scenarios (15 total) +
compareScenarioReports()(Issue #87) - ✅ CLI: ci + anonymize + diff commands (Issue #88)
- ✅ Integration examples — 4 example projects (Issue #89)
- ✅ Contribution guide + 5 good-first-issues (Issue #90)
- ✅ v0.3 docs content — 5 new + 3 updated docs pages (Issue #91)
- ✅
@ocpp-debugkit/toolkit@0.3.0published to npm - ✅ Git tag
v0.3.0+ GitHub releasev0.3.0created - ✅ v0.3.0 milestone closed
- ✅
REPEATED_BOOT_NOTIFICATION— flags 2+ BootNotification calls within five minutes. Added as the 16th detection rule (Issue #105).
- ✅
@ocpp-debugkit/toolkit@0.3.1published to npm - ✅ Git tag
v0.3.1+ GitHub releasev0.3.1created
- ✅
@ocpp-debugkit/toolkit@0.3.2published 2026-07-14, corrected the package README, which advertised 10 detection rules and 10 scenarios against the 16 and 15 actually shipping (Issue #118, PR #119)
- ✅
parseOpenOcppTrace()reads the Open OCPP Trace v1.1 interchange format (JSONL or JSON array of records);parseTrace()auto-detects and delegates (Issue #121) - ✅ Raw-frame precedence, messageId-based action derivation, unknown-field
tolerance; shared untrusted-input limits extracted to
parseLimits.ts - ✅
deriveOpenOcppTraceView()exposes the format's consumer view - ✅ 15 specification conformance fixtures vendored and asserted in CI
- ✅
toOpenOcppTraceRecords()/toOpenOcppTraceJsonl()export any parsed trace as v1.1 records:rawfrom the stored frame, responseactionback-filled by correlation, skip-and-flag for events the format cannot represent - ✅
ocpp-debugkit convert <file> [--output]emits the JSONL, carrying trace-level metadata over from JSON Object inputs - ✅ Every exported record validates against the specification's JSON Schema (vendored) in CI; round-trip tests prove export-then-reparse preserves the consumer view and the events
- ✅
@ocpp-debugkit/toolkit@0.4.0published 2026-07-17, Open OCPP Trace interop (#121, #122) - ✅
@ocpp-debugkit/toolkit@0.4.1published 2026-07-22,METER_VALUE_ANOMALYscoped to cumulative energy registers per connector (Issue #127, PR #129) - ✅
@ocpp-debugkit/toolkit@0.4.2published 2026-07-22,STATUS_TRANSITION_VIOLATIONtracked per connector (Issue #128, PR #131) - ✅
@ocpp-debugkit/toolkit@0.4.3published 2026-07-28,firmware-update-successscenario (Issue #104, PR #133) - ✅
@ocpp-debugkit/toolkit@0.4.4published 2026-07-30,firmware-update-failurescenario (Issue #138, PR #147) - ✅ Git tags + GitHub releases
v0.4.0throughv0.4.4created
- ✅ Second external contribution to a
good-first-issuearrived (#133, firmware-update-success scenario for #104), after #114 for #105. - ✅
CONTRIBUTING.mdnow documentspnpm format:check, which CI enforced and the guide never named, with both command lists ordered to match the CI job - ✅ Hard-coded rule and scenario counts dropped from the architecture table after drifting twice; the READMEs stay authoritative and the suite asserts them
- ✅ "Adding a Scenario" names all four files carrying the count, and station IDs are documented as unique per scenario
- ✅ Good-first-issues carry a one-open-claim-at-a-time policy
- ✅ Second
good-first-issuecompleted by an outside contributor: #133 for #104, shipped in0.4.3. The scenario registry is at 16. Two of the five original good-first-issues have now been completed, by two different people. - ✅ Scenario changesets sized as
patch, notminor(Issue #142, PR #143). The changeset from #133 was a minor, which would have spent0.5.0on one scenario and pushed OCPP 2.0.1 to0.6.0. The convention is now written intoCONTRIBUTING.md. - ✅ Detection-rule coverage audited: 13 of 16 rules had a scenario.
TIMEOUT_NO_HEARTBEAT,FIRMWARE_UPDATE_FAILUREandREPEATED_BOOT_NOTIFICATIONhad none. - ✅ Three good-first-issues opened to close that gap (#137, #138, #139), each with its full trace specified and checked against the detection engine before publishing, so the specs are known to fire exactly one rule.
- ✅ #108 (
meter-value-zero) retargeted as a negative control. Its originalexpectedFailures: ['METER_VALUE_ANOMALY']could not hold: that rule fires only on negative or decreasing cumulative readings, and a flat series is neither. - ✅ Station IDs allocated per issue so parallel work cannot collide:
CS-SYNTHETIC-016shipped in #133,017to #108,018to #137,019shipped in #147,020to #139. - ✅ #140 landed (PR #148): the standing invariant that every detection rule
ships with a scenario in the same PR, added to
CONTRIBUTING.mdas a statement and as step 7 of the rule checklist. This is what stops the coverage gap reopening. - ✅ Third
good-first-issuecompleted: #138 (firmware-update-failure) by the same contributor as #133, shipped in0.4.4(Issue #138, PR #147). The registry is at 17. Their PR bumped the detection-rule count in the READMEs by mistake (a scenario is not a rule); corrected in review, rules stay 16. - ✅ The patch-not-minor changeset convention held on its own for
0.4.4: the release PR proposed0.4.4with no intervention, sov0.5.0stays reserved for OCPP 2.0.1. - ✅ Third external contributor arrived (
MayurK-cmd), assigned #137 (heartbeat-timeout). #139 held for them next under the one-claim policy. - 🔜 #144 proposes a
METER_VALUE_STUCKrule for a register that never advances, the positive counterpart to #108.
Rule coverage: after #147, two of the sixteen detection rules still lack a
scenario, TIMEOUT_NO_HEARTBEAT (#137, assigned) and REPEATED_BOOT_NOTIFICATION
(#139, open).
Scenario arithmetic to the v1.0 target of 20+: 17 today, plus #108, #137 and #139 lands at 20, at which point all 16 detection rules are covered.
- ✅ This document went stale after three consecutive releases (
0.3.2,0.4.3,0.4.4), each needing a manual catch-up PR (#146, #150). The cause was structural: the changesets release PR bumpspackage.jsonandCHANGELOG.mdand never touched this file, so the version drifted every cycle. - ✅
scripts/update-current-state.mjsnow runs immediately afterchangeset version, wired through aversion:packagesroot script that the release workflow'sversioncommand calls. The edit rides the existing "version packages" PR, which already gets CI and a human merge, so nothing pushes directly to protectedmainand no extra PR is created. - ✅ Two spots are machine-owned: the
@ocpp-debugkit/toolkitrow in the Package Status Table, and the Release Log between itsRELEASE-LOGmarkers. The editorial prose, including the Current Version narrative, stays hand-written by design, since a per-release sentence is commentary rather than a fact. - ✅ The script is idempotent and uses only Node built-ins, so it adds no
dependency.
scripts/update-current-state.test.tscovers changelog parsing, the table replace, insert ordering, idempotency, and both error paths. - ✅ An eslint override supplies Node globals for
scripts/**/*.mjs, which run outside the TypeScript build. - 🔜 One link is unproven until it runs for real: the changesets action committing this file into the version PR. Standard changesets behaviour commits whatever the version command produces, so the next release is the check. If the entry is missing from the next "version packages" PR, that step is where to look.
- v0.5.0 - OCPP 2.0.1 Support - extend the engine beyond 1.6J: message set, device model, scenarios, and detection
- v1.0.0 - Stable FOSS Ecosystem - API stabilization, 20+ scenarios, docs overhaul, contributor onboarding
- Future - active scenario runner (live endpoint testing), playground, YAML trace format support
- Nothing is blocked. One design question is open: #144 (
METER_VALUE_STUCK) needs decisions on what counts as stuck, how to avoid reporting legitimateSuspendedEV/SuspendedEVSEflatlines, whether a register stuck at zero is a separate signal from one stuck at a non-zero value, and severity. It most likely wants the per-connector model arriving with v0.5.0, so it is not urgent.
| Package | Status | Version |
|---|---|---|
@ocpp-debugkit/toolkit |
published | 0.4.5 |
@ocpp-debugkit/core |
deprecated | 0.1.1 |
@ocpp-debugkit/scenarios |
deprecated | 0.1.1 |
@ocpp-debugkit/reporter |
deprecated | 0.1.1 |
@ocpp-debugkit/cli |
deprecated | 0.1.1 |
The website (landing, inspector, docs) now lives in
ocpp-debugkit/website, deployed at
ocppdebugkit.com and built on the published @ocpp-debugkit/toolkit package.