From 683cc10e81a67bb62d2563878c8460fb2e7e1655 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 10 Sep 2026 20:39:28 +0700 Subject: [PATCH 1/2] docs: add scoped IEC-103 master runtime contract --- src/ARIEC60870.Master/AGENTS.md | 130 ++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 src/ARIEC60870.Master/AGENTS.md diff --git a/src/ARIEC60870.Master/AGENTS.md b/src/ARIEC60870.Master/AGENTS.md new file mode 100644 index 0000000..4265471 --- /dev/null +++ b/src/ARIEC60870.Master/AGENTS.md @@ -0,0 +1,130 @@ +# ARIEC60870 Master Runtime — Scoped Engineering Contract + +These instructions apply to `src/ARIEC60870.Master/**` and extend the repository root `AGENTS.md`. The root file remains authoritative for product identity, IEC-103 polling policy, UI/output policy, clean-room rules, release behavior, and simulator boundaries. + +## 1. Runtime authority + +`ARIEC60870.Master` owns active-master session state, transport orchestration, polling policy, FCB/FCV behavior, Class 1 drain decisions, startup sequence, timeout/backoff, and live protocol evidence. + +Desktop/CLI code may command and observe the master; it must not become a second master state machine. + +## 2. Root-cause-first rule + +For session, polling, serial, GI, ACD, DFC, timeout, Stop, or reconnect defects: + +`reproduce → capture evidence → trace state transition → identify owner → fix → regression test → validate cleanup` + +Do not add timers, delays, extra polling, duplicate flags, or UI-side recovery until the actual state transition and transport behavior are understood. + +If three patches in the same runtime subsystem still chase symptoms, STOP before patch four and re-audit the state machine, transport ownership, cancellation path, and protocol evidence. + +## 3. Explicit state machines + +Connection/session progress must be represented explicitly rather than inferred from scattered booleans. + +Expected states should cover the actual lifecycle, for example: + +`Idle → Opening → Starting → Running → Stopping → Completed/Faulted → Idle` + +Protocol subflows such as GI and Class 1 drain should also have explicit bounded progression. + +Unexpected input must produce a defined state/result, not an undefined transition or infinite retry. + +## 4. Result-oriented failure architecture + +Expected runtime outcomes should use typed Result/status values rather than exception-driven normal control flow. + +Examples: + +- timeout/no data; +- DFC busy; +- invalid or rejected secondary response; +- transport closed during Stop; +- cancellation; +- exhausted bounded retry/drain; +- unsupported operation; +- malformed frame reported by Core; +- serial device unavailable. + +Infrastructure exceptions from serial/.NET I/O should be caught at the transport/session boundary and converted into a structured runtime result plus diagnostic evidence where recovery is possible. + +Do not silently swallow exceptions. Do not use throw/retry loops as a protocol state machine. + +## 5. Stop/cancellation is a first-class path + +Stop is not an exceptional afterthought. + +- one owner controls the active session cancellation token; +- transport close may be used to release blocked serial operations; +- close/dispose races during cancellation are expected boundary conditions and must be contained; +- finalization must run exactly once; +- session state must converge to a stable terminal/idle state; +- no stale callback may revive a stopped session. + +Never rely on arbitrary sleeps to make Stop reliable. + +## 6. Polling and backpressure + +Preserve the root polling contract: + +- Class 2 is normal/background polling; +- Class 1 drain is triggered by protocol evidence such as ACD or bounded GI follow-up; +- NO DATA/ACD clear ends unnecessary Class 1 drain; +- DFC causes bounded backoff; +- timeout retry/reset policy remains controlled. + +Do not create an unbounded request queue. If command production can outrun the transport, use explicit bounded scheduling/backpressure. + +## 7. Diagnostic pipeline + +Detailed operator diagnostics are important, but the polling/receive path must remain lightweight. + +Hot runtime paths should emit compact structured evidence/events into bounded storage or queues. Expensive formatting, report text, and UI-ready message composition should happen outside the critical receive/polling path where practical. + +- bound queues/retained evidence; +- aggregate or rate-limit repeated normal timeout/no-data conditions; +- preserve counters for full-session totals; +- never allow diagnostic persistence/UI consumers to block master progress; +- diagnostic failure must not corrupt protocol state. + +## 8. Transactional configuration/session promotion + +New transport/session/polling configuration should be validated before it replaces active/last-known-good state. + +`parse → validate → prepare → activate` + +If opening or preparation fails, do not leave a half-active session whose UI state claims Running. + +## 9. Resource ownership + +Every session has deterministic ownership for: + +- serial/network transport; +- timers; +- cancellation sources; +- receive loops; +- polling tasks; +- event subscriptions; +- retained evidence buffers. + +Repeated Start/Stop and reconnect cycles must not accumulate tasks, timers, handlers, or open handles. + +## 10. Performance and regression evidence + +For changes to polling, receive loops, evidence retention, or cancellation, measure/inspect relevant before/after behavior when practical: + +- poll cadence/jitter; +- command/response latency; +- CPU during sustained monitoring; +- memory/evidence growth; +- Stop latency; +- retained queue depth; +- repeated Start/Stop resource count. + +A repeatable regression of roughly >10% in a relevant metric requires investigation and explicit justification. + +Every bug fix should add the highest-value deterministic Master test practical, especially for timeout, DFC, ACD/Class 1 drain, cancellation, transport close, and finalization. + +## Final rule + +The master runtime must remain deterministic even when the relay is silent, slow, busy, malformed, disconnected, or the operator presses Stop at the worst possible moment. From f8e0964289eee6f084250df539002eb0f772db0e Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Thu, 10 Sep 2026 20:39:59 +0700 Subject: [PATCH 2/2] docs: add scoped protocol core failure contract --- src/ARIEC60870.Core/AGENTS.md | 105 ++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 src/ARIEC60870.Core/AGENTS.md diff --git a/src/ARIEC60870.Core/AGENTS.md b/src/ARIEC60870.Core/AGENTS.md new file mode 100644 index 0000000..64aca70 --- /dev/null +++ b/src/ARIEC60870.Core/AGENTS.md @@ -0,0 +1,105 @@ +# ARIEC60870 Core — Protocol Parsing Contract + +These instructions apply to `src/ARIEC60870.Core/**` and extend the repository root `AGENTS.md`. + +## 1. Core authority + +Core owns byte-level FT1.2/link/ASDU decoding, protocol primitives, mapping primitives, and offline analysis semantics. UI and Master may consume Core results; they must not duplicate parser truth. + +Parsing is evidence-preserving protocol work. Malformed input must be observable and bounded, never converted into invented protocol meaning. + +## 2. Root-cause-first parser workflow + +For decoding defects: + +1. reduce the failing frame to the smallest lawful synthetic fixture that reproduces the issue; +2. identify the exact protocol layer/field whose invariant is violated; +3. fix the parser/model at that layer; +4. add deterministic positive + negative regression coverage; +5. verify existing valid frames remain unchanged. + +If three patches still chase parsing symptoms, STOP before patch four and re-audit framing boundaries, length rules, field offsets, type/COT interpretation, and parser ownership. + +## 3. Try/Result parsing, not exception control flow + +Untrusted frame bytes must not use exceptions for expected malformed-data control flow. + +Prefer explicit parse outcomes such as `TryParse`, typed Result objects, or equivalent discriminated status carrying: + +- success/failure; +- failure code/layer; +- safe offset/field context; +- parsed value when valid; +- raw evidence needed by higher layers. + +Exceptions remain appropriate for genuine programming/invariant bugs, not normal invalid field values, short frames, unsupported ASDU types, checksum mismatch, or truncated input. + +## 4. Defensive byte parsing + +Before every indexed/length-dependent read validate the required bounds. + +Explicitly handle where applicable: + +- minimum frame length; +- declared vs available length; +- checksum/end byte; +- control/link fields; +- ASDU header availability; +- FUN/INF/data payload boundaries; +- time fields; +- unsupported/unknown Type or COT; +- numeric conversion and overflow; +- partial captures/noise before a frame. + +Never trust a length byte enough to index memory without checking actual buffer bounds. + +## 5. Preserve unknown evidence + +Unknown/unsupported values must remain visible as unknown/raw evidence. Do not guess vendor semantics, signal names, or unsupported protocol meaning. + +Friendly interpretation is allowed only when supported by protocol data or user mapping. Raw bytes and numeric protocol identity remain available to callers. + +## 6. Bounded work + +Parser complexity must be bounded by supplied data. Do not add unbounded search/retry loops for malformed streams. + +For stream resynchronization, make progress explicit: every iteration must consume input, find a bounded candidate, or terminate with a structured result. + +Avoid unnecessary per-frame allocations on high-frequency paths. Pool/reuse only when measurement justifies the complexity. + +## 7. Numerical/time correctness + +Validate protocol time/date fields before constructing host date/time values. Invalid relay timestamps must remain invalid/diagnostic evidence rather than silently becoming PC arrival time. + +Numeric decoding must define byte order, signedness, scale/representation, and invalid/overflow behavior explicitly. + +## 8. Diagnostics separation + +Core should return compact structured parse evidence/errors. It should not synchronously format large UI/report strings on hot parsing paths. + +Higher layers may turn structured results into operator diagnostics. Error storms from repeated malformed/noise input must be bounded/aggregated by the owning runtime layer. + +## 9. Regression contract + +Parser changes should cover applicable cases: + +- valid fixed/variable frame; +- shortest valid frame; +- truncated frame at each meaningful boundary; +- declared-length mismatch; +- checksum/end failure; +- unknown Type/COT/FUN/INF; +- invalid timestamp; +- boundary numeric values; +- back-to-back frames/noise/resynchronization; +- round-trip/golden bytes where the project owns lawful fixtures. + +Do not change expected bytes/interpretation in golden tests merely to make a new implementation pass without explaining why the old expectation was wrong. + +## 10. Completion evidence + +For substantial Core changes report root cause, protocol layer, structured failure behavior, regression fixtures/tests, performance/allocation impact where relevant, and any unsupported ambiguity that remains. + +## Final rule + +One malformed or unknown frame must never crash the analyzer, hang the stream parser, erase raw evidence, or force higher layers to invent meaning.