Files
2026-10-08 11:01:03 +02:00

124 lines
6.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 **[done]**
- [done] 1. `report/jsonl.py` — rotating JSONL (size + backups), config-digest
header line, single-line atomic-ish appends. Live-verified.
- [done] 2. `report/textlog.py` — plain-text anomaly log (spec §9.1):
`anomalies-YYYYMMDD.log`, one line per detected anomaly, clear-text status
`NAME / SUB (0xNN)`, recoveries logged (incident narrative), per-day files,
shared rotation. Live-verified.
- [done] 3. `report/eventlog.py` — Windows Event Log (WARN+), severity-mapped
via a RegisterEventSource handle + positional ReportEvent (keyword args
raise TypeError — found live). Source message-file registration lands with
`install` (Phase 3). Live-verified delivery.
- [done] 4. `report/http.py` — optional webhook (`report.http` config),
bounded queue + daemon sender, never blocks the poll loop; failures counted
and dropped, injectable `post` for tests.
- [done] 5. Heartbeat assembly (D15, Phase 1) + reporter fan-out dispatcher
(`report/base.py`): per-sink failure isolation, best-effort close. The
`run` verb wires console + JSONL + textlog + eventlog (+http when set).
## Phase 3 — Windows service **[partial]**
- [done] 1. `service.py` — `ServiceFramework` host (monitor thread +
poller, `PYS_SERVICE_STARTED/STOPPED` lifecycle entries); clean stop
(`STOP_PENDING`, 30 s join-with-timeout, abandon hung COM thread,
never joined — verified in-process via the pywin32 debug host against
the fake client: start → poll → stop in 0.1 s).
- [done] 2. CLI verbs `install`/`uninstall`/`start`/`stop`/`status` —
actionable error mapping (elevation / not-installed / not-running
hints), install validates the config BEFORE touching the SCM, and
prints the recorded config path + auto-start/recovery summary.
- [done] 3. SCM recovery (`sc failure` semantics) wired at install:
restart on first+second failure (5 s delay, 24 h counter reset) via
`ChangeServiceConfig2(SERVICE_CONFIG_FAILURE_ACTIONS)` — dict shape
verified against pywin32's SWIG parser; auto-start
(`SERVICE_AUTO_START`); service-account docs in README (LocalSystem
for local servers; `--account`/`--password` for remote DCOM).
- [done] 4. `%ProgramData%\OPCDA-Diagnozer\` conventions documented
(README deployment layout); config path recorded in the service's
registry `Parameters` at install so startup never depends on the
service CWD; `install` also registers the Event Log source with its
message file (the eventlog reporter's deferred step).
- [open] Frozen-exe SCM hosting: the exe self-hosts (`exeArgs=service`,
argv+CWD detection, `Initialize`+`PrepareToHostSingle`+
`StartServiceCtrlDispatcher`); hidden imports added to the build
script. Needs ONE careful elevated live pass (install → start →
kill-server → stop → reboot) against the built exe — terminal here
is not elevated; the in-process debug-host run is verified.
## 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.