These rules apply to every AI/code agent working in this repository. arstack61850 is protocol/embedded engineering software. Correctness, deterministic timing, bounded resource use, interoperability, and failure containment are product requirements from the first implementation.
Do not begin with a disposable, intentionally naive, prototype-only implementation when the production architecture is knowable.
Choose the smallest production-quality design that satisfies the requirement without speculative complexity.
Priority order:
- protocol correctness and data integrity;
- deterministic/fail-safe behavior;
- regression/interoperability compatibility;
- bounded latency, CPU, memory, and queue depth;
- maintainability and testability.
Never trade a working IEC 61850 capability for a shortcut that only makes a local demo pass.
For non-trivial changes:
RECONNAISSANCE -> REPRODUCE/BASELINE -> ROOT CAUSE -> INVARIANTS -> ARCHITECTURE IMPACT -> IMPLEMENT -> REGRESSION TEST -> NEGATIVE/FAILURE TEST -> PERFORMANCE/TIMING CHECK -> BUILD/CI -> INTEROP VALIDATION
Before editing:
- locate the current protocol path, state machine, buffer ownership, callback/lifecycle owner, and all consumers;
- identify wire-format, timing, sequence, dataset, control-block, and public API invariants;
- determine root cause before patching symptoms;
- avoid creating a second parser/state machine/cache/source of truth for behavior that already exists;
- preserve portable core behavior unless a platform-specific boundary is explicitly required.
If one approach fails, stop and reassess assumptions. Do not stack workaround upon workaround.
Keep the protocol core independent from UI, shell, packaging, and platform glue.
Preferred conceptual direction:
Protocol/Core models and codecs -> Session/state machines and application services -> Platform adapters (socket, timer, filesystem, NIC, OS) -> GUI/CLI/embedded integration
Rules:
- wire encoding/decoding must not depend on UI code;
- ESP32, Windows/Linux, and test transports should adapt to shared protocol semantics instead of forking them;
- avoid global mutable protocol state;
- explicit ownership is required for sessions, buffers, timers, sockets, datasets, reports, control blocks, and callbacks;
- do not introduce abstraction layers with no current use.
Treat every received frame, file, configuration, and remote endpoint as fallible.
Before accessing fields:
- validate minimum header length;
- validate declared payload length against available bytes;
- validate offsets, counts, indexes, and alignment assumptions;
- validate enum/tag/type values;
- validate integer arithmetic for overflow/underflow;
- validate string/BER/MMS lengths before allocation or copy;
- reject or safely isolate malformed/truncated inputs.
Never index or dereference based only on untrusted wire metadata.
In C/C++ use explicit bounds checks, RAII where available, scoped ownership, std::optional/typed result/error patterns where appropriate, and safe integer conversions.
Do not hide errors with broad catch-all handling. Fail at a meaningful boundary and preserve process/session availability when isolation is possible.
A malformed packet must not crash the analyzer, GUI, service, or embedded target.
IEC 61850 association, report, GOOSE, Sampled Values, control, file transfer, discovery, reconnect, and embedded generation logic must use explicit states/transitions where state exists.
Never repair a state-machine race with arbitrary sleeps or retries.
Every retry policy must be bounded and have:
- maximum attempt or timeout behavior;
- cancellation/shutdown behavior;
- clear transition after terminal failure;
- no unbounded reconnect or busy loop.
Unexpected or out-of-order traffic must not corrupt session state.
Do not perform blocking filesystem, DNS/network setup, logging, allocation-heavy formatting, GUI work, or unrelated computation inside high-frequency packet callbacks, transmit loops, ISR-adjacent paths, or timing-critical workers.
On desktop, expensive work belongs on bounded background execution rather than the UI thread.
On embedded targets, timing-critical work must remain deterministic and bounded. Avoid dynamic behavior whose worst-case cost is unknown.
Never spawn one thread/task per packet or event.
All producer/consumer paths must have an explicit overload policy.
Avoid unbounded queues for:
- captured frames;
- MMS responses;
- GOOSE events;
- SV samples;
- logs;
- GUI updates;
- transmit requests;
- device telemetry.
Use bounded ring buffers, batching, coalescing, sampling, drop policies, or backpressure according to semantics.
When loss is acceptable for presentation/telemetry, prefer dropping/coalescing stale intermediate data over growing memory without bound.
When loss is not acceptable, apply backpressure or fail explicitly rather than silently corrupting order/data.
For periodic transmit and sample generation:
- avoid heap allocation in the steady-state hot loop;
- precompute invariant frame sections when practical;
- reuse packet buffers;
- bound per-cycle work;
- keep logging/diagnostics off the timing-critical path;
- separate configuration changes from the real-time send loop using safe handoff;
- measure jitter, missed deadlines, CPU load, queue depth, and sustained-rate behavior where applicable.
Never claim real-time behavior from average timing alone. Consider worst-case and sustained load.
Embedded and high-rate desktop paths must avoid unnecessary allocation churn.
Prefer:
- preallocated/reused packet buffers;
- fixed-capacity or bounded containers when maximums are known;
- spans/views instead of copies;
- incremental parsing instead of duplicated whole-message structures;
- deterministic cleanup through RAII/scoped ownership.
Use object pools only when the lifetime model is safe and profiling/measurement shows value.
Never keep raw pointers to buffers whose ownership/lifetime can end asynchronously.
Do not eagerly duplicate large captures/SCL datasets into multiple complete representations.
Prefer streaming/chunked parsing and indexed access when practical.
Validate malformed XML/SCL, missing references, inconsistent datasets, invalid addressing, unsupported editions/features, and oversized declarations.
Do not invent engineering values to compensate for missing source data. Preserve unknown/invalid states explicitly.
Every worker, socket, timer, queue, callback registration, file handle, mapping, and platform handle must have a clear owner and shutdown path.
Shutdown/reconfigure must:
- signal cancellation/stop;
- prevent new work from entering;
- drain/drop according to documented semantics;
- join/retire workers safely;
- release sockets/handles/buffers;
- prevent callbacks into destroyed objects.
Do not detach threads merely to avoid lifecycle work.
Do not cosmetically alter protocol behavior to imitate a specific vendor trace/tool.
Wire behavior must follow the implemented IEC 61850 contract, supported edition/profile, and documented project compatibility requirements.
When matching another implementation, distinguish clearly between:
- required standard behavior;
- accepted de-facto interoperability behavior;
- vendor-specific behavior;
- unsupported behavior.
Add deterministic fixtures for every compatibility quirk that is intentionally supported.
Performance-sensitive changes should identify relevant measurements, such as:
- messages/s or samples/s;
- sustained CPU load;
- allocation rate/heap high-water mark;
- queue depth/drop count;
- transmit jitter/deadline misses;
- parse throughput;
- reconnect/association latency;
- firmware task stack usage;
- desktop UI latency when applicable.
Do not claim optimization without evidence.
Prefer simpler algorithms and bounded data structures over speculative micro-optimization.
For ESP32 targets:
- no blocking network/file/console operations in timing-critical tasks;
- avoid uncontrolled heap allocation after steady state begins;
- size task stacks intentionally and monitor high-water marks;
- use bounded FreeRTOS queues/ring buffers;
- avoid priority inversion and long critical sections;
- keep ISR work minimal and defer processing safely;
- watchdog behavior must expose actual deadlock/stall problems, not be disabled as a workaround;
- reconnect/network failures must not wedge the main application loop;
- firmware loading/update paths must validate image metadata, size, target compatibility, integrity where available, and failure recovery before replacing working firmware.
Expected/recoverable failures must not use exceptions as normal control flow in protocol, parsing, generator, capture, callback, or embedded timing-critical paths. Use explicit status/result contracts such as project-defined Result<T, E>, std::expected where the configured C++ standard/library supports it, compact status enums/structs, or std::optional only when detailed failure information is unnecessary.
Normal conditions such as malformed length, unsupported tag/value, timeout, disconnect, buffer exhaustion, invalid configuration, missing optional data, or rejected frame must return a deterministic status rather than throw through the hot path.
Exceptions from host OS, filesystem/network wrappers, STL/third-party desktop libraries, or integration code may still occur. Catch them at the nearest meaningful platform/infrastructure boundary and convert them to the same structured status/error model. Never allow exception unwinding through SV/GOOSE transmit loops, receive/decode callbacks, ISR-adjacent code, or deterministic embedded tasks. Use noexcept only when every reachable operation actually honors the contract; do not add it merely to hide unsafe code.
Hot-path diagnostics must enqueue only compact structured events, for example {errorCode, subsystem, numericContext, timestamp/counter}. Do not allocate/format strings, write files, print console logs, serialize JSON, perform network telemetry, or block while reporting an error from a timing-critical path.
The diagnostic transport must be bounded and non-blocking for real-time producers. Prefer a fixed-capacity/SPSC ring buffer where the producer/consumer topology allows it, or another bounded queue with a documented overload policy. When full, aggregate/deduplicate/rate-limit repeated events, increment dropped-event counters, and preserve high-severity/latest information according to policy; never stall SV/GOOSE timing or packet processing because diagnostics are slow.
Human-readable formatting, persistence, telemetry, and GUI display belong on a lower-priority background consumer. The diagnostic subsystem is observational: its own failure must not deadlock, reset, or corrupt the protocol stack.
Do not create multiple incompatible result/error frameworks. Reuse one lightweight machine-readable error-code model across related protocol/core subsystems, with contextual formatting outside hot paths.
Every protocol bug fix should add/update a deterministic regression test whenever technically practical.
Test the exact failure mode: malformed lengths, disconnect timing, sequence/order issue, timeout, wraparound, unsupported data, buffer boundary, state transition, or platform-specific failure.
Before changing public structs/APIs, binary/wire formats, config semantics, timing defaults, or supported behavior, evaluate compatibility with existing consumers and tests.
Prefer the smallest coherent root-cause fix.
Do not:
- mix unrelated refactoring into a focused bug fix;
- duplicate parsers/state machines/services;
- rewrite a working subsystem because a local patch is difficult to understand;
- add dependencies without evaluating size, portability, security, maintenance, and runtime impact;
- add background workers/caches merely as generic performance patterns.
A task is not complete because it compiles.
Validate as applicable: BUILD
- STATIC ANALYSIS
- UNIT/DETERMINISTIC FIXTURE TESTS
- REGRESSION TEST
- MALFORMED/NEGATIVE TESTS
- TIMING/PERFORMANCE CHECK
- RESOURCE/LIFECYCLE CHECK
- DESKTOP/EMBEDDED TARGET BUILD
- SIMULATOR/LOOPBACK
- AUTHORIZED INTEROP/LAB VALIDATION
Never claim a validation step was run when it was not.
Report:
- Changed;
- Root cause;
- Architecture decision;
- Invariants preserved;
- Regression protection;
- Timing/performance impact;
- exact validation executed;
- remaining genuine limitations.
Before changing live discovery, Open SCL, SCL-assisted connect, IED-name resolution, DataSet/RCB model reconstruction, SCL type synthesis, Save SCL, or Edition conversion, read AI_READ_FIRST.md, SCL_EXPORT.md, docs/SCL_IMPORT_NORMALIZATION_PROFILE.md, and docs/SCL_EXPORT_RECONSTRUCTION_PROFILE.md.
The non-negotiable architecture is:
LIVE MMS DISCOVERY --------+
|
v
CANONICAL IED MODEL
^
|
OPEN SCL -> typed normalize-+
|
v
SHARED EDITION EXPORTERS
Rules:
- There is one semantic source of truth. Do not create separate discovered-IED and opened-SCL semantic trees, caches, resolvers, or exporters.
- Live discovery and Open SCL are different ingress adapters into the same canonical IEC 61850 semantics.
- Open SCL is a typed semantic import. The XML DOM/tree is source evidence, not the application/protocol source of truth.
- Save SCL is a local projection of the canonical model. Selecting another target edition must not trigger hidden rediscovery or require a live connection.
- Source edition and target edition are handled by typed import/export profiles. Do not implement conversion with global string replacement or schema-specific branches scattered through discovery/runtime code.
- Keep file-declared structure/configuration separate from current live runtime overlays such as values, RCB ownership, EntryID, runtime-added DataSets, and association state.
IED@nameis authoritative for an opened file. A discovery-derived IED name must be evidence-scored. SCL-assisted connect cross-checks the two and reports mismatch; it must not silently rename either model.- Preserve DataSet member order and semantic references exactly.
- Treat
Unknown,NotRepresentableInSourceProfile, andKnownFalseas different states. Never silently convert missing/unrepresentable semantics to false or a guessed default. - Source-only metadata such as original type IDs, Header/history, descriptions, topology, and private extensions belongs to provenance/source evidence; it must not become a second semantic model.
- Discovery-derived type IDs are deterministic synthetic identifiers unless independently known; never present them as recovered original vendor IDs.
- A worker/thread may provide responsiveness, progress, parsing, serialization, or validation off the UI thread. It must not be the reason identity or model semantics are correct.
- Same-edition normalized round-trip acceptance is semantic idempotence, not byte-for-byte XML equality. Cross-edition round-trip compares only the representable semantic subset and must report loss explicitly.
If a patch cannot explain how it preserves this single-model contract, stop and redesign before coding.
Think like the engineer responsible for a protocol stack and embedded device under sustained field load for years, not like a prototype generator trying to make one packet or screenshot pass.
Understand first. Bound every hot path. Make states explicit. Preserve protocol semantics. Validate malformed inputs. Measure timing. Prevent regressions.