# TODO — implementation status Companion to `docs/IMPLEMENTATION_PLAN.md` (the authoritative plan) and `CHANGELOG.md` (what shipped). One row per plan task; updated as work lands. Legend: **[done]** complete & verified · **[partial]** some of it landed · **[todo]** not started · **[skip]** dropped by decision. ## Phase 0 — Feasibility & environment **[done]** - [done] Wrapper backend decision: OPC DA Automation Wrapper (`OPC.Automation`) → 32-bit interpreter mandatory (spec §11). - [done] Test server: Matrikon OPC Simulation Server, local ProgID `Matrikon.OPC.Simulation.1`; baseline read RTT + wrapper error shapes recorded (repo memory). - [done] Dependencies chosen for a 32-bit, no-compiler install: ruamel.yaml (PyYAML has no cp314 win32 wheels), pywin32. ## Phase 1 — Core engine **[done]** - [done] 1. `config.py` — typed YAML config + strict validation; **D16 `devices:` indicators with configurable `ok_values`**; `check-config` prints them. - [done] 2. `quality.py` — decode tables (spec §7) + `status_text(raw) -> "NAME / SUBCODE (0xNN)"` clear-text helper. - [done] 3. `opc/base.py` — client/browser protocols, `ReadResult`, `OpcError` with decoded HRESULT. - [done] 4. `opc/automation.py` — connect / browse / read (per-item `Item.Read`; `SyncRead` is unmarshallable from pywin32 — verified), per-tag errors, safe COM teardown. - [skip] Single persistent group + static items (poller creates its own per client; the throwaway-group read pattern is verified). - [done] 5. `monitor/state.py` — connection state machine, exponential backoff (1 s → 60 s cap, reset on success), reconnect accounting, D3 flapping window (pure, clock-fed). - [done] 6. `monitor/detectors.py` — D4–D16 pure detectors + rolling window: latency spike/streak/p95 (D4–D6), quality transitions/persistence/flapping (D8–D11), stale/frozen (D12–D13), mass-bad latch (D14), **D16 device-link** (`ok_values` edge-level DOWN/UP). - [done] 7. `monitor/poller.py` — coordinator + dedicated COM worker thread (client born on the worker), D7 soft-hang flag, hard-hang watchdog (abandon + rebuild), backoff between sessions. - [open] Poll source default (CACHE vs DEVICE) — needs a spec decision: Matrikon shows BAD qualities on some tags only via CACHE. - [done] 8. `run` verb — foreground monitoring loop, console reporter with clear-text status, heartbeat (D15), Ctrl+C stop (second Ctrl+C aborts). - [done] 9. `opc/fake.py` — scenario engine: scripted polls (exact results / `"good"` shorthand) and `PollScriptError` whole-call failures; hang clients are plain sleeping fakes in tests. - [open] D12 stale events re-emit every poll while a tag stays stale — the spec defines the condition per-poll; ops may want debounce/latch (decide before the 24 h soak; live sim output showed the spam clearly). ## Phase 2 — Reporters - [todo] 1. `report/jsonl.py` — rotating JSONL (size + backups), config-digest header, atomic-ish append. **The console reporter already emits the same events in `run` (Phase 1); Phase 2 swaps in the fan-out.** - [todo] 2. `report/textlog.py` — **plain-text anomaly log** (spec §9.1): `anomalies-YYYYMMDD.log`, one line per detected anomaly, clear-text status `NAME (0xNN)`, recoveries logged so it reads as an incident narrative. - [todo] 3. `report/eventlog.py` — Windows Event Log (WARN+), source registration via `install`. - [todo] 4. `report/http.py` — optional webhook, bounded queue, never blocks polling. - [todo] 5. Heartbeat assembly (D15) + reporter fan-out dispatcher. ## Phase 3 — Windows service - [todo] 1. `service.py` — `ServiceFramework` host; clean stop (`STOP_PENDING`, join-with-timeout, abandon hung COM thread). - [todo] 2. CLI verbs `install`/`uninstall`/`start`/`stop` (sc.exe semantics). - [todo] 3. SCM recovery (`sc failure ... restart`), auto-start, service account docs. - [todo] 4. `%ProgramData%\OPCDA-Diagnozer\` config/log dir conventions (frozen exe already defaults there). - [open] Frozen-exe SCM hosting: console exe must expose `ServiceMain` — one careful build pass (pywin32 `ServiceControlDispatcher`). ## Phase 4 — Integration & chaos testing - [todo] Scenario matrix vs the sim server: server kill / service stop / firewall silent drop / latency injection / device comm failure / quality oscillation / stale+frozen / mass bad / config errors / **D16: flip a device indicator (e.g. a `P500x.Connected` alias)**. - [todo] 24 h soak (AC10): no leaks, reconnect accounting correct, heartbeats every 5 min, ≤ ~50 MB RAM, ~0 % CPU idle. ## Phase 5 — Packaging & docs - [done] PyInstaller exe build (`scripts/build_exe.ps1`, onedir + onefile, read canary) — live-verified. - [done] README (install, config, DCOM checklist, HRESULT table). - [todo] Ops runbook: reading events, kind meanings (spec §12 matrix), threshold tuning, evidence locations. - [todo] 1.0.0 tag once AC1–AC10 pass. ## Housekeeping - [todo] Wire `status_text` into `read`/`probe` output once it exists (currently shows `raw_hex` + decoded name separately). - [todo] Record the CACHE-vs-DEVICE poll-source decision in the spec once made; add fake-client scripted reads for both sources to the poller tests.