Skip to content

ADR 0052: Only an operator command starts a batch, and a scheduled start time is a plan rather than a trigger

Status: Accepted Date: 2026-08-11 Issue: #1462

Context

A Batch may carry spec.scheduledStartTime. The batch operator read it as a second way to start a batch. processPendingCommand waited while the time was in the future, and once it had passed it reported a start with no command annotation anywhere:

reconcile 0: phase="Allocating" state=""        msg="Allocating equipment units"
reconcile 1: phase="Running"    state="Running" msg="Units allocated, starting batch execution"

That was the whole mechanism. Naming a time set a vessel running, and no person at the plant was involved.

Three things in the product said the opposite. The MES create response set awaitingOperatorStart: true unconditionally, so the one work order where the claim was false asserted it as loudly as the ones where it was true. docs/mes-integration.md said a dispatched batch waits "for an operator to issue Start from the DCS. This is deliberate: an external system can queue work, and only a person at the plant can set a vessel running." And #864 recorded scheduling as deferred until customer demand. Only docs/architecture.md described the auto-start, in two places, and it described it as intended.

The MES work-order endpoint copies the field straight from the request, and examples/mes-integration/work-order.json — the file the integration guide tells integrators to POST — carries one. The shipped example was the case that broke the documented rule.

This is the honesty gap of #1451 pointed the other way. That endpoint accepted work it would never run. This one ran work it had said it would not run without a person, which is the more expensive direction for a control system.

The gateway is one door onto the field, not the only one. Batch is a CRD, so anyone with cluster credentials can apply one, and the fenced scheduling controller creates batches carrying the field as well. Closing the MES door alone would have left the sentence in the integration guide true of that door and false of the product.

Decision

A dcs.io/command: Start is the only thing that starts a batch. The scheduled-start branch is gone from processPendingCommand, so no code path starts a batch that was not commanded, whichever door created it.

spec.scheduledStartTime is the planned start. It is recorded on the batch, reported on GET /work-orders/{id}, and exported as the B2MML ProductionRequest StartTime — which is what that element means in ISA-95: a requested time, not an instruction. It gates nothing.

Because it gates nothing in either direction, it does not defer an operator's Start either. Start before the planned time starts the batch, at the moment the operator asks. The operator at the plant is the authority on when the vessel runs, and a field an external system wrote does not overrule them.

awaitingOperatorStart: true on the MES create response becomes true by construction rather than by hope, which is what #1450 added the field to state.

Alternatives Considered

Refuse a scheduledStartTime at the MES door. Rejected. It fixes the door this was found at and leaves the mechanism live behind every other one, so the integration guide's sentence would still have been false for a hand-applied Batch. It also refuses a field an ISA-95 work order naturally carries, which costs integrators a real capability — recording when the order is planned for — to close a hole that is not in the field.

Drop the field when copying to the Batch. Rejected for the same reason plus one more: silently discarding a value the caller sent is how #1451 happened. The MES would have gone on sending a planned time and gone on getting 201, with nothing recording it.

Keep the behaviour and report it truthfullyawaitingOperatorStart: false when the field is set, and a paragraph in the guide saying a work order may carry its own start time. Rejected by the founder on 2026-08-11. It is a coherent product, but it makes remote-triggered start a supported capability of a control system, and that is a capability to add deliberately with the interlocks and the authorization story worked out, not one to inherit from a field that was only ever meant to describe a plan.

Keep the future half as a gate, refusing or deferring a Start issued before the planned time. Rejected. It is the same defect wearing the opposite sign: an external system would decide when the plant may not run, and the operator holding the Start button would be overruled by a scheduling record. It also silently swallowed an Abort — a batch with a future planned time returned before the abort branch was reached, so cancelling a queued batch did nothing until its planned time arrived.

Consequences

A batch whose planned start has passed sits in Pending until someone starts it. That is a behaviour change for any deployment relying on the auto-start, and the change is the point. Nothing in the shipped surfaces relied on it: the schedule front-ends were removed in #480 and the schedule dispatch mode was refused in #1451, so the MES work-order path was the only live door onto it.

A batch the fenced scheduling controller creates at an entry's planned time now waits for an operator like any other. A schedule still decides when the work appears; it no longer decides when the vessel runs.

Aborting a queued batch works whatever its planned start says.

Time-triggered start is not a product capability today. A deployment that wants one gets it as a designed feature with its own authorization, not as a side effect of a metadata field — #864 is where that revival belongs.