fix(calculation): read cache carries real input status + timestamps
This commit is contained in:
@@ -81,10 +81,43 @@ Two deploy-time gates run over the result:
|
||||
- **Nothing publishes until every declared dependency has arrived at least once.** Before that a read
|
||||
returns `BadWaitingForInitialData`. This is the same gate `VirtualTagActor` uses, and it exists so a
|
||||
calculation cannot publish a confident-looking value derived from defaults.
|
||||
- **Equal results are deduped** — an evaluation producing the same value as last time does not
|
||||
re-publish.
|
||||
- **Equal results are deduped on (value, *status*)** — an evaluation producing the same value *and*
|
||||
the same status as last time does not re-publish. The status half is load-bearing: a device that
|
||||
comes back after an outage usually comes back holding the reading it had before, so a value-only
|
||||
dedup would swallow the recovery and leave the node Bad forever.
|
||||
- **Timers are grouped by interval**, one timer per distinct interval, not one per tag.
|
||||
|
||||
## Input quality
|
||||
|
||||
The contract is stated once, in
|
||||
[`docs/DriverLifecycle.md` §"The quality contract"](../DriverLifecycle.md#the-quality-contract): a
|
||||
value consumer never reports a quality better than its source's. A calc tag's inputs are other tags'
|
||||
live values, so its output inherits their quality:
|
||||
|
||||
| Worst input status | What the driver does | What the calc tag publishes |
|
||||
|---|---|---|
|
||||
| Good | evaluates | the computed value, `Good` |
|
||||
| Uncertain (any sub-code) | **evaluates** — an Uncertain value is present and usable | the computed value, stamped with that Uncertain sub-code |
|
||||
| Bad (any sub-code) | **holds** — the script does not run | the **last known value**, stamped with that input's Bad sub-code, once per change of status |
|
||||
| a dependency never pushed | holds (the pre-existing warm-up gate) | nothing — the node stays `BadWaitingForInitialData` |
|
||||
|
||||
Notes worth knowing before authoring against this:
|
||||
|
||||
- **The sub-code propagates**, so the output says *why*: `BadNoCommunication` means go look at the
|
||||
device feeding the input; `BadInternalError` means this tag's own script failed.
|
||||
- **Holding on Bad is what removes the NRE storm.** A comms-lost input used to arrive as *Good, null*,
|
||||
so `(double)ctx.GetTag("x").Value` threw on every trigger and filled `/script-log` with identical
|
||||
"script threw" entries. Now the script simply is not run, and **no script-log entry is written** — an
|
||||
input dying is not a script fault.
|
||||
- ⚠️ **A script that relied on receiving `null` for a dead input no longer runs at all** while that
|
||||
input is Bad. Rewrite such a script to assume its inputs are usable; the driver has already decided
|
||||
they are.
|
||||
- **`ctx.GetTag(path).StatusCode` and `.SourceTimestampUtc` are real.** The read cache used to stamp
|
||||
every input `Good` at evaluation time; it now passes the input's own snapshot through. Since Bad
|
||||
inputs never reach a script, what a script can observe is Good or Uncertain.
|
||||
- **`ReadAsync` reports the same status the subscription carried**, so a polled read and a subscribed
|
||||
client never disagree about the same node.
|
||||
|
||||
## Error semantics
|
||||
|
||||
An evaluator failure publishes **Bad quality carrying the last-known value** — the value is not
|
||||
|
||||
Reference in New Issue
Block a user