Skip to content

Commit 676e899

Browse files
committed
Merge branch 'main' into feat/repeated-boot-notification-scenario
Resolves the conflict from branching before ocpp-debugkit#161. The previous resolution left three stray '>' markers (index.ts and index.test.ts) and deleted heartbeat-timeout.ts, which reverted ocpp-debugkit#161 and broke typecheck and build. Restores heartbeat-timeout.ts, drops the markers, rebuilds the registry wiring on current main, and moves the scenario count to 21 across all four sites. The scenario file and changeset are unchanged from this branch.
2 parents 20e3d78 + ca2bb0e commit 676e899

11 files changed

Lines changed: 315 additions & 29 deletions

File tree

‎.changeset/brave-pandas-count.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@ocpp-debugkit/toolkit': patch
3+
---
4+
5+
Add a meter-value-zero scenario to ensure a flat zero meter register does not report a failure.

‎CURRENT_STATE.md‎

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@
88
99
## Current Version
1010

11-
`0.4.4`, firmware-update-failure scenario (published 2026-07-30). The v0.4.x
12-
interop and correctness milestone is complete; the line continues to take patch
13-
releases for scenario additions. `0.4.3` and `0.4.4` each carry a
14-
`good-first-issue` completed by an outside contributor: after
15-
`REPEATED_BOOT_NOTIFICATION` (Issue #105, PR #114) in `0.3.1`, that is three
16-
external good-first contributions in total.
11+
`0.4.6`, heartbeat-timeout scenario (published 2026-08-08). The v0.4.x interop and
12+
correctness milestone is complete; the line continues to take patch releases for
13+
scenario additions. Three of those patches each carry a `good-first-issue`
14+
completed by an outside contributor, `0.4.3` (Issue #104, PR #133), `0.4.4`
15+
(Issue #138, PR #147) and `0.4.6` (Issue #137, PR #161). With
16+
`REPEATED_BOOT_NOTIFICATION` (Issue #105, PR #114) in `0.3.1`, that is four
17+
external good-first contributions in total, from three different people.
1718

1819
## Release Log
1920

@@ -24,6 +25,7 @@ step). Do not edit between the markers by hand. Full history lives in
2425
sections further down.
2526

2627
<!-- RELEASE-LOG:START -->
28+
- `0.4.6` (2026-08-08): Add `heartbeat-timeout` scenario covering the `TIMEOUT_NO_HEARTBEAT` detection rule. The synthetic trace boots a station with `interval=300`, then sends a `StatusNotification` past the 2× interval threshold (`06:12:00.000Z`) with no `Heartbeat` anywhere in the trace.
2729
- `0.4.5` (2026-07-30): Report every refusing `AuthorizationStatus` in `FAILED_AUTHORIZATION`, not just `Invalid` (#156). The OCPP 1.6 enumeration (edition 2, section 7.2) has five values and only `Accepted` permits charging, so `Blocked`, `Expired` and `ConcurrentTx` end a driver's session exactly as `Invalid` does. The rule fired on `Invalid` alone, 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 the `ConcurrentTx` case. Adds a `refused-authorization` scenario covering the three newly reported statuses, bringing the corpus to 18.; Match `FIRMWARE_UPDATE_FAILURE` to the OCPP 1.6 `FirmwareStatus` enumeration (#154, edition 2 section 7.25). The rule matched `DownloadPaused`, `InstallFailed` and `InstallRebootingFailed`, none of which are 1.6 status values, and did not match `InstallationFailed`, 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 exactly `DownloadFailed` and `InstallationFailed`, and the `firmware-update-failure` scenario reports `InstallationFailed` instead of the non-spec `InstallFailed` it used before.; Transcribe the `STATUS_TRANSITION_VIOLATION` matrix 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 from `Faulted` to any pre-fault state and from `Unavailable` straight 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 -> Unavailable` and its siblings), produced a spurious warning. `Preparing -> Unavailable` and `Finishing -> Reserved` are 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.
2830
- `0.4.4` (2026-07-30): feat(scenarios): add firmware-update-failure scenario
2931
<!-- RELEASE-LOG:END -->
@@ -385,7 +387,7 @@ these fixes introduce is required across detection.
385387
IDs are documented as unique per scenario
386388
- ✅ Good-first-issues carry a one-open-claim-at-a-time policy
387389

388-
### External Contribution Pipeline (2026-07-26 to 2026-07-30)
390+
### External Contribution Pipeline (2026-07-26 to 2026-08-08)
389391

390392
- ✅ Second `good-first-issue` completed by an outside contributor: #133 for
391393
#104, shipped in `0.4.3`. The scenario registry is at 16. Two of the five
@@ -405,8 +407,11 @@ these fixes introduce is required across detection.
405407
only on negative or decreasing cumulative readings, and a flat series is
406408
neither.
407409
- ✅ Station IDs allocated per issue so parallel work cannot collide:
408-
`CS-SYNTHETIC-016` shipped in #133, `017` to #108, `018` to #137, `019`
409-
shipped in #147, `020` to #139.
410+
`CS-SYNTHETIC-016` shipped in #133, `018` shipped in #161, `019` shipped in
411+
#147, `020` reserved for #139, `021` reserved for #108. `017` is NOT free:
412+
`refused-authorization.ts` took it in `0.4.5`, which is why #108 moved off it.
413+
Live allocation is 004 through 019 in use, 020 and 021 reserved, so the next
414+
free number is 022.
410415
- ✅ #140 landed (PR #148): the standing invariant that every detection rule
411416
ships with a scenario in the same PR, added to `CONTRIBUTING.md` as a statement
412417
and as step 7 of the rule checklist. This is what stops the coverage gap
@@ -420,15 +425,36 @@ these fixes introduce is required across detection.
420425
for OCPP 2.0.1.
421426
- ✅ Third external contributor arrived (`MayurK-cmd`), assigned #137
422427
(`heartbeat-timeout`). #139 held for them next under the one-claim policy.
428+
- ✅ Fourth `good-first-issue` completed and the third external contributor's
429+
first merge: #137 (`heartbeat-timeout`) by `MayurK-cmd` (Issue #137, PR #161).
430+
The registry is at 19. They also picked up a missing
431+
`getScenario('refused-authorization')` assertion that PR #159 left behind, which
432+
was not asked for and was correct.
433+
- ✅ Both remaining scenario issues are now assigned: #139 to `MayurK-cmd` under
434+
the promise made when they claimed two at once, and #108 to
435+
`YANGCHUNHONG3000`, their third.
436+
- ✅ #108 carried a live trap, caught before the contributor started: it reserved
437+
`CS-SYNTHETIC-017`, which `refused-authorization.ts` had already consumed in
438+
`0.4.5`. Anyone following the issue would have created the same collision #133
439+
did. Moved to `CS-SYNTHETIC-021`.
440+
- ✅ The release automation from #152 proved itself unattended: `0.4.5` was logged
441+
and the Package Status Table moved with no manual step. Only the narrative here
442+
still needs a human, which is the split by design.
423443
- 🔜 #144 proposes a `METER_VALUE_STUCK` rule for a register that never advances,
424444
the positive counterpart to #108.
445+
- 🔜 #163 records that CI never evaluates the scenarios' declared `assertions`.
446+
`evaluateScenario` reaches the registry only through `ocpp-debugkit ci`, which
447+
is not a workflow step, and the one test that touches it builds its scenarios by
448+
hand. Nine scenario files assert things nothing verifies. Found while reviewing
449+
#161; pre-existing and unrelated to that contribution.
425450

426-
Rule coverage: after #147, two of the sixteen detection rules still lack a
427-
scenario, `TIMEOUT_NO_HEARTBEAT` (#137, assigned) and `REPEATED_BOOT_NOTIFICATION`
428-
(#139, open).
451+
Rule coverage: after #161, one of the sixteen detection rules still lacks a
452+
scenario. Verified by iterating the registry, 15 of the 16 codes appear in some
453+
scenario's `expectedFailures`, and `REPEATED_BOOT_NOTIFICATION` (#139, assigned)
454+
is the sole gap.
429455

430-
Scenario arithmetic to the v1.0 target of 20+: 17 today, plus #108, #137 and #139
431-
lands at 20, at which point all 16 detection rules are covered.
456+
Scenario arithmetic to the v1.0 target of 20+: 19 today, and #108 plus #139 take
457+
it to 21, at which point all 16 detection rules are covered.
432458

433459
### Release Log Automation (Issue #151, PR #152)
434460

@@ -477,7 +503,7 @@ lands at 20, at which point all 16 detection rules are covered.
477503

478504
| Package | Status | Version |
479505
|---------|--------|---------|
480-
| `@ocpp-debugkit/toolkit` | published | 0.4.5 |
506+
| `@ocpp-debugkit/toolkit` | published | 0.4.6 |
481507
| `@ocpp-debugkit/core` | deprecated | 0.1.1 |
482508
| `@ocpp-debugkit/scenarios` | deprecated | 0.1.1 |
483509
| `@ocpp-debugkit/reporter` | deprecated | 0.1.1 |

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ validate behavior against known scenarios.
2222
firmware update failures, suspicious session duration, slow CSMS responses,
2323
heartbeat interval violations, meter value anomalies, unresponsive CSMS, and
2424
repeated boot notifications.
25-
- **Scenario Evaluator** — 20 predefined scenarios with expected failure
25+
- **Scenario Evaluator** — 21 predefined scenarios with expected failure
2626
outcomes for testing the analysis engine. Supports external scenario files.
2727
- **Replay Engine** — Deterministic, pure replay engine with step forward/back,
2828
jump-to-event, and configurable playback speed.

‎packages/toolkit/CHANGELOG.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# @ocpp-debugkit/toolkit
22

3+
## 0.4.6
4+
5+
### Patch Changes
6+
7+
- dc2a77c: Add `heartbeat-timeout` scenario covering the `TIMEOUT_NO_HEARTBEAT` detection rule. The synthetic trace boots a station with `interval=300`, then sends a `StatusNotification` past the 2× interval threshold (`06:12:00.000Z`) with no `Heartbeat` anywhere in the trace.
8+
39
## 0.4.5
410

511
### Patch Changes

‎packages/toolkit/README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ report generation, React components, and CLI.
2020
firmware update failures, suspicious session duration, slow CSMS responses,
2121
heartbeat interval violations, meter value anomalies, unresponsive CSMS, and
2222
repeated boot notifications.
23-
- **Scenario Evaluator** — 20 predefined scenarios with expected failure
23+
- **Scenario Evaluator** — 21 predefined scenarios with expected failure
2424
outcomes for testing the analysis engine. Supports external scenario files.
2525
- **Replay Engine** — Deterministic, pure replay engine with step forward/back,
2626
jump-to-event, and configurable playback speed. No timers or I/O.

‎packages/toolkit/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@ocpp-debugkit/toolkit",
3-
"version": "0.4.5",
3+
"version": "0.4.6",
44
"description": "Open-source DevTools for debugging OCPP charging sessions — parser, normalizer, timeline, failure detection, scenarios, replay, reports, CLI, and React components.",
55
"license": "Apache-2.0",
66
"type": "module",
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
export default {
2+
name: 'heartbeat-timeout',
3+
description:
4+
'Station boots and never sends a Heartbeat. BootNotification response sets interval=300s, threshold at 600s. StatusNotification after threshold triggers TIMEOUT_NO_HEARTBEAT.',
5+
trace: {
6+
traceId: 'scenario-heartbeat-timeout',
7+
metadata: {
8+
stationId: 'CS-SYNTHETIC-018',
9+
ocppVersion: '1.6',
10+
source: 'synthetic-scenario',
11+
description: 'Station boots and never sends a Heartbeat — timeout expected.',
12+
},
13+
events: [
14+
{
15+
timestamp: '2026-01-15T06:00:00.000Z',
16+
direction: 'CS_TO_CSMS',
17+
message: [
18+
2,
19+
'msg-001',
20+
'BootNotification',
21+
{
22+
chargePointVendor: 'SyntheticVendor',
23+
chargePointModel: 'SM-100',
24+
chargePointSerialNumber: 'CS-SYNTHETIC-018',
25+
firmwareVersion: '1.0.0',
26+
},
27+
],
28+
},
29+
{
30+
timestamp: '2026-01-15T06:00:00.500Z',
31+
direction: 'CSMS_TO_CS',
32+
message: [
33+
3,
34+
'msg-001',
35+
{
36+
currentTime: '2026-01-15T06:00:00.500Z',
37+
interval: 300,
38+
status: 'Accepted',
39+
},
40+
],
41+
},
42+
{
43+
timestamp: '2026-01-15T06:12:00.000Z',
44+
direction: 'CS_TO_CSMS',
45+
message: [
46+
2,
47+
'msg-002',
48+
'StatusNotification',
49+
{ connectorId: 1, status: 'Available', errorCode: 'NoError' },
50+
],
51+
},
52+
{
53+
timestamp: '2026-01-15T06:12:00.500Z',
54+
direction: 'CSMS_TO_CS',
55+
message: [3, 'msg-002', {}],
56+
},
57+
],
58+
},
59+
expectedFailures: ['TIMEOUT_NO_HEARTBEAT'],
60+
assertions: [
61+
{
62+
type: 'failure_severity',
63+
params: { code: 'TIMEOUT_NO_HEARTBEAT', severity: 'warning' },
64+
},
65+
{
66+
type: 'failure_count',
67+
params: { code: 'TIMEOUT_NO_HEARTBEAT', min: 1, max: 1 },
68+
},
69+
],
70+
};
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
export default {
2+
name: 'meter-value-zero',
3+
description:
4+
'Transaction with a flat zero meter register throughout. A zero reading is a valid negative control and must not trigger METER_VALUE_ANOMALY.',
5+
trace: {
6+
traceId: 'scenario-meter-value-zero',
7+
metadata: {
8+
stationId: 'CS-SYNTHETIC-021',
9+
ocppVersion: '1.6',
10+
source: 'synthetic-scenario',
11+
description:
12+
'Station completes a four-minute transaction while the cumulative energy register remains at zero.',
13+
},
14+
events: [
15+
{
16+
timestamp: '2026-01-15T09:00:00.000Z',
17+
direction: 'CS_TO_CSMS',
18+
message: [
19+
2,
20+
'msg-001',
21+
'BootNotification',
22+
{
23+
chargePointVendor: 'SyntheticVendor',
24+
chargePointModel: 'SM-100',
25+
chargePointSerialNumber: 'CS-SYNTHETIC-021',
26+
firmwareVersion: '1.0.0',
27+
},
28+
],
29+
},
30+
{
31+
timestamp: '2026-01-15T09:00:00.500Z',
32+
direction: 'CSMS_TO_CS',
33+
message: [
34+
3,
35+
'msg-001',
36+
{ currentTime: '2026-01-15T09:00:00.500Z', interval: 300, status: 'Accepted' },
37+
],
38+
},
39+
{
40+
timestamp: '2026-01-15T09:00:30.000Z',
41+
direction: 'CS_TO_CSMS',
42+
message: [2, 'msg-002', 'Authorize', { idTag: 'SYNTHETIC-TAG-021' }],
43+
},
44+
{
45+
timestamp: '2026-01-15T09:00:30.500Z',
46+
direction: 'CSMS_TO_CS',
47+
message: [3, 'msg-002', { idTagInfo: { status: 'Accepted' } }],
48+
},
49+
{
50+
timestamp: '2026-01-15T09:01:00.000Z',
51+
direction: 'CS_TO_CSMS',
52+
message: [
53+
2,
54+
'msg-003',
55+
'StartTransaction',
56+
{ connectorId: 1, idTag: 'SYNTHETIC-TAG-021', meterStart: 0 },
57+
],
58+
},
59+
{
60+
timestamp: '2026-01-15T09:01:00.500Z',
61+
direction: 'CSMS_TO_CS',
62+
message: [3, 'msg-003', { idTagInfo: { status: 'Accepted' }, transactionId: 100021 }],
63+
},
64+
{
65+
timestamp: '2026-01-15T09:02:00.000Z',
66+
direction: 'CS_TO_CSMS',
67+
message: [
68+
2,
69+
'msg-004',
70+
'MeterValues',
71+
{
72+
connectorId: 1,
73+
transactionId: 100021,
74+
meterValue: [
75+
{
76+
sampledValue: [
77+
{ value: '0', measurand: 'Energy.Active.Import.Register', unit: 'Wh' },
78+
],
79+
},
80+
],
81+
},
82+
],
83+
},
84+
{
85+
timestamp: '2026-01-15T09:02:00.500Z',
86+
direction: 'CSMS_TO_CS',
87+
message: [3, 'msg-004', {}],
88+
},
89+
{
90+
timestamp: '2026-01-15T09:03:00.000Z',
91+
direction: 'CS_TO_CSMS',
92+
message: [
93+
2,
94+
'msg-005',
95+
'MeterValues',
96+
{
97+
connectorId: 1,
98+
transactionId: 100021,
99+
meterValue: [
100+
{
101+
sampledValue: [
102+
{ value: '0', measurand: 'Energy.Active.Import.Register', unit: 'Wh' },
103+
],
104+
},
105+
],
106+
},
107+
],
108+
},
109+
{
110+
timestamp: '2026-01-15T09:03:00.500Z',
111+
direction: 'CSMS_TO_CS',
112+
message: [3, 'msg-005', {}],
113+
},
114+
{
115+
timestamp: '2026-01-15T09:04:00.000Z',
116+
direction: 'CS_TO_CSMS',
117+
message: [
118+
2,
119+
'msg-006',
120+
'MeterValues',
121+
{
122+
connectorId: 1,
123+
transactionId: 100021,
124+
meterValue: [
125+
{
126+
sampledValue: [
127+
{ value: '0', measurand: 'Energy.Active.Import.Register', unit: 'Wh' },
128+
],
129+
},
130+
],
131+
},
132+
],
133+
},
134+
{
135+
timestamp: '2026-01-15T09:04:00.500Z',
136+
direction: 'CSMS_TO_CS',
137+
message: [3, 'msg-006', {}],
138+
},
139+
{
140+
timestamp: '2026-01-15T09:05:00.000Z',
141+
direction: 'CS_TO_CSMS',
142+
message: [
143+
2,
144+
'msg-007',
145+
'StopTransaction',
146+
{
147+
transactionId: 100021,
148+
idTag: 'SYNTHETIC-TAG-021',
149+
meterStop: 0,
150+
reason: 'Local',
151+
},
152+
],
153+
},
154+
{
155+
timestamp: '2026-01-15T09:05:00.500Z',
156+
direction: 'CSMS_TO_CS',
157+
message: [3, 'msg-007', { idTagInfo: { status: 'Accepted' } }],
158+
},
159+
],
160+
},
161+
expectedFailures: [],
162+
assertions: [{ type: 'no_failures', params: {} }],
163+
};

0 commit comments

Comments
 (0)