fix(doors): verify and reconcile stale Open Longest door states #196
Labels
No labels
blocked
bug
enhancement
high-priority
low-priority
needs-info
needs-triage
ready-for-agent
ready-for-human
referenced
research
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
gabogg/hikcentral#196
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Production symptom
The operator reports that Doors Open the Longest includes doors shown as closed in the HikCentral UI at
10.10.1.251. This is an operationally misleading live status and needs prompt investigation.Live check (2026-09-30)
http://10.10.1.251:8888responded; authenticatedGET /api/doors/statusreturned five Open Longest entries.POST /artemis/api/resource/v1/acsDoor/acsDoorListreturned 115 doors. All five ranked door IDs (1703, 1673, 45, 1684, 1690) haddoorState: 2(open) there and in the app response. The reported UI disagreement was not independently reproduced at this snapshot.GET /ISAPI/AccessControl/Door/statusreturned HTTP 404 after a successful Bumblebee login, so it is not currently a usable second source as documented.background_monitor()callssync_doors_async()every loop andreconcile_door_states_with_upstream_async()every 60 seconds. Both read the same ArtemisacsDoorListendpoint (app/services/monitor_service.py,app/services/door_service.py). The timed reconciliation can repair missed WebSocket/webhook updates only if this endpoint has the correct current state; it cannot detect an endpoint that lags or disagrees with HikCentral's UI.Investigation required
acsDoorList, and the actual API/request that supplies HikCentral UI's live state at the same time. Redact credentials, session IDs, and personal data from artifacts.Acceptance criteria
Related: #191 covers explicitly time-limited human status records for unreliable hardware reports; it does not replace this synchronization diagnosis. Keep this bug distinct from the Supervisor feature milestone unless triage establishes it as a prerequisite.
Local reproduction of a stale-state path:
doors_updateoverview with door D open at timestamp T: Open Longest count = 1.door_transitionsclose for D at T+20: count = 0.doors_updateoverview with D still open at T: count returns to 1.The local Node harness printed
{"afterOpen":1,"afterClose":0,"afterStalePoll":1}.TelemetryEngine._ingestDoorTransitions()checks transition age before applying it (app/static/js/src/telemetry/telemetry_engine.js:697-712), but_ingestDoors()applies each subsequent overview without rejecting an older door state (:771-895). The existing test attests/frontend/test_telemetry_engine.test.js:713checks only a newer overview superseding a transition. The focused frontend test files pass, so this older-overview case lacks coverage.There is a corresponding backend risk:
sync_doors_async()accepts the latestacsDoorListstate on each poll; itslast_observed_activity > poll_start_timeguard only protects a webhook that arrived during that poll. If a webhook closes a door and a later catalog poll still says open, the later poll can reopen it. The 60-second reconciler consults the same catalog endpoint.This proves a code path that can make a closed door reappear, but does not prove it occurred for the production doors sampled earlier. A simultaneous capture of HikCentral UI status, Artemis catalog status, app API status, and browser snapshot for one affected door remains necessary to identify the production source of stale data.
Read-only agm code investigation reviewed locally. Full path: Artemis
acsDoorList→sync_doors_async()→ SQLite/in-memory doors →get_door_overview().open_longest→ HTTP/WebSocket overview →TelemetryEngine._ingestDoors()/_buildSnapshot()→CommandDeckAdapter.renderOpenLongest(). The browser recomputes the ranking from its door cache; it does not render the serveropen_longestarray directly. The previously posted 1 → 0 → 1 stale-overview reproduction is confirmed by a local Node harness. Focused frontend tests pass but do not cover this ordering case.Additional verified code risks:
door_classifier.classify_door()requiresprev_transitions == 0to classify a long-standing non-utility OPEN reading asSENSORLESS_OPEN. After recorded transitions, that branch cannot classify it sensorless;determine_door_exclusion()can clear a prior AUTO exclusion when category becomesVERIFIED_SENSOR. This may admit unreliable open circuits to the ranking, but requires physical sensor/wiring evidence for each affected door.198914(opening) only. Reconciliation queries198915(closing) only afteracsDoorListalready reports a closed state. If the catalog keeps reporting OPEN and a close webhook was missed, the event history cannot repair the state under the current logic.acsDoorList; this is a capacity risk, but not the observed installation's immediate cause because the live response contained 115 doors.Production follow-up: ranked doors sampled after the worker report were
VERIFIED_SENSORwith nonzero observed transitions, consistent with the classifier condition but insufficient to prove sensorless wiring. Direct read-only closing-event queries for door IDs 1703 and 45 over the preceding eight hours returned no198915records. The HikCentral UI's data source and physical status of these doors remain unverified. Do not treat a relay/status semantic mismatch as established without a simultaneous UI/API capture.Worker made no project code edits. I independently re-ran the two focused frontend test files successfully. Local backend test rerun was inconclusive because the available pytest command did not complete promptly; the worker reported its focused backend suite passing, but that result has not been independently reproduced here.