Skip to content

bug: STATUS_TRANSITION_VIOLATION matrix does not match the OCPP 1.6 section 4.9 transition table #155

Description

@sepehr-safari

Describe the Bug

VALID_TRANSITIONS in packages/toolkit/src/core/detection.ts does not match the
status transition table in OCPP 1.6 edition 2, section 4.9. It disagrees in both
directions: it flags 22 transitions the table permits, and it permits 2 the table
does not list.

The table in section 4.9 lists 53 permitted transitions across the nine
ChargePointStatus values. Written out per source status:

From Permitted target statuses (section 4.9)
Available Preparing, Charging, SuspendedEV, SuspendedEVSE, Reserved, Unavailable, Faulted
Preparing Available, Charging, SuspendedEV, SuspendedEVSE, Finishing, Faulted
Charging Available, SuspendedEV, SuspendedEVSE, Finishing, Unavailable, Faulted
SuspendedEV Available, Charging, SuspendedEVSE, Finishing, Unavailable, Faulted
SuspendedEVSE Available, Charging, SuspendedEV, Finishing, Unavailable, Faulted
Finishing Available, Preparing, Unavailable, Faulted
Reserved Available, Preparing, Unavailable, Faulted
Unavailable Available, Preparing, Charging, SuspendedEV, SuspendedEVSE, Faulted
Faulted Available, Preparing, Charging, SuspendedEV, SuspendedEVSE, Finishing, Reserved, Unavailable

The rule's matrix has 33 entries, of which 31 are in the table.

Flagged although the table permits them (false positives), 22:

Available -> SuspendedEV          Available -> SuspendedEVSE
Preparing -> SuspendedEV          Preparing -> Finishing
Charging -> Unavailable
SuspendedEV -> SuspendedEVSE      SuspendedEV -> Unavailable
SuspendedEVSE -> SuspendedEV      SuspendedEVSE -> Unavailable
Finishing -> Preparing            Finishing -> Unavailable
Reserved -> Preparing
Unavailable -> Preparing          Unavailable -> Charging
Unavailable -> SuspendedEV        Unavailable -> SuspendedEVSE
Faulted -> Preparing              Faulted -> Charging
Faulted -> SuspendedEV            Faulted -> SuspendedEVSE
Faulted -> Finishing              Faulted -> Reserved

The Faulted and Unavailable rows are the worst of it. The table's I1-I8
row means a connector recovering from a fault may return to any pre-fault state,
and the H row means a connector coming back from Unavailable may go straight
to any operative state. The rule allows recovery only to Available, so every
station that faults mid-session and resumes charging, or that comes off a
ChangeAvailability back into a live session, produces a spurious warning.
Charging -> Unavailable (C8) and the three -> Unavailable rows are the same
kind of thing: a scheduled availability change lands during a session and gets
flagged.

Permitted although the table does not list them (false negatives), 2:

Preparing -> Unavailable    (no B8 cell)
Finishing -> Reserved       (no F7 cell)

Steps to Reproduce

Build the toolkit, then evaluate a two-message StatusNotification trace for each
ordered pair and compare against the table. For a single case:

const events = [
  { message: [2, 'n1', 'StatusNotification', { connectorId: 1, status: 'Faulted' }] },
  { message: [2, 'n2', 'StatusNotification', { connectorId: 1, status: 'Charging' }] },
];
const trace = parseTrace(JSON.stringify({ events }));
detectFailures(trace.events, buildSessionTimeline(trace.events)).map((f) => f.code);

Expected Behavior

Faulted -> Charging is cell I3 in the table, so no violation.

Actual Behavior

STATUS_TRANSITION_VIOLATION.

Environment

  • OCPP DebugKit version: 0.4.4
  • Node.js version: 26.x
  • OS: macOS

Proposed Fix

Replace VALID_TRANSITIONS with the table above, transcribed cell by cell, and
cite section 4.9 next to it so the next reader can check it against the spec
rather than against intuition. Add tests for the recovery rows (Faulted -> and
Unavailable ->) and for the two transitions that should now be flagged.

Two adjacent questions this issue deliberately does not change, so that the fix
stays a straight transcription. Both are worth their own issue if we want them:

  1. Repeated identical status. The table has no diagonal, so the rule treats
    Available -> Available as a violation, and will continue to. A charge point
    answering a TriggerMessage for StatusNotification, or re-reporting state
    after a reconnect, legitimately repeats its current status. Worth deciding
    whether a repeat should be exempt.
  2. connectorId 0. Section 4.9 notes the table applies to connectorId > 0,
    and that connectorId 0 is limited to Available, Unavailable, and Faulted.
    Every ordered pair among those three is permitted by the full table, so
    applying one matrix to both produces no false positives, but it also means a
    connectorId 0 reporting Charging is not flagged as out of its applicable set.

Note for whoever picks this up: DebugKit Studio carries an independent
implementation of this rule with the same matrix (src/ocpp/detection.zig), held
against the toolkit by the contract-v1 conformance corpus. It needs the same
transcription, or the two implementations diverge on every pair the shared
corpus does not cover.

Metadata

Metadata

Assignees

No one assigned

    Labels

    type:bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions