124 lines
6.8 KiB
Markdown
124 lines
6.8 KiB
Markdown
# 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. |