Skip to content

PQC251 — Poll must cover every status function it can

A status function the Thing declares is never set by the poll.

Commit refreshes exactly the functions the batch touched. A function the poll never sets keeps whatever value it last had — and for a function nothing else updates, that value is unknown, forever, on an operator's screen. A door whose tamper status never left unknown looks identical to a door whose tamper sensor is fine.

The generated batch builder carries one Set overload per status-bearing function the Thing declares, so the declared set and the covered set are both readable, and the difference between them is exact.

// reported: AcmeDoorStatusBatchBuilder also offers Set(TamperState), never called
public static async Task PollStatus(AcmeDoor door, CancellationToken ct)
    => await door.StatusBatch()
        .Set(DoorState.Secured)
        .Commit(ct);

How to fix it

Set it, if the protocol reports it. Usually the status query already carries the value and nobody wired it up:

var status = await protocol.ReadDoor(door.Address, ct);
await door.StatusBatch()
    .Set(status.DoorState)
    .Set(status.Tampered ? TamperState.Tamper : TamperState.Sealed)
    .Commit(ct);

Declare it, if the protocol cannot. Some states are event-only — the panel reports them when they change and offers no query. Say so on the handler:

[PollExcludes(typeof(TamperState), "Acme reports door tamper on the event stream only; no status query exposes it")]
public static async Task PollStatus(AcmeDoor door, CancellationToken ct)

The declaration is the point. A missing Set and a deliberate one look identical in code, and only the author knows which this is — so the exception has to be written down where the omission is, not in a document nobody opens while editing the handler.

What the check looks at

Coverage is assembled across everything the handler reaches. A poll handler is normally a dispatcher — one helper per element type, each building its own batch — so the rule follows calls within the adapter and unions what they set. A batch built in a helper counts for the handler that calls it.

The batch types judged are the ones actually committed somewhere under the handler. A Thing type the poll never touches is not this handler's problem.

What is not reported

Batches outside a poll. A trouble notification that updates two functions is a targeted update, not a refresh; Commit never clears the functions it did not touch, so partial batches are normal there.

Whether the protocol really exposes the state. The build knows the function is declared and unset. Whether the vendor offers a query for it needs the manual — which is what [PollExcludes] records.

If you disagree with a report

Do not suppress it. A wrong report is a bug in the check — report it with the code that triggered it.