Describe the Bug
FAILED_AUTHORIZATION fires only when an Authorize response carries
idTagInfo.status === 'Invalid':
const status = getAuthorizeStatus(event);
if (status === 'Invalid') { ... }
The OCPP 1.6 AuthorizationStatus enumeration (edition 2, section 7.2) has five
values, and only one of them permits charging:
| Value |
Meaning in the spec |
Accepted |
Identifier is allowed for charging |
Blocked |
Identifier has been blocked, not allowed for charging |
Expired |
Identifier has expired, not allowed for charging |
Invalid |
Identifier is unknown, not allowed for charging |
ConcurrentTx |
Identifier is already involved in another transaction |
So four of the five are refusals, and the rule detects one of them. A driver
whose token is blocked or expired hits exactly the failure the rule exists to
surface, the trace shows the same shape (an Authorize answered with a refusing
status, then no transaction), and the report says nothing. Silence from a
detector reads as "this is not the problem", which is worse here than no rule at
all.
ConcurrentTx is the one with a caveat: section 7.2 marks it "Only relevant for
a StartTransaction.req", so seeing it in an Authorize.conf is itself
irregular. It is still a refusal, and treating it as one costs nothing.
Steps to Reproduce
- Build the toolkit.
- Evaluate an
Authorize call answered with Blocked:
const events = [
{ message: [2, 'a1', 'Authorize', { idTag: 'SYNTHETIC-TAG-001' }] },
{ message: [3, 'a1', { idTagInfo: { status: 'Blocked' } }] },
];
const trace = parseTrace(JSON.stringify({ events }));
detectFailures(trace.events, buildSessionTimeline(trace.events)).map((f) => f.code);
- Repeat with
Expired and ConcurrentTx.
Expected Behavior
Each of the four non-Accepted statuses is reported as a failed authorization,
naming which status it was.
Actual Behavior
Only Invalid is reported. Blocked, Expired, and ConcurrentTx produce
nothing.
Environment
- OCPP DebugKit version: 0.4.4
- Node.js version: 26.x
- OS: macOS
Proposed Fix
The open question is whether these are one code or several. Proposal: keep a
single FAILED_AUTHORIZATION code covering all four refusals, with the status
named in the description (Authorization rejected: idTag returned "Blocked" status), and severity unchanged at warning.
Reasoning for one code rather than four: the operator-facing question is the same
in every case (the identity layer refused, so no energy will flow), the
suggested steps already cover all four causes, and the specific status is on the
failure object for anyone who wants to branch on it. Four codes would grow the
FailureCode union, and every code is part of the published surface and of the
contract-v1 corpus shared with Studio, so they are worth adding only when a
consumer would act differently. If we later want ConcurrentTx separated out
(it does point at CSMS state rather than at the token), that can be split then.
Out of scope, worth its own issue: the rule only inspects Authorize responses.
StartTransaction.conf and StopTransaction.conf also carry idTagInfo, and
section 4.8 is explicit that the Central System verifies the identifier again on
StartTransaction, so a session can start and then be deauthorized. That path is
currently invisible to this rule.
Note for whoever picks this up: this one widens what the engine detects, so it is
the change most likely to move contract-v1 goldens. None of the current
scenarios carry a non-Accepted, non-Invalid idTagInfo, so the corpus should
come out unchanged, but check rather than assume, and Studio's Zig rule
(src/ocpp/detection.zig) needs the same widening in lockstep.
Describe the Bug
FAILED_AUTHORIZATIONfires only when anAuthorizeresponse carriesidTagInfo.status === 'Invalid':The OCPP 1.6
AuthorizationStatusenumeration (edition 2, section 7.2) has fivevalues, and only one of them permits charging:
AcceptedBlockedExpiredInvalidConcurrentTxSo four of the five are refusals, and the rule detects one of them. A driver
whose token is blocked or expired hits exactly the failure the rule exists to
surface, the trace shows the same shape (an
Authorizeanswered with a refusingstatus, then no transaction), and the report says nothing. Silence from a
detector reads as "this is not the problem", which is worse here than no rule at
all.
ConcurrentTxis the one with a caveat: section 7.2 marks it "Only relevant fora
StartTransaction.req", so seeing it in anAuthorize.confis itselfirregular. It is still a refusal, and treating it as one costs nothing.
Steps to Reproduce
Authorizecall answered withBlocked:ExpiredandConcurrentTx.Expected Behavior
Each of the four non-
Acceptedstatuses is reported as a failed authorization,naming which status it was.
Actual Behavior
Only
Invalidis reported.Blocked,Expired, andConcurrentTxproducenothing.
Environment
Proposed Fix
The open question is whether these are one code or several. Proposal: keep a
single
FAILED_AUTHORIZATIONcode covering all four refusals, with the statusnamed in the description (
Authorization rejected: idTag returned "Blocked" status), and severity unchanged atwarning.Reasoning for one code rather than four: the operator-facing question is the same
in every case (the identity layer refused, so no energy will flow), the
suggested steps already cover all four causes, and the specific status is on the
failure object for anyone who wants to branch on it. Four codes would grow the
FailureCodeunion, and every code is part of the published surface and of thecontract-v1corpus shared with Studio, so they are worth adding only when aconsumer would act differently. If we later want
ConcurrentTxseparated out(it does point at CSMS state rather than at the token), that can be split then.
Out of scope, worth its own issue: the rule only inspects
Authorizeresponses.StartTransaction.confandStopTransaction.confalso carryidTagInfo, andsection 4.8 is explicit that the Central System verifies the identifier again on
StartTransaction, so a session can start and then be deauthorized. That path iscurrently invisible to this rule.
Note for whoever picks this up: this one widens what the engine detects, so it is
the change most likely to move
contract-v1goldens. None of the currentscenarios carry a non-
Accepted, non-InvalididTagInfo, so the corpus shouldcome out unchanged, but check rather than assume, and Studio's Zig rule
(
src/ocpp/detection.zig) needs the same widening in lockstep.