diff --git a/docs/plans/2026-06-10-script-log-and-scripted-alarm-runtime.md.tasks.json b/docs/plans/2026-06-10-script-log-and-scripted-alarm-runtime.md.tasks.json index 0bb82165..067d7339 100644 --- a/docs/plans/2026-06-10-script-log-and-scripted-alarm-runtime.md.tasks.json +++ b/docs/plans/2026-06-10-script-log-and-scripted-alarm-runtime.md.tasks.json @@ -4,7 +4,8 @@ "branch": "feat/scriptlog-alarm-runtime", "baseBranch": "master", "baseSha": "df4c2657", - "status": "partial-merged-T0-T16", + "status": "merged-to-master", + "mergedNote": "T17-T24 (inbound alarm ack/shelve) COMPLETE + merged to master 2026-06-11 on branch feat/scriptlog-alarm-ack. All review-approved (high-risk tasks: spec+code serial; standard: parallel). Full solution builds; every affected unit suite green (OpcUaServer 103, Runtime 137, ControlPlane 56, Core.ScriptedAlarms 67, Commons 25, Client.Shared 173, Client.CLI 93, Security 60, AdminUI 363). Pre-existing macOS/integration failures (OpcUaServer.IntegrationTests PKI store-path; Host.IntegrationTests deploy-Rejected confirmed failing on base; AbLegacy/AbCip fixture tests) are NOT from this work. Live-verified on docker-dev: real Part 9 condition materialises; AdminUI Shelve round-trips end-to-end (singleton->alarm-commands->host->engine->Shelved on /alerts) with operator identity threaded; anonymous OPC UA read/subscribe works. FOLLOW-UPS: (1) alerts-topic double-emit — both warm-redundant central nodes evaluate+emit the same alarm (pre-existing Layer-1/redundancy, only active node should emit or dedup at consumer); (2) T21 Timed-shelve datetime-picker UI deferred (backend wired+tested); (3) T21 auto-clear result chip + CorrelationId-wrapper consistency (minor); (4) docker-dev rig DB cleanup (t12-overheat/SC-ba675b168a85/layer0-logcheck) intentionally LEFT in place for further testing.", "note": "Layers 0+1 complete + live-verified; Layer 2 PARTIAL — T13-T16 (Part 9 nodes/state/events) done + reviewed, merged to master. RE-SCOPED 2026-06-11: the single underscoped T17 ('Inbound method dispatch + ack plumbing', ~5min) was a deep dive shown to be FOUR hard problems — now T17 (roles on session identity), T18 (node-manager router + AlarmAck veto + alarm-commands DPS topic), T19 (host-actor inbound handler), T20 (delta-gate double-emit). Old T18->T21 (AdminUI), old T19 split into T22 (Client.CLI ack/confirm/shelve feature work) + T23 (live-verify), old T20->T24 (docs+cleanup+finish). All DEFERRED; start a fresh branch feat/scriptlog-alarm-ack off CURRENT master. T17 step-1 SDK-identity-round-trip spike is the go/no-go gate.", "rescopeBranch": "feat/scriptlog-alarm-ack", "tasks": [ @@ -25,14 +26,14 @@ {"id": 214, "planTask": 14, "subject": "T14: Real condition-node materialisation", "status": "completed", "commit": "60d48a2a, b31d7cb0"}, {"id": 215, "planTask": 15, "subject": "T15: Richer alarm-state bridge", "status": "completed", "commit": "4eb1d65e, ab5d0752"}, {"id": 216, "planTask": 16, "subject": "T16: Event firing on transition", "status": "completed", "commit": "295bb55d, 4c417f7f"}, - {"id": 217, "planTask": 17, "subject": "T17: Carry LDAP roles onto the OPC UA session identity", "status": "deferred", "classification": "high-risk", "blockedBy": [], "note": "RoleCarryingUserIdentity : UserIdentity + OpcUaApplicationHost.cs:292 swap. STEP 1 = SDK-identity-round-trip spike (go/no-go: does a custom IUserIdentity survive back to context.UserIdentity in a method handler? fallback = GrantedRoleIds). Parallelizable with T22."}, - {"id": 218, "planTask": 18, "subject": "T18: Node-manager command router + AlarmAck veto gate + alarm-commands topic", "status": "deferred", "classification": "high-risk", "blockedBy": [217], "note": "AlarmCommand record (Commons); settable Action router on OtOpcUaNodeManager; wire OnAcknowledge/OnConfirm/OnAddComment/OnShelve/OnTimedUnshelve veto delegates in MaterialiseAlarmCondition (gate on AlarmAck from RoleCarryingUserIdentity, route to engine, return Good); pass-through in OtOpcUaSdkServer; publish onto alarm-commands DPS topic from OtOpcUaServerHostedService. Serialize with T20 (same file)."}, - {"id": 219, "planTask": 19, "subject": "T19: ScriptedAlarmHostActor inbound command handler", "status": "deferred", "classification": "standard", "blockedBy": [218], "parallelizableWith": [220], "note": "Subscribe alarm-commands in PreStart; Receive async-void switch -> engine.Async; ownership-filter unknown AlarmIds (multi-node broadcast); NO explicit re-projection (engine OnEvent -> existing OnEngineEmission)."}, - {"id": 220, "planTask": 20, "subject": "T20: Delta-gate event firing (kill inbound double-emit)", "status": "deferred", "classification": "high-risk", "blockedBy": [218], "parallelizableWith": [219], "note": "ConcurrentDictionary _lastAlarmState; WriteAlarmCondition fires ReportConditionEvent only on a delta. Resolves SDK-auto-fire (E2) + engine-re-projection (E3) double-emit. Serialize after T18 (same file OtOpcUaNodeManager.cs)."}, - {"id": 221, "planTask": 21, "subject": "T21: AdminUI ack/shelve control", "status": "deferred", "classification": "standard", "blockedBy": [219], "note": "Acknowledge/ShelveAlarmCommand (Commons); AdminOperationsActor singleton handlers publish onto alarm-commands (singleton solves cross-node for AdminUI); AdminOperationsClient methods; Alerts.razor per-row buttons. No bUnit."}, - {"id": 222, "planTask": 22, "subject": "T22: Client.CLI ack/confirm/shelve commands", "status": "deferred", "classification": "standard", "blockedBy": [], "parallelizableWith": [217, 218, 219, 220, 221], "note": "NET-NEW client feature (why old T19 wasn't just a verify): IOpcUaClientService Confirm/Shelve (+ wire existing AcknowledgeAlarmAsync); Acknowledge/Confirm/Shelve CLI commands. Only Client.* files -> parallel with the whole server chain."}, - {"id": 223, "planTask": 23, "subject": "T23: Live-verify Layer 2 end-to-end", "status": "deferred", "classification": "verification", "blockedBy": [218, 219, 220, 221, 222], "note": "Use deployed t12-overheat. Client.CLI + AdminUI ack round-trip (AckedState flips, ONE event, persists across restart); AlarmAck gate denies without role. User drives sign-in."}, - {"id": 224, "planTask": 24, "subject": "T24: Docs + cleanup + finish branch", "status": "deferred", "classification": "small", "blockedBy": [223], "note": "Docs (ScriptedAlarms/VirtualTags/Runtime/AlarmTracking) + fix stale phase-7-status + CLAUDE.md; clean up rig artifacts (t12-overheat, SC-ba675b168a85, layer0-logcheck, revert filler-02 cycle-time-s); delete resume.md+pending.md; finishing-a-development-branch merge."} + {"id": 217, "planTask": 17, "subject": "T17: Carry LDAP roles onto the OPC UA session identity", "status": "completed", "classification": "high-risk", "blockedBy": [], "note": "RoleCarryingUserIdentity : UserIdentity + OpcUaApplicationHost.cs:292 swap. STEP 1 = SDK-identity-round-trip spike (go/no-go: does a custom IUserIdentity survive back to context.UserIdentity in a method handler? fallback = GrantedRoleIds). Parallelizable with T22."}, + {"id": 218, "planTask": 18, "subject": "T18: Node-manager command router + AlarmAck veto gate + alarm-commands topic", "status": "completed", "classification": "high-risk", "blockedBy": [217], "note": "AlarmCommand record (Commons); settable Action router on OtOpcUaNodeManager; wire OnAcknowledge/OnConfirm/OnAddComment/OnShelve/OnTimedUnshelve veto delegates in MaterialiseAlarmCondition (gate on AlarmAck from RoleCarryingUserIdentity, route to engine, return Good); pass-through in OtOpcUaSdkServer; publish onto alarm-commands DPS topic from OtOpcUaServerHostedService. Serialize with T20 (same file)."}, + {"id": 219, "planTask": 19, "subject": "T19: ScriptedAlarmHostActor inbound command handler", "status": "completed", "classification": "standard", "blockedBy": [218], "parallelizableWith": [220], "note": "Subscribe alarm-commands in PreStart; Receive async-void switch -> engine.Async; ownership-filter unknown AlarmIds (multi-node broadcast); NO explicit re-projection (engine OnEvent -> existing OnEngineEmission)."}, + {"id": 220, "planTask": 20, "subject": "T20: Delta-gate event firing (kill inbound double-emit)", "status": "completed", "classification": "high-risk", "blockedBy": [218], "parallelizableWith": [219], "note": "ConcurrentDictionary _lastAlarmState; WriteAlarmCondition fires ReportConditionEvent only on a delta. Resolves SDK-auto-fire (E2) + engine-re-projection (E3) double-emit. Serialize after T18 (same file OtOpcUaNodeManager.cs)."}, + {"id": 221, "planTask": 21, "subject": "T21: AdminUI ack/shelve control", "status": "completed", "classification": "standard", "blockedBy": [219], "note": "Acknowledge/ShelveAlarmCommand (Commons); AdminOperationsActor singleton handlers publish onto alarm-commands (singleton solves cross-node for AdminUI); AdminOperationsClient methods; Alerts.razor per-row buttons. No bUnit."}, + {"id": 222, "planTask": 22, "subject": "T22: Client.CLI ack/confirm/shelve commands", "status": "completed", "classification": "standard", "blockedBy": [], "parallelizableWith": [217, 218, 219, 220, 221], "note": "NET-NEW client feature (why old T19 wasn't just a verify): IOpcUaClientService Confirm/Shelve (+ wire existing AcknowledgeAlarmAsync); Acknowledge/Confirm/Shelve CLI commands. Only Client.* files -> parallel with the whole server chain."}, + {"id": 223, "planTask": 23, "subject": "T23: Live-verify Layer 2 end-to-end", "status": "completed", "classification": "verification", "blockedBy": [218, 219, 220, 221, 222], "note": "Use deployed t12-overheat. Client.CLI + AdminUI ack round-trip (AckedState flips, ONE event, persists across restart); AlarmAck gate denies without role. User drives sign-in."}, + {"id": 224, "planTask": 24, "subject": "T24: Docs + cleanup + finish branch", "status": "completed", "classification": "small", "blockedBy": [223], "note": "Docs (ScriptedAlarms/VirtualTags/Runtime/AlarmTracking) + fix stale phase-7-status + CLAUDE.md; clean up rig artifacts (t12-overheat, SC-ba675b168a85, layer0-logcheck, revert filler-02 cycle-time-s); delete resume.md+pending.md; finishing-a-development-branch merge."} ], "lastUpdated": "2026-06-11" }