Requirements and acceptance

Version 1.0.0. These values are teaching configuration, not sensor calibration.

ID Contract Check
R01 Startup JSON reports version, synthetic mode, default threshold and missing input; state begins FAULT Startup schema plus initial snapshot
R02 Inject time; acquire at most once per turn, no earlier than 1000 ms after prior acquisition; stale after age >3000 ms Scheduler equality/gap/wrap and timestamp tests
R03 JSON Lines with stable sample fields and JSON null for unavailable temperature Independent JSON parser and expected event sequences
R04 NORMAL enters ALERT at temperature >= threshold; default 28.0 °C 28.0 equality and boundary jitter
R05 ALERT clears at temperature <= threshold−1.0 °C; retain state inside band 27.0 equality; 27.5 from each direction
R06 Missing, nonfinite, or input age >3000 ms enters FAULT; stale value never current Missing/NaN/infinity/stale tests and replay
R07 Two consecutive fresh valid samples recover; invalid input or a stale gap resets count Recovery and interrupted recovery; after fault classify using fresh entry threshold
R08 threshold <decimal> terminated by LF; 64-byte maximum before LF, including optional CR; 0–50 °C inclusive; RAM only Full-line grammar, range, CRLF, NUL, overflow drain, truncation and repeated-command tests
R09 New process or Monitor::reset() restores threshold 28.0 and FAULT/missing Reset tests and replay reset event
R10 Compile exactly the same policy/parser/output .cpp sources for host and firmware Build source lists and file hashes
R11 Tests detect seeded source defects; original source still passes Isolated mutation reports and restored build evidence
R12 Export contains every needed source; second clone compiles/tests without authoring files Bundle verification, fresh clone host check; Windows second-checkout firmware receipt required separately

At startup FAULT/missing also uses the two-sample recovery rule. A first valid sample is current but the state remains FAULT/recovering. A second valid sample chooses NORMAL or ALERT using the entry threshold, without resurrecting a pre-fault alert. Missing input means the adapter has explicitly failed to supply a value. NaN and infinity are invalid regardless of threshold.

Call poll(now) before inspecting time-dependent state or changing a threshold. Acquisition calls poll internally. Age is uint32_t(now - sampled_at); the one-wrap interpretation requires chronological calls and gaps below 2^31 ms (about 24.85 days). Replay rejects larger/backward gaps. A scheduling gap does not create invented catch-up samples. Freshness is inclusive at 3000 ms and expired at 3001 ms; tests use an injected clock rather than waiting.

A valid threshold command re-evaluates a currently non-faulted reading against the new entry threshold immediately. It cannot clear FAULT or advance recovery. Invalid commands leave every setting intact. The parser accepts optional leading +/-, at least one integer digit, and optional decimal point followed by at least one fractional digit; it rejects exponents, embedded whitespace, trailing characters and nonfinite words. The optional final CR enables CRLF terminals. Oversized lines are drained to LF before another command can be accepted. A partial line at end of host input returns truncated; firmware waits for LF without blocking its loop. No timeout, persistence, or host timestamp is claimed to measure physical hardware behavior.