docs(plans): bookkeeping sync — reconcile stale trackers with merged code
A verified audit of all ~90 plan documents (2026-08-01) found ~30 .tasks.json
trackers and several plan headers still reporting 'pending'/'draft' for work
fully merged to main. Sync them so future audits don't re-litigate closed work:
- Flip ~380 stale task statuses to completed across March/May/June/July
trackers (audit-log series, milestones M5-M10, playwright waves, stillpending,
LocalDb, ClusterClient->gRPC DoD rows, and more), each verified against
code/git evidence before flipping.
- Annotate obsolete-not-done rows: ClusterClient CLI transport (never built,
HTTP shipped), TreeView Areas/Instances pages (replaced by Topology),
template-tree drag-drop (dropped for M9 menu reorder), otopcua item C
(premise superseded by #17).
- Flip stale headers: aggregated-live-alarm + kpi-rollups 'Draft not executed'
-> Delivered 2026-07-10; otopcua cutover-scope SCOPING -> DECIDED;
scadabridge-rename -> Implemented; LocalDb phase1/2 status strings ->
merged 28ca04d7.
- Fix doc drift: T9/T10 'deferred' -> shipped as SMS (Teams dropped); waitfor
sandbox follow-up shipped; followups #52/#53/#54/#162/#207 resolved; purge
TODO closed by PendingDeploymentPurgeActor; live-gate pre-existing failures
#28/#29/#31 fixed; auto-down boot-alone residual superseded by self-first
seeds; supersession banners on keep-oldest SBR + ClusterClient-era designs;
requirements-traceability 'Pending' clarified as frozen plan-generation
status.
Deliberately left pending (genuinely open, tracked in the pending-work list):
opcua-tag-browser task 19 (live smoke), ipsen tasks 7-8 (vd03 verification),
selfform task 7 (vd03 overlay, user-held), live-gate observation 1
(external-system delete orphan bug), otopcua item A + maxDepth calibration.
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
**Date**: 2026-03-16
|
||||
**Component**: Cluster Infrastructure (`docs/requirements/Component-ClusterInfrastructure.md`)
|
||||
**Status**: Approved
|
||||
**Status**: Approved — superseded in part: the keep-oldest SBR decision was replaced by the auto-down decision 2026-07-21 (`docs/plans/2026-07-21-auto-down-availability-decision.md`).
|
||||
|
||||
## Problem
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
**Date**: 2026-03-16
|
||||
**Component**: Central–Site Communication (`docs/requirements/Component-Communication.md`)
|
||||
**Status**: Approved
|
||||
**Status**: Approved — transport decisions superseded by the ClusterClient→gRPC migration (`docs/plans/2026-07-22-clusterclient-to-grpc-plan.md`); the "no buffering at central" decision still stands.
|
||||
|
||||
## Problem
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
{
|
||||
"planPath": "docs/plans/2026-03-17-deploy-artifacts-remove-configdb.md",
|
||||
"status": "2026-08-01 bookkeeping sync: statuses reconciled against merged code",
|
||||
"tasks": [
|
||||
{"id": 4, "subject": "Task 1: Create SiteExternalSystemRepository", "status": "pending"},
|
||||
{"id": 5, "subject": "Task 2: Create SiteNotificationRepository", "status": "pending"},
|
||||
{"id": 6, "subject": "Task 3: Add data connections to DeployArtifactsCommand", "status": "pending"},
|
||||
{"id": 7, "subject": "Task 4: Wire site-local repositories into DI", "status": "pending", "blockedBy": [4, 5, 6]},
|
||||
{"id": 8, "subject": "Task 5: Add Deploy Artifacts button to Sites admin page", "status": "pending", "blockedBy": [9]},
|
||||
{"id": 9, "subject": "Task 6: Update ArtifactDeploymentService to include all artifact types", "status": "pending"},
|
||||
{"id": 10, "subject": "Task 7: End-to-end test", "status": "pending", "blockedBy": [7, 8, 9]}
|
||||
{"id": 4, "subject": "Task 1: Create SiteExternalSystemRepository", "status": "completed"},
|
||||
{"id": 5, "subject": "Task 2: Create SiteNotificationRepository", "status": "completed", "note": "Shipped as planned, then deliberately removed 2026-07-10 (arch-review 08 §1.3 — excise vestigial site notification surface; notification delivery is central-only). The notification_lists / smtp_configurations tables survive on the site as deliberately-empty, unregistered (non-replicated) tables."},
|
||||
{"id": 6, "subject": "Task 3: Add data connections to DeployArtifactsCommand", "status": "completed"},
|
||||
{"id": 7, "subject": "Task 4: Wire site-local repositories into DI", "status": "completed", "blockedBy": [4, 5, 6]},
|
||||
{"id": 8, "subject": "Task 5: Add Deploy Artifacts button to Sites admin page", "status": "completed", "blockedBy": [9]},
|
||||
{"id": 9, "subject": "Task 6: Update ArtifactDeploymentService to include all artifact types", "status": "completed"},
|
||||
{"id": 10, "subject": "Task 7: End-to-end test", "status": "completed", "blockedBy": [7, 8, 9]}
|
||||
],
|
||||
"lastUpdated": "2026-03-17T16:40:00Z"
|
||||
"lastUpdated": "2026-08-01T00:00:00Z"
|
||||
}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
{
|
||||
"planPath": "docs/plans/2026-03-17-management-service-cli.md",
|
||||
"status": "2026-08-01 bookkeeping sync: statuses reconciled against merged code",
|
||||
"tasks": [
|
||||
{"id": 11, "subject": "Task 1: Create ManagementService project and test project", "status": "pending"},
|
||||
{"id": 12, "subject": "Task 2: Define management message contracts in Commons", "status": "pending"},
|
||||
{"id": 13, "subject": "Task 3: Implement ManagementActor", "status": "pending", "blockedBy": [11, 12]},
|
||||
{"id": 14, "subject": "Task 4: Register ManagementActor on Central with ClusterClientReceptionist", "status": "pending", "blockedBy": [13]},
|
||||
{"id": 15, "subject": "Task 5: Create CLI project with ClusterClient scaffolding", "status": "pending", "blockedBy": [12]},
|
||||
{"id": 16, "subject": "Task 6: Implement CLI command groups", "status": "pending", "blockedBy": [15]},
|
||||
{"id": 17, "subject": "Task 7: Write ManagementActor unit tests", "status": "pending", "blockedBy": [13]},
|
||||
{"id": 18, "subject": "Task 8: End-to-end integration test", "status": "pending", "blockedBy": [14, 16, 17]}
|
||||
{"id": 11, "subject": "Task 1: Create ManagementService project and test project", "status": "completed"},
|
||||
{"id": 12, "subject": "Task 2: Define management message contracts in Commons", "status": "completed"},
|
||||
{"id": 13, "subject": "Task 3: Implement ManagementActor", "status": "completed", "blockedBy": [11, 12]},
|
||||
{"id": 14, "subject": "Task 4: Register ManagementActor on Central with ClusterClientReceptionist", "status": "completed", "blockedBy": [13], "note": "OBSOLETE — never built; CLI shipped HTTP-only (ManagementHttpClient), receptionist deleted in the 2026-07-22 gRPC migration. Marked completed only to close the tracker."},
|
||||
{"id": 15, "subject": "Task 5: Create CLI project with ClusterClient scaffolding", "status": "completed", "blockedBy": [12], "note": "OBSOLETE — never built; CLI shipped HTTP-only (ManagementHttpClient), receptionist deleted in the 2026-07-22 gRPC migration. The CLI project itself exists; only the ClusterClient scaffolding is obsolete."},
|
||||
{"id": 16, "subject": "Task 6: Implement CLI command groups", "status": "completed", "blockedBy": [15]},
|
||||
{"id": 17, "subject": "Task 7: Write ManagementActor unit tests", "status": "completed", "blockedBy": [13]},
|
||||
{"id": 18, "subject": "Task 8: End-to-end integration test", "status": "completed", "blockedBy": [14, 16, 17]}
|
||||
],
|
||||
"lastUpdated": "2026-03-17T17:00:00Z"
|
||||
"lastUpdated": "2026-08-01T00:00:00Z"
|
||||
}
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
{
|
||||
"planPath": "docs/plans/2026-03-21-grpc-streaming-channel.md",
|
||||
"status": "2026-08-01 bookkeeping sync: statuses reconciled against merged code — feature shipped and later extended by the full ClusterClient→gRPC migration (docs/plans/2026-07-22-clusterclient-to-grpc-plan.md)",
|
||||
"tasks": [
|
||||
{"id": 0, "taskId": "1", "subject": "Task 0: Proto Definition & Stub Generation", "status": "pending"},
|
||||
{"id": 1, "taskId": "2", "subject": "Task 1: Site Config — GrpcPort in NodeOptions", "status": "pending", "blockedBy": [0]},
|
||||
{"id": 2, "taskId": "3", "subject": "Task 2: Site Entity — gRPC Address Fields", "status": "pending", "blockedBy": [0]},
|
||||
{"id": 3, "taskId": "4", "subject": "Task 3: Site-Side gRPC Server — StreamRelayActor", "status": "pending", "blockedBy": [0]},
|
||||
{"id": 4, "taskId": "5", "subject": "Task 4: Site-Side gRPC Server — SiteStreamGrpcServer", "status": "pending", "blockedBy": [3]},
|
||||
{"id": 5, "taskId": "6", "subject": "Task 5: Switch Site Host to WebApplicationBuilder + gRPC", "status": "pending", "blockedBy": [4]},
|
||||
{"id": 6, "taskId": "7", "subject": "Task 6: Central-Side gRPC Client", "status": "pending", "blockedBy": [0]},
|
||||
{"id": 7, "taskId": "8", "subject": "Task 7: Update DebugStreamBridgeActor to Use gRPC", "status": "pending", "blockedBy": [6, 5]},
|
||||
{"id": 8, "taskId": "9", "subject": "Task 8: Remove ClusterClient Streaming Path", "status": "pending", "blockedBy": [7]},
|
||||
{"id": 9, "taskId": "10", "subject": "Task 9: Docker & End-to-End Integration Test", "status": "pending", "blockedBy": [5, 1, 2]},
|
||||
{"id": 10, "taskId": "11", "subject": "Task 10: Documentation Updates", "status": "pending", "blockedBy": [9]},
|
||||
{"id": 11, "taskId": "12", "subject": "Task 11: Final Guardrail Tests", "status": "pending", "blockedBy": [9]}
|
||||
{"id": 0, "taskId": "1", "subject": "Task 0: Proto Definition & Stub Generation", "status": "completed"},
|
||||
{"id": 1, "taskId": "2", "subject": "Task 1: Site Config — GrpcPort in NodeOptions", "status": "completed", "blockedBy": [0]},
|
||||
{"id": 2, "taskId": "3", "subject": "Task 2: Site Entity — gRPC Address Fields", "status": "completed", "blockedBy": [0]},
|
||||
{"id": 3, "taskId": "4", "subject": "Task 3: Site-Side gRPC Server — StreamRelayActor", "status": "completed", "blockedBy": [0]},
|
||||
{"id": 4, "taskId": "5", "subject": "Task 4: Site-Side gRPC Server — SiteStreamGrpcServer", "status": "completed", "blockedBy": [3]},
|
||||
{"id": 5, "taskId": "6", "subject": "Task 5: Switch Site Host to WebApplicationBuilder + gRPC", "status": "completed", "blockedBy": [4]},
|
||||
{"id": 6, "taskId": "7", "subject": "Task 6: Central-Side gRPC Client", "status": "completed", "blockedBy": [0]},
|
||||
{"id": 7, "taskId": "8", "subject": "Task 7: Update DebugStreamBridgeActor to Use gRPC", "status": "completed", "blockedBy": [6, 5]},
|
||||
{"id": 8, "taskId": "9", "subject": "Task 8: Remove ClusterClient Streaming Path", "status": "completed", "blockedBy": [7]},
|
||||
{"id": 9, "taskId": "10", "subject": "Task 9: Docker & End-to-End Integration Test", "status": "completed", "blockedBy": [5, 1, 2]},
|
||||
{"id": 10, "taskId": "11", "subject": "Task 10: Documentation Updates", "status": "completed", "blockedBy": [9]},
|
||||
{"id": 11, "taskId": "12", "subject": "Task 11: Final Guardrail Tests", "status": "completed", "blockedBy": [9]}
|
||||
],
|
||||
"lastUpdated": "2026-03-21T14:15:00Z"
|
||||
"lastUpdated": "2026-08-01T00:00:00Z"
|
||||
}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
{
|
||||
"planPath": "docs/plans/2026-03-22-primary-backup-data-connections.md",
|
||||
"status": "2026-08-01 bookkeeping sync: statuses reconciled against merged code — shipped (EF migration AddPrimaryBackupDataConnections; failover coverage in DataConnectionActorTests)",
|
||||
"tasks": [
|
||||
{"id": 1, "subject": "Task 1: Entity Model & Database Migration", "status": "pending"},
|
||||
{"id": 2, "subject": "Task 2: Update CreateConnectionCommand & Manager Actor", "status": "pending", "blockedBy": [1]},
|
||||
{"id": 3, "subject": "Task 3: DataConnectionActor Failover State Machine", "status": "pending", "blockedBy": [1, 2]},
|
||||
{"id": 4, "subject": "Task 4: Failover Tests", "status": "pending", "blockedBy": [3]},
|
||||
{"id": 5, "subject": "Task 5: Health Reporting & Site Event Logging", "status": "pending", "blockedBy": [3]},
|
||||
{"id": 6, "subject": "Task 6: Central UI Changes", "status": "pending", "blockedBy": [1]},
|
||||
{"id": 7, "subject": "Task 7: CLI, Management API, and Deployment", "status": "pending", "blockedBy": [1]},
|
||||
{"id": 8, "subject": "Task 8: Documentation Updates", "status": "pending", "blockedBy": [3]}
|
||||
{"id": 1, "subject": "Task 1: Entity Model & Database Migration", "status": "completed", "note": "EF migration AddPrimaryBackupDataConnections."},
|
||||
{"id": 2, "subject": "Task 2: Update CreateConnectionCommand & Manager Actor", "status": "completed", "blockedBy": [1]},
|
||||
{"id": 3, "subject": "Task 3: DataConnectionActor Failover State Machine", "status": "completed", "blockedBy": [1, 2]},
|
||||
{"id": 4, "subject": "Task 4: Failover Tests", "status": "completed", "blockedBy": [3], "note": "Covered by DataConnectionActorTests."},
|
||||
{"id": 5, "subject": "Task 5: Health Reporting & Site Event Logging", "status": "completed", "blockedBy": [3]},
|
||||
{"id": 6, "subject": "Task 6: Central UI Changes", "status": "completed", "blockedBy": [1]},
|
||||
{"id": 7, "subject": "Task 7: CLI, Management API, and Deployment", "status": "completed", "blockedBy": [1]},
|
||||
{"id": 8, "subject": "Task 8: Documentation Updates", "status": "completed", "blockedBy": [3]}
|
||||
],
|
||||
"lastUpdated": "2026-03-22T12:00:00Z"
|
||||
"lastUpdated": "2026-08-01T00:00:00Z"
|
||||
}
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
{
|
||||
"planPath": "docs/plans/2026-03-23-treeview-component.md",
|
||||
"status": "2026-08-01 bookkeeping sync: statuses reconciled against merged code",
|
||||
"tasks": [
|
||||
{"id": 22, "subject": "Task 1: Create TreeView.razor — Core Rendering (R1-R4, R14)", "status": "pending"},
|
||||
{"id": 23, "subject": "Task 2: Add Selection Support (R5)", "status": "pending", "blockedBy": [22]},
|
||||
{"id": 24, "subject": "Task 3: Add Session Storage Persistence (R11)", "status": "pending", "blockedBy": [23]},
|
||||
{"id": 25, "subject": "Task 4: Add ExpandAll, CollapseAll, RevealNode (R12, R13)", "status": "pending", "blockedBy": [24]},
|
||||
{"id": 26, "subject": "Task 5: Add Context Menu (R15)", "status": "pending", "blockedBy": [25]},
|
||||
{"id": 27, "subject": "Task 6: Add External Filtering Tests (R8)", "status": "pending", "blockedBy": [26]},
|
||||
{"id": 28, "subject": "Task 7: Integrate TreeView into Data Connections Page", "status": "pending", "blockedBy": [27]},
|
||||
{"id": 29, "subject": "Task 8: Integrate TreeView into Areas Page", "status": "pending", "blockedBy": [27]},
|
||||
{"id": 30, "subject": "Task 9: Integrate TreeView into Instances Page", "status": "pending", "blockedBy": [27]},
|
||||
{"id": 31, "subject": "Task 10: Full Build Verification", "status": "pending", "blockedBy": [28, 29, 30]}
|
||||
{"id": 22, "subject": "Task 1: Create TreeView.razor — Core Rendering (R1-R4, R14)", "status": "completed"},
|
||||
{"id": 23, "subject": "Task 2: Add Selection Support (R5)", "status": "completed", "blockedBy": [22]},
|
||||
{"id": 24, "subject": "Task 3: Add Session Storage Persistence (R11)", "status": "completed", "blockedBy": [23]},
|
||||
{"id": 25, "subject": "Task 4: Add ExpandAll, CollapseAll, RevealNode (R12, R13)", "status": "completed", "blockedBy": [24]},
|
||||
{"id": 26, "subject": "Task 5: Add Context Menu (R15)", "status": "completed", "blockedBy": [25]},
|
||||
{"id": 27, "subject": "Task 6: Add External Filtering Tests (R8)", "status": "completed", "blockedBy": [26]},
|
||||
{"id": 28, "subject": "Task 7: Integrate TreeView into Data Connections Page", "status": "completed", "blockedBy": [27]},
|
||||
{"id": 29, "subject": "Task 8: Integrate TreeView into Areas Page", "status": "completed", "blockedBy": [27], "note": "OBSOLETE — those pages never existed; Site/Area/Instance render in one tree on Pages/Deployment/Topology.razor (2026-05-11 topology design). Marked completed only to close the tracker."},
|
||||
{"id": 30, "subject": "Task 9: Integrate TreeView into Instances Page", "status": "completed", "blockedBy": [27], "note": "OBSOLETE — those pages never existed; Site/Area/Instance render in one tree on Pages/Deployment/Topology.razor (2026-05-11 topology design). Marked completed only to close the tracker."},
|
||||
{"id": 31, "subject": "Task 10: Full Build Verification", "status": "completed", "blockedBy": [28, 29, 30]}
|
||||
],
|
||||
"lastUpdated": "2026-03-23T00:00:00Z"
|
||||
"lastUpdated": "2026-08-01T00:00:00Z"
|
||||
}
|
||||
|
||||
@@ -1,29 +1,30 @@
|
||||
{
|
||||
"planPath": "docs/plans/2026-05-11-templates-folder-hierarchy.md",
|
||||
"tasks": [
|
||||
{"id": 7, "subject": "Task 0: Confirm baseline + create work branch", "status": "pending"},
|
||||
{"id": 8, "subject": "Task 1: Add TemplateFolder entity + Template.FolderId", "status": "pending", "blockedBy": [7]},
|
||||
{"id": 9, "subject": "Task 2: EF configuration for TemplateFolder + Template.FolderId", "status": "pending", "blockedBy": [8]},
|
||||
{"id": 10, "subject": "Task 3: Generate EF migration AddTemplateFolders", "status": "pending", "blockedBy": [9]},
|
||||
{"id": 11, "subject": "Task 4: Repository methods for TemplateFolder", "status": "pending", "blockedBy": [10]},
|
||||
{"id": 12, "subject": "Task 5: TemplateFolderService.CreateFolderAsync (TDD)", "status": "pending", "blockedBy": [11]},
|
||||
{"id": 13, "subject": "Task 6: TemplateFolderService.RenameFolderAsync", "status": "pending", "blockedBy": [12]},
|
||||
{"id": 14, "subject": "Task 7: TemplateFolderService.MoveFolderAsync with cycle detection", "status": "pending", "blockedBy": [13]},
|
||||
{"id": 15, "subject": "Task 8: TemplateFolderService.DeleteFolderAsync (non-empty check)", "status": "pending", "blockedBy": [14]},
|
||||
{"id": 16, "subject": "Task 9: TemplateService.MoveTemplateAsync", "status": "pending", "blockedBy": [11]},
|
||||
{"id": 17, "subject": "Task 10: DI registration for TemplateFolderService", "status": "pending", "blockedBy": [15, 16]},
|
||||
{"id": 18, "subject": "Task 11: Management command records for TemplateFolder", "status": "pending", "blockedBy": [17]},
|
||||
{"id": 19, "subject": "Task 12: ManagementActor authorization + handlers", "status": "pending", "blockedBy": [18]},
|
||||
{"id": 20, "subject": "Task 13: Templates.razor — load folders alongside templates", "status": "pending", "blockedBy": [17]},
|
||||
{"id": 21, "subject": "Task 14: Build new TmplNode tree model", "status": "pending", "blockedBy": [20]},
|
||||
{"id": 22, "subject": "Task 15: Split-pane layout + new TreeView wiring", "status": "pending", "blockedBy": [21]},
|
||||
{"id": 23, "subject": "Task 16: Per-kind context menus", "status": "pending", "blockedBy": [22]},
|
||||
{"id": 24, "subject": "Task 17: New-folder, new-template, move-template dialogs", "status": "pending", "blockedBy": [23]},
|
||||
{"id": 25, "subject": "Task 18: Drag-drop reorganization", "status": "pending", "blockedBy": [24]},
|
||||
{"id": 26, "subject": "Task 19: Deep-link reveal on load", "status": "pending", "blockedBy": [22]},
|
||||
{"id": 27, "subject": "Task 20: bUnit tests for the new page", "status": "pending", "blockedBy": [22]},
|
||||
{"id": 28, "subject": "Task 21: Documentation updates", "status": "pending", "blockedBy": [25, 26, 27]},
|
||||
{"id": 29, "subject": "Task 22: Final smoke + green-suite check", "status": "pending", "blockedBy": [25, 26, 27, 28]}
|
||||
{"id": 7, "subject": "Task 0: Confirm baseline + create work branch", "status": "completed"},
|
||||
{"id": 8, "subject": "Task 1: Add TemplateFolder entity + Template.FolderId", "status": "completed", "blockedBy": [7]},
|
||||
{"id": 9, "subject": "Task 2: EF configuration for TemplateFolder + Template.FolderId", "status": "completed", "blockedBy": [8]},
|
||||
{"id": 10, "subject": "Task 3: Generate EF migration AddTemplateFolders", "status": "completed", "blockedBy": [9]},
|
||||
{"id": 11, "subject": "Task 4: Repository methods for TemplateFolder", "status": "completed", "blockedBy": [10]},
|
||||
{"id": 12, "subject": "Task 5: TemplateFolderService.CreateFolderAsync (TDD)", "status": "completed", "blockedBy": [11]},
|
||||
{"id": 13, "subject": "Task 6: TemplateFolderService.RenameFolderAsync", "status": "completed", "blockedBy": [12]},
|
||||
{"id": 14, "subject": "Task 7: TemplateFolderService.MoveFolderAsync with cycle detection", "status": "completed", "blockedBy": [13]},
|
||||
{"id": 15, "subject": "Task 8: TemplateFolderService.DeleteFolderAsync (non-empty check)", "status": "completed", "blockedBy": [14]},
|
||||
{"id": 16, "subject": "Task 9: TemplateService.MoveTemplateAsync", "status": "completed", "blockedBy": [11]},
|
||||
{"id": 17, "subject": "Task 10: DI registration for TemplateFolderService", "status": "completed", "blockedBy": [15, 16]},
|
||||
{"id": 18, "subject": "Task 11: Management command records for TemplateFolder", "status": "completed", "blockedBy": [17]},
|
||||
{"id": 19, "subject": "Task 12: ManagementActor authorization + handlers", "status": "completed", "blockedBy": [18]},
|
||||
{"id": 20, "subject": "Task 13: Templates.razor — load folders alongside templates", "status": "completed", "blockedBy": [17]},
|
||||
{"id": 21, "subject": "Task 14: Build new TmplNode tree model", "status": "completed", "blockedBy": [20]},
|
||||
{"id": 22, "subject": "Task 15: Split-pane layout + new TreeView wiring", "status": "completed", "blockedBy": [21]},
|
||||
{"id": 23, "subject": "Task 16: Per-kind context menus", "status": "completed", "blockedBy": [22]},
|
||||
{"id": 24, "subject": "Task 17: New-folder, new-template, move-template dialogs", "status": "completed", "blockedBy": [23]},
|
||||
{"id": 25, "subject": "Task 18: Drag-drop reorganization", "status": "completed", "blockedBy": [24], "notes": "DROPPED — superseded by M9 menu-based reorder (commits e3bc19c6/314c7dea) + MoveFolderDialog; no DnD anywhere in CentralUI"},
|
||||
{"id": 26, "subject": "Task 19: Deep-link reveal on load", "status": "completed", "blockedBy": [22], "notes": "OBSOLETE — /design/templates/{id} is TemplateEdit's own route; two-page split makes in-tree reveal moot"},
|
||||
{"id": 27, "subject": "Task 20: bUnit tests for the new page", "status": "completed", "blockedBy": [22]},
|
||||
{"id": 28, "subject": "Task 21: Documentation updates", "status": "completed", "blockedBy": [25, 26, 27]},
|
||||
{"id": 29, "subject": "Task 22: Final smoke + green-suite check", "status": "completed", "blockedBy": [25, 26, 27, 28]}
|
||||
],
|
||||
"lastUpdated": "2026-05-11"
|
||||
"status": "2026-08-01 bookkeeping sync: statuses reconciled against merged code",
|
||||
"lastUpdated": "2026-08-01"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# Derive-on-compose: implementation status
|
||||
|
||||
> **2026-08-01:** The "Still to verify" items below are satisfied — the
|
||||
> Phase-3 migration ships and auto-applies, and
|
||||
> `2026-05-18-contained-template-names-design.md` records it applied and
|
||||
> browser-verified on the dev cluster. The "How to resume" section is
|
||||
> historical.
|
||||
|
||||
> **For Claude resuming later:** All nine phases are implemented. This
|
||||
> file is the change-record for the work, not a plan. See the companion
|
||||
> design doc `2026-05-12-derive-on-compose-design.md` for rationale.
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
{
|
||||
"planPath": "docs/plans/2026-05-12-opcua-config-model.md",
|
||||
"tasks": [
|
||||
{"id": 45, "subject": "Task 1: Create OPC UA config POCOs + ValidationCategory.ConnectionConfig", "status": "pending"},
|
||||
{"id": 46, "subject": "Task 2: TDD failing tests for OpcUaEndpointConfigSerializer", "status": "pending", "blockedBy": [45]},
|
||||
{"id": 47, "subject": "Task 3: Implement OpcUaEndpointConfigSerializer", "status": "pending", "blockedBy": [46]},
|
||||
{"id": 48, "subject": "Task 4: TDD failing tests for OpcUaEndpointConfigValidator", "status": "pending", "blockedBy": [45]},
|
||||
{"id": 49, "subject": "Task 5: Implement OpcUaEndpointConfigValidator", "status": "pending", "blockedBy": [48]},
|
||||
{"id": 50, "subject": "Task 6: Refactor OpcUaDataConnection.ConnectAsync to use FromFlatDict", "status": "pending", "blockedBy": [47]},
|
||||
{"id": 51, "subject": "Task 7: Refactor DeploymentManagerActor.EnsureDclConnections", "status": "pending", "blockedBy": [47]},
|
||||
{"id": 52, "subject": "Task 8: TDD failing bUnit tests for OpcUaEndpointEditor", "status": "pending", "blockedBy": [45, 49]},
|
||||
{"id": 53, "subject": "Task 9: Implement OpcUaEndpointEditor.razor", "status": "pending", "blockedBy": [52]},
|
||||
{"id": 54, "subject": "Task 10: TDD failing bUnit tests for DataConnectionForm refactor", "status": "pending", "blockedBy": [47, 49]},
|
||||
{"id": 55, "subject": "Task 11: Refactor DataConnectionForm.razor", "status": "pending", "blockedBy": [53, 54]},
|
||||
{"id": 56, "subject": "Task 12: Solution build + all test suites green", "status": "pending", "blockedBy": [50, 51, 55]},
|
||||
{"id": 57, "subject": "Task 13: Docker deploy + browser smoke", "status": "pending", "blockedBy": [56]},
|
||||
{"id": 58, "subject": "Task 14: Push to origin", "status": "pending", "blockedBy": [57]}
|
||||
{"id": 45, "subject": "Task 1: Create OPC UA config POCOs + ValidationCategory.ConnectionConfig", "status": "completed"},
|
||||
{"id": 46, "subject": "Task 2: TDD failing tests for OpcUaEndpointConfigSerializer", "status": "completed", "blockedBy": [45]},
|
||||
{"id": 47, "subject": "Task 3: Implement OpcUaEndpointConfigSerializer", "status": "completed", "blockedBy": [46]},
|
||||
{"id": 48, "subject": "Task 4: TDD failing tests for OpcUaEndpointConfigValidator", "status": "completed", "blockedBy": [45]},
|
||||
{"id": 49, "subject": "Task 5: Implement OpcUaEndpointConfigValidator", "status": "completed", "blockedBy": [48]},
|
||||
{"id": 50, "subject": "Task 6: Refactor OpcUaDataConnection.ConnectAsync to use FromFlatDict", "status": "completed", "blockedBy": [47]},
|
||||
{"id": 51, "subject": "Task 7: Refactor DeploymentManagerActor.EnsureDclConnections", "status": "completed", "blockedBy": [47]},
|
||||
{"id": 52, "subject": "Task 8: TDD failing bUnit tests for OpcUaEndpointEditor", "status": "completed", "blockedBy": [45, 49]},
|
||||
{"id": 53, "subject": "Task 9: Implement OpcUaEndpointEditor.razor", "status": "completed", "blockedBy": [52]},
|
||||
{"id": 54, "subject": "Task 10: TDD failing bUnit tests for DataConnectionForm refactor", "status": "completed", "blockedBy": [47, 49]},
|
||||
{"id": 55, "subject": "Task 11: Refactor DataConnectionForm.razor", "status": "completed", "blockedBy": [53, 54]},
|
||||
{"id": 56, "subject": "Task 12: Solution build + all test suites green", "status": "completed", "blockedBy": [50, 51, 55]},
|
||||
{"id": 57, "subject": "Task 13: Docker deploy + browser smoke", "status": "completed", "blockedBy": [56]},
|
||||
{"id": 58, "subject": "Task 14: Push to origin", "status": "completed", "blockedBy": [57]}
|
||||
],
|
||||
"lastUpdated": "2026-05-12T04:33:33Z"
|
||||
"status": "2026-08-01 bookkeeping sync: statuses reconciled against merged code",
|
||||
"lastUpdated": "2026-08-01"
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
**Scope:** All Razor pages, layout, and shared components in `src/ZB.MOM.WW.ScadaBridge.CentralUI`.
|
||||
**Reference pattern:** `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Admin/Sites.razor` — 2-column responsive card grid, header flex row, kebab menus, search filter, Bootstrap collapse for noisy details, `@key=` on iterated cards, "No X match the filter." and empty-state CTAs.
|
||||
|
||||
> **2026-08-01: fully superseded** — every cross-cutting recommendation was either shipped or explicitly scoped into the M10 UI/UX platform plan (`2026-06-18`) and KPI History (#26); retained for reference only.
|
||||
|
||||
## Constraints (recap)
|
||||
|
||||
- Blazor Server + Bootstrap 5 only. **No third-party component frameworks** (no MudBlazor / Radzen / Blazorise / Syncfusion).
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
{
|
||||
"planPath": "docs/plans/2026-05-16-expression-trigger.md",
|
||||
"tasks": [
|
||||
{"id": 25, "subject": "Task 1: Trigger model + codecs", "status": "pending"},
|
||||
{"id": 26, "subject": "Task 2: Runtime expression evaluation", "status": "pending", "blockedBy": [25]},
|
||||
{"id": 27, "subject": "Task 3: Trigger editor panels", "status": "pending", "blockedBy": [25]},
|
||||
{"id": 28, "subject": "Task 4: Pre-deployment validation", "status": "pending", "blockedBy": [25, 26]},
|
||||
{"id": 29, "subject": "Task 5: Build, deploy, verify", "status": "pending", "blockedBy": [25, 26, 27, 28]}
|
||||
{"id": 25, "subject": "Task 1: Trigger model + codecs", "status": "completed"},
|
||||
{"id": 26, "subject": "Task 2: Runtime expression evaluation", "status": "completed", "blockedBy": [25]},
|
||||
{"id": 27, "subject": "Task 3: Trigger editor panels", "status": "completed", "blockedBy": [25]},
|
||||
{"id": 28, "subject": "Task 4: Pre-deployment validation", "status": "completed", "blockedBy": [25, 26]},
|
||||
{"id": 29, "subject": "Task 5: Build, deploy, verify", "status": "completed", "blockedBy": [25, 26, 27, 28]}
|
||||
],
|
||||
"lastUpdated": "2026-05-16"
|
||||
"status": "2026-08-01 bookkeeping sync: statuses reconciled against merged code",
|
||||
"lastUpdated": "2026-08-01"
|
||||
}
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
{
|
||||
"planPath": "docs/plans/2026-05-18-notification-outbox.md",
|
||||
"tasks": [
|
||||
{"id": 7, "subject": "Task 1: Create Component-NotificationOutbox.md", "status": "pending"},
|
||||
{"id": 8, "subject": "Task 2: Revise Component-NotificationService.md", "status": "pending", "blockedBy": [7]},
|
||||
{"id": 9, "subject": "Task 3: Revise Component-StoreAndForward.md", "status": "pending", "blockedBy": [7]},
|
||||
{"id": 10, "subject": "Task 4: Revise Component-HealthMonitoring.md", "status": "pending", "blockedBy": [7]},
|
||||
{"id": 11, "subject": "Task 5: Revise Component-SiteEventLogging.md", "status": "pending", "blockedBy": [7]},
|
||||
{"id": 12, "subject": "Task 6: Revise Component-Communication.md", "status": "pending", "blockedBy": [7]},
|
||||
{"id": 13, "subject": "Task 7: Revise Component-CentralUI.md", "status": "pending", "blockedBy": [7]},
|
||||
{"id": 14, "subject": "Task 8: Revise Component-ConfigurationDatabase.md and Component-Commons.md", "status": "pending", "blockedBy": [7]},
|
||||
{"id": 15, "subject": "Task 9: Update README.md", "status": "pending", "blockedBy": [7, 8, 9, 10, 11, 12, 13, 14]},
|
||||
{"id": 16, "subject": "Task 10: Update CLAUDE.md", "status": "pending", "blockedBy": [7, 8, 9, 10, 11, 12, 13, 14]},
|
||||
{"id": 17, "subject": "Task 11: Cross-reference consistency sweep", "status": "pending", "blockedBy": [7, 8, 9, 10, 11, 12, 13, 14, 15, 16]}
|
||||
{"id": 7, "subject": "Task 1: Create Component-NotificationOutbox.md", "status": "completed"},
|
||||
{"id": 8, "subject": "Task 2: Revise Component-NotificationService.md", "status": "completed", "blockedBy": [7]},
|
||||
{"id": 9, "subject": "Task 3: Revise Component-StoreAndForward.md", "status": "completed", "blockedBy": [7]},
|
||||
{"id": 10, "subject": "Task 4: Revise Component-HealthMonitoring.md", "status": "completed", "blockedBy": [7]},
|
||||
{"id": 11, "subject": "Task 5: Revise Component-SiteEventLogging.md", "status": "completed", "blockedBy": [7]},
|
||||
{"id": 12, "subject": "Task 6: Revise Component-Communication.md", "status": "completed", "blockedBy": [7]},
|
||||
{"id": 13, "subject": "Task 7: Revise Component-CentralUI.md", "status": "completed", "blockedBy": [7]},
|
||||
{"id": 14, "subject": "Task 8: Revise Component-ConfigurationDatabase.md and Component-Commons.md", "status": "completed", "blockedBy": [7]},
|
||||
{"id": 15, "subject": "Task 9: Update README.md", "status": "completed", "blockedBy": [7, 8, 9, 10, 11, 12, 13, 14]},
|
||||
{"id": 16, "subject": "Task 10: Update CLAUDE.md", "status": "completed", "blockedBy": [7, 8, 9, 10, 11, 12, 13, 14]},
|
||||
{"id": 17, "subject": "Task 11: Cross-reference consistency sweep", "status": "completed", "blockedBy": [7, 8, 9, 10, 11, 12, 13, 14, 15, 16]}
|
||||
],
|
||||
"lastUpdated": "2026-05-18"
|
||||
"status": "2026-08-01 bookkeeping sync: statuses reconciled against merged code",
|
||||
"lastUpdated": "2026-08-01"
|
||||
}
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
{
|
||||
"planPath": "docs/plans/2026-05-19-cached-call-tracking.md",
|
||||
"tasks": [
|
||||
{"id": 6, "subject": "Task 1: Create Site Call Audit component doc", "status": "pending"},
|
||||
{"id": 7, "subject": "Task 2: Add tracking contracts to Commons", "status": "pending", "blockedBy": [6]},
|
||||
{"id": 8, "subject": "Task 3: Update Store-and-Forward doc", "status": "pending", "blockedBy": [6, 7]},
|
||||
{"id": 9, "subject": "Task 4: Update External System Gateway doc", "status": "pending", "blockedBy": [6, 7]},
|
||||
{"id": 10, "subject": "Task 5: Update Site Runtime Script Runtime API", "status": "pending", "blockedBy": [6, 7]},
|
||||
{"id": 11, "subject": "Task 6: Update Communication doc", "status": "pending", "blockedBy": [6, 7]},
|
||||
{"id": 12, "subject": "Task 7: Update Configuration Database doc", "status": "pending", "blockedBy": [6, 7]},
|
||||
{"id": 13, "subject": "Task 8: Update Central UI doc", "status": "pending", "blockedBy": [6, 7]},
|
||||
{"id": 14, "subject": "Task 9: Update Health Monitoring doc", "status": "pending", "blockedBy": [6, 7]},
|
||||
{"id": 15, "subject": "Task 10: Note shared model in notification docs", "status": "pending", "blockedBy": [6, 7]},
|
||||
{"id": 16, "subject": "Task 11: Update README component table", "status": "pending", "blockedBy": [6]},
|
||||
{"id": 17, "subject": "Task 12: Update CLAUDE.md", "status": "pending", "blockedBy": [6]},
|
||||
{"id": 18, "subject": "Task 13: Final cross-reference consistency pass", "status": "pending", "blockedBy": [6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]}
|
||||
{"id": 6, "subject": "Task 1: Create Site Call Audit component doc", "status": "completed"},
|
||||
{"id": 7, "subject": "Task 2: Add tracking contracts to Commons", "status": "completed", "blockedBy": [6]},
|
||||
{"id": 8, "subject": "Task 3: Update Store-and-Forward doc", "status": "completed", "blockedBy": [6, 7]},
|
||||
{"id": 9, "subject": "Task 4: Update External System Gateway doc", "status": "completed", "blockedBy": [6, 7]},
|
||||
{"id": 10, "subject": "Task 5: Update Site Runtime Script Runtime API", "status": "completed", "blockedBy": [6, 7]},
|
||||
{"id": 11, "subject": "Task 6: Update Communication doc", "status": "completed", "blockedBy": [6, 7]},
|
||||
{"id": 12, "subject": "Task 7: Update Configuration Database doc", "status": "completed", "blockedBy": [6, 7]},
|
||||
{"id": 13, "subject": "Task 8: Update Central UI doc", "status": "completed", "blockedBy": [6, 7]},
|
||||
{"id": 14, "subject": "Task 9: Update Health Monitoring doc", "status": "completed", "blockedBy": [6, 7]},
|
||||
{"id": 15, "subject": "Task 10: Note shared model in notification docs", "status": "completed", "blockedBy": [6, 7]},
|
||||
{"id": 16, "subject": "Task 11: Update README component table", "status": "completed", "blockedBy": [6]},
|
||||
{"id": 17, "subject": "Task 12: Update CLAUDE.md", "status": "completed", "blockedBy": [6]},
|
||||
{"id": 18, "subject": "Task 13: Final cross-reference consistency pass", "status": "completed", "blockedBy": [6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]}
|
||||
],
|
||||
"lastUpdated": "2026-05-19"
|
||||
"status": "2026-08-01 bookkeeping sync: statuses reconciled against merged code",
|
||||
"lastUpdated": "2026-08-01"
|
||||
}
|
||||
|
||||
@@ -1,32 +1,33 @@
|
||||
{
|
||||
"planPath": "docs/plans/2026-05-19-notification-outbox-implementation.md",
|
||||
"tasks": [
|
||||
{"id": 18, "subject": "Task 1: Notification enums", "status": "pending"},
|
||||
{"id": 19, "subject": "Task 2: Notification entity POCO", "status": "pending", "blockedBy": [18]},
|
||||
{"id": 20, "subject": "Task 3: Type field on NotificationList", "status": "pending", "blockedBy": [19]},
|
||||
{"id": 21, "subject": "Task 4: Notification EF configuration + DbSet", "status": "pending", "blockedBy": [20]},
|
||||
{"id": 22, "subject": "Task 5: NotificationOutbox repository", "status": "pending", "blockedBy": [21]},
|
||||
{"id": 23, "subject": "Task 6: EF migration AddNotificationsTable", "status": "pending", "blockedBy": [22]},
|
||||
{"id": 24, "subject": "Task 7: Site/central notification message contracts", "status": "pending", "blockedBy": [23]},
|
||||
{"id": 25, "subject": "Task 8: Outbox query/action contracts", "status": "pending", "blockedBy": [24]},
|
||||
{"id": 26, "subject": "Task 9: Scaffold ZB.MOM.WW.ScadaBridge.NotificationOutbox project", "status": "pending", "blockedBy": [25]},
|
||||
{"id": 27, "subject": "Task 10: NotificationOutboxOptions", "status": "pending", "blockedBy": [26]},
|
||||
{"id": 28, "subject": "Task 11: Delivery adapter abstraction", "status": "pending", "blockedBy": [27]},
|
||||
{"id": 29, "subject": "Task 12: Email delivery adapter", "status": "pending", "blockedBy": [28]},
|
||||
{"id": 30, "subject": "Task 13: NotificationOutboxActor ingest", "status": "pending", "blockedBy": [29]},
|
||||
{"id": 31, "subject": "Task 14: Dispatcher loop", "status": "pending", "blockedBy": [30]},
|
||||
{"id": 32, "subject": "Task 15: Query, retry, discard, KPI handlers", "status": "pending", "blockedBy": [31]},
|
||||
{"id": 33, "subject": "Task 16: Daily purge job", "status": "pending", "blockedBy": [32]},
|
||||
{"id": 34, "subject": "Task 17: AddNotificationOutbox DI extension", "status": "pending", "blockedBy": [33]},
|
||||
{"id": 35, "subject": "Task 18: Retarget site S&F notification handler to central", "status": "pending", "blockedBy": [34]},
|
||||
{"id": 36, "subject": "Task 19: Async Notify.Send + Notify.Status", "status": "pending", "blockedBy": [35]},
|
||||
{"id": 37, "subject": "Task 20: Central ingest routing", "status": "pending", "blockedBy": [36]},
|
||||
{"id": 38, "subject": "Task 21: Host registration + appsettings", "status": "pending", "blockedBy": [37]},
|
||||
{"id": 39, "subject": "Task 22: CommunicationService outbox methods", "status": "pending", "blockedBy": [38]},
|
||||
{"id": 40, "subject": "Task 23: Notification Outbox Blazor page", "status": "pending", "blockedBy": [39]},
|
||||
{"id": 41, "subject": "Task 24: Health dashboard outbox KPI tiles", "status": "pending", "blockedBy": [40]},
|
||||
{"id": 42, "subject": "Task 25: End-to-end integration test", "status": "pending", "blockedBy": [41]},
|
||||
{"id": 43, "subject": "Task 26: Full build + suite verification", "status": "pending", "blockedBy": [42]}
|
||||
{"id": 18, "subject": "Task 1: Notification enums", "status": "completed"},
|
||||
{"id": 19, "subject": "Task 2: Notification entity POCO", "status": "completed", "blockedBy": [18]},
|
||||
{"id": 20, "subject": "Task 3: Type field on NotificationList", "status": "completed", "blockedBy": [19]},
|
||||
{"id": 21, "subject": "Task 4: Notification EF configuration + DbSet", "status": "completed", "blockedBy": [20]},
|
||||
{"id": 22, "subject": "Task 5: NotificationOutbox repository", "status": "completed", "blockedBy": [21]},
|
||||
{"id": 23, "subject": "Task 6: EF migration AddNotificationsTable", "status": "completed", "blockedBy": [22]},
|
||||
{"id": 24, "subject": "Task 7: Site/central notification message contracts", "status": "completed", "blockedBy": [23]},
|
||||
{"id": 25, "subject": "Task 8: Outbox query/action contracts", "status": "completed", "blockedBy": [24]},
|
||||
{"id": 26, "subject": "Task 9: Scaffold ZB.MOM.WW.ScadaBridge.NotificationOutbox project", "status": "completed", "blockedBy": [25]},
|
||||
{"id": 27, "subject": "Task 10: NotificationOutboxOptions", "status": "completed", "blockedBy": [26]},
|
||||
{"id": 28, "subject": "Task 11: Delivery adapter abstraction", "status": "completed", "blockedBy": [27]},
|
||||
{"id": 29, "subject": "Task 12: Email delivery adapter", "status": "completed", "blockedBy": [28]},
|
||||
{"id": 30, "subject": "Task 13: NotificationOutboxActor ingest", "status": "completed", "blockedBy": [29]},
|
||||
{"id": 31, "subject": "Task 14: Dispatcher loop", "status": "completed", "blockedBy": [30]},
|
||||
{"id": 32, "subject": "Task 15: Query, retry, discard, KPI handlers", "status": "completed", "blockedBy": [31]},
|
||||
{"id": 33, "subject": "Task 16: Daily purge job", "status": "completed", "blockedBy": [32]},
|
||||
{"id": 34, "subject": "Task 17: AddNotificationOutbox DI extension", "status": "completed", "blockedBy": [33]},
|
||||
{"id": 35, "subject": "Task 18: Retarget site S&F notification handler to central", "status": "completed", "blockedBy": [34]},
|
||||
{"id": 36, "subject": "Task 19: Async Notify.Send + Notify.Status", "status": "completed", "blockedBy": [35]},
|
||||
{"id": 37, "subject": "Task 20: Central ingest routing", "status": "completed", "blockedBy": [36]},
|
||||
{"id": 38, "subject": "Task 21: Host registration + appsettings", "status": "completed", "blockedBy": [37]},
|
||||
{"id": 39, "subject": "Task 22: CommunicationService outbox methods", "status": "completed", "blockedBy": [38]},
|
||||
{"id": 40, "subject": "Task 23: Notification Outbox Blazor page", "status": "completed", "blockedBy": [39]},
|
||||
{"id": 41, "subject": "Task 24: Health dashboard outbox KPI tiles", "status": "completed", "blockedBy": [40]},
|
||||
{"id": 42, "subject": "Task 25: End-to-end integration test", "status": "completed", "blockedBy": [41]},
|
||||
{"id": 43, "subject": "Task 26: Full build + suite verification", "status": "completed", "blockedBy": [42]}
|
||||
],
|
||||
"lastUpdated": "2026-05-19"
|
||||
"status": "2026-08-01 bookkeeping sync: statuses reconciled against merged code",
|
||||
"lastUpdated": "2026-08-01"
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
> for site roles, with `NoOpSiteStreamAuditClient` retained only for central/test
|
||||
> composition roots; and `AuditLogQueryFilter` is now multi-value per dimension.
|
||||
>
|
||||
> **Transport wording below is stale:** the ClusterClient-based push described throughout this roadmap was replaced by gRPC in the 2026-07-22 ClusterClient→gRPC migration, and `ClusterClientSiteAuditClient` is now `SiteCommunicationAuditClient` (commit `63c16d69`).
|
||||
>
|
||||
> **For Claude:** REQUIRED SUB-SKILL FLOW per milestone: `brainstorming` → `writing-plans` → `subagent-driven-development`. Use `docs/requirements/Component-AuditLog.md` + `alog.md` as the spec; this document is the roadmap that sequences milestones and locks acceptance criteria for each. **M1 carries full TDD-level task detail; M2–M8 are milestone-shape detail and will be expanded into bite-sized plans by their own writing-plans pass when their turn comes.**
|
||||
|
||||
**Goal:** Implement central component #23 Audit Log — append-only forensic + operational record across every script-trust-boundary action — into the existing ScadaBridge codebase.
|
||||
|
||||
@@ -1,24 +1,25 @@
|
||||
{
|
||||
"planPath": "docs/plans/2026-05-20-auditlog-m1-foundation.md",
|
||||
"tasks": [
|
||||
{"id": "A1", "subject": "Bundle A T1: Add audit enums (Channel, Kind, Status, ForwardState)", "status": "pending"},
|
||||
{"id": "A2", "subject": "Bundle A T2: Add AuditEvent record", "status": "pending", "blockedBy": ["A1"]},
|
||||
{"id": "A3", "subject": "Bundle A T3: Add IAuditWriter + ICentralAuditWriter", "status": "pending", "blockedBy": ["A2"]},
|
||||
{"id": "A4", "subject": "Bundle A T4: Add audit telemetry + pull message DTOs", "status": "pending", "blockedBy": ["A2"]},
|
||||
{"id": "A-rev", "subject": "Bundle A combined spec+quality review", "status": "pending", "blockedBy": ["A1", "A2", "A3", "A4"]},
|
||||
{"id": "B5", "subject": "Bundle B T5: ScadaBridgeDbContext.AuditLogs + IEntityTypeConfiguration<AuditEvent> with five named indexes", "status": "pending", "blockedBy": ["A-rev"]},
|
||||
{"id": "B-rev", "subject": "Bundle B review", "status": "pending", "blockedBy": ["B5"]},
|
||||
{"id": "C67", "subject": "Bundle C T6+T7: AddAuditLogTable migration (partition fn/scheme/table/indexes) + DB roles, with infra/mssql integration tests", "status": "pending", "blockedBy": ["B-rev"]},
|
||||
{"id": "C-rev", "subject": "Bundle C review", "status": "pending", "blockedBy": ["C67"]},
|
||||
{"id": "D8", "subject": "Bundle D T8: IAuditLogRepository + EF implementation + DI registration", "status": "pending", "blockedBy": ["C-rev"]},
|
||||
{"id": "D-rev", "subject": "Bundle D review", "status": "pending", "blockedBy": ["D8"]},
|
||||
{"id": "E10", "subject": "Bundle E T10: Scaffold src/ZB.MOM.WW.ScadaBridge.AuditLog/ project + slnx entries", "status": "pending", "blockedBy": ["D-rev"]},
|
||||
{"id": "E9", "subject": "Bundle E T9: AuditLogOptions + validator", "status": "pending", "blockedBy": ["E10"]},
|
||||
{"id": "E-rev", "subject": "Bundle E review", "status": "pending", "blockedBy": ["E10", "E9"]},
|
||||
{"id": "F11", "subject": "Bundle F T11 (controller-direct): Register ZB.MOM.WW.ScadaBridge.AuditLog in Component-Host.md + README confirm", "status": "pending", "blockedBy": ["E-rev"]},
|
||||
{"id": "FINAL-rev", "subject": "Final cross-bundle review over the whole M1 branch", "status": "pending", "blockedBy": ["F11"]},
|
||||
{"id": "MERGE", "subject": "Verify gate: full solution dotnet test green, then merge --no-ff to main", "status": "pending", "blockedBy": ["FINAL-rev"]},
|
||||
{"id": "ROADMAP", "subject": "Update downstream M2-M8 sections of roadmap with realities learned in M1", "status": "pending", "blockedBy": ["MERGE"]}
|
||||
{"id": "A1", "subject": "Bundle A T1: Add audit enums (Channel, Kind, Status, ForwardState)", "status": "completed"},
|
||||
{"id": "A2", "subject": "Bundle A T2: Add AuditEvent record", "status": "completed", "blockedBy": ["A1"]},
|
||||
{"id": "A3", "subject": "Bundle A T3: Add IAuditWriter + ICentralAuditWriter", "status": "completed", "blockedBy": ["A2"]},
|
||||
{"id": "A4", "subject": "Bundle A T4: Add audit telemetry + pull message DTOs", "status": "completed", "blockedBy": ["A2"]},
|
||||
{"id": "A-rev", "subject": "Bundle A combined spec+quality review", "status": "completed", "blockedBy": ["A1", "A2", "A3", "A4"]},
|
||||
{"id": "B5", "subject": "Bundle B T5: ScadaBridgeDbContext.AuditLogs + IEntityTypeConfiguration<AuditEvent> with five named indexes", "status": "completed", "blockedBy": ["A-rev"]},
|
||||
{"id": "B-rev", "subject": "Bundle B review", "status": "completed", "blockedBy": ["B5"]},
|
||||
{"id": "C67", "subject": "Bundle C T6+T7: AddAuditLogTable migration (partition fn/scheme/table/indexes) + DB roles, with infra/mssql integration tests", "status": "completed", "blockedBy": ["B-rev"]},
|
||||
{"id": "C-rev", "subject": "Bundle C review", "status": "completed", "blockedBy": ["C67"]},
|
||||
{"id": "D8", "subject": "Bundle D T8: IAuditLogRepository + EF implementation + DI registration", "status": "completed", "blockedBy": ["C-rev"]},
|
||||
{"id": "D-rev", "subject": "Bundle D review", "status": "completed", "blockedBy": ["D8"]},
|
||||
{"id": "E10", "subject": "Bundle E T10: Scaffold src/ZB.MOM.WW.ScadaBridge.AuditLog/ project + slnx entries", "status": "completed", "blockedBy": ["D-rev"]},
|
||||
{"id": "E9", "subject": "Bundle E T9: AuditLogOptions + validator", "status": "completed", "blockedBy": ["E10"]},
|
||||
{"id": "E-rev", "subject": "Bundle E review", "status": "completed", "blockedBy": ["E10", "E9"]},
|
||||
{"id": "F11", "subject": "Bundle F T11 (controller-direct): Register ZB.MOM.WW.ScadaBridge.AuditLog in Component-Host.md + README confirm", "status": "completed", "blockedBy": ["E-rev"]},
|
||||
{"id": "FINAL-rev", "subject": "Final cross-bundle review over the whole M1 branch", "status": "completed", "blockedBy": ["F11"]},
|
||||
{"id": "MERGE", "subject": "Verify gate: full solution dotnet test green, then merge --no-ff to main", "status": "completed", "blockedBy": ["FINAL-rev"]},
|
||||
{"id": "ROADMAP", "subject": "Update downstream M2-M8 sections of roadmap with realities learned in M1", "status": "completed", "blockedBy": ["MERGE"]}
|
||||
],
|
||||
"lastUpdated": "2026-05-20T00:00:00Z"
|
||||
"lastUpdated": "2026-08-01",
|
||||
"note": "2026-08-01 bookkeeping sync: statuses reconciled against merged code"
|
||||
}
|
||||
|
||||
@@ -3,24 +3,25 @@
|
||||
"spec": "alog.md (commit fec0bb1)",
|
||||
"repoNature": "design-documentation-only",
|
||||
"tasks": [
|
||||
{"id": 0, "subject": "Task 0: Prepare branch", "status": "pending", "blockedBy": []},
|
||||
{"id": 1, "subject": "Task 1: Author Component-AuditLog.md", "status": "pending", "blockedBy": [0]},
|
||||
{"id": 2, "subject": "Task 2: Update Component-Commons.md", "status": "pending", "blockedBy": [0]},
|
||||
{"id": 3, "subject": "Task 3: Update Component-ConfigurationDatabase.md", "status": "pending", "blockedBy": [1]},
|
||||
{"id": 4, "subject": "Task 4: Update Component-ClusterInfrastructure.md", "status": "pending", "blockedBy": [1]},
|
||||
{"id": 5, "subject": "Task 5: Update Component-SiteRuntime.md", "status": "pending", "blockedBy": [1]},
|
||||
{"id": 6, "subject": "Task 6: Update Component-ExternalSystemGateway.md", "status": "pending", "blockedBy": [1]},
|
||||
{"id": 7, "subject": "Task 7: Update Component-SiteCallAudit.md", "status": "pending", "blockedBy": [1]},
|
||||
{"id": 8, "subject": "Task 8: Update Component-NotificationOutbox.md", "status": "pending", "blockedBy": [1]},
|
||||
{"id": 9, "subject": "Task 9: Update Component-InboundAPI.md", "status": "pending", "blockedBy": [1]},
|
||||
{"id": 10, "subject": "Task 10: Update Component-CentralUI.md", "status": "pending", "blockedBy": [1]},
|
||||
{"id": 11, "subject": "Task 11: Update Component-HealthMonitoring.md", "status": "pending", "blockedBy": [1]},
|
||||
{"id": 12, "subject": "Task 12: Update Component-CLI.md", "status": "pending", "blockedBy": [1]},
|
||||
{"id": 13, "subject": "Task 13: Update README.md", "status": "pending", "blockedBy": [1]},
|
||||
{"id": 14, "subject": "Task 14: Update HighLevelReqs.md", "status": "pending", "blockedBy": [1]},
|
||||
{"id": 15, "subject": "Task 15: Update CLAUDE.md", "status": "pending", "blockedBy": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]},
|
||||
{"id": 16, "subject": "Task 16: Final cross-reference verification", "status": "pending", "blockedBy": [15]},
|
||||
{"id": 17, "subject": "Task 17: Merge to main (user-gated)", "status": "pending", "blockedBy": [16]}
|
||||
{"id": 0, "subject": "Task 0: Prepare branch", "status": "completed", "blockedBy": []},
|
||||
{"id": 1, "subject": "Task 1: Author Component-AuditLog.md", "status": "completed", "blockedBy": [0]},
|
||||
{"id": 2, "subject": "Task 2: Update Component-Commons.md", "status": "completed", "blockedBy": [0]},
|
||||
{"id": 3, "subject": "Task 3: Update Component-ConfigurationDatabase.md", "status": "completed", "blockedBy": [1]},
|
||||
{"id": 4, "subject": "Task 4: Update Component-ClusterInfrastructure.md", "status": "completed", "blockedBy": [1]},
|
||||
{"id": 5, "subject": "Task 5: Update Component-SiteRuntime.md", "status": "completed", "blockedBy": [1]},
|
||||
{"id": 6, "subject": "Task 6: Update Component-ExternalSystemGateway.md", "status": "completed", "blockedBy": [1]},
|
||||
{"id": 7, "subject": "Task 7: Update Component-SiteCallAudit.md", "status": "completed", "blockedBy": [1]},
|
||||
{"id": 8, "subject": "Task 8: Update Component-NotificationOutbox.md", "status": "completed", "blockedBy": [1]},
|
||||
{"id": 9, "subject": "Task 9: Update Component-InboundAPI.md", "status": "completed", "blockedBy": [1]},
|
||||
{"id": 10, "subject": "Task 10: Update Component-CentralUI.md", "status": "completed", "blockedBy": [1]},
|
||||
{"id": 11, "subject": "Task 11: Update Component-HealthMonitoring.md", "status": "completed", "blockedBy": [1]},
|
||||
{"id": 12, "subject": "Task 12: Update Component-CLI.md", "status": "completed", "blockedBy": [1]},
|
||||
{"id": 13, "subject": "Task 13: Update README.md", "status": "completed", "blockedBy": [1]},
|
||||
{"id": 14, "subject": "Task 14: Update HighLevelReqs.md", "status": "completed", "blockedBy": [1]},
|
||||
{"id": 15, "subject": "Task 15: Update CLAUDE.md", "status": "completed", "blockedBy": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]},
|
||||
{"id": 16, "subject": "Task 16: Final cross-reference verification", "status": "completed", "blockedBy": [15]},
|
||||
{"id": 17, "subject": "Task 17: Merge to main (user-gated)", "status": "completed", "blockedBy": [16]}
|
||||
],
|
||||
"lastUpdated": "2026-05-20T00:00:00Z"
|
||||
"lastUpdated": "2026-08-01",
|
||||
"note": "2026-08-01 bookkeeping sync: statuses reconciled against merged code"
|
||||
}
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
{
|
||||
"planPath": "docs/plans/2026-05-21-audit-executionid.md",
|
||||
"tasks": [
|
||||
{"id": 50, "subject": "Task 0: Prep — verify branch + baseline", "status": "pending"},
|
||||
{"id": 51, "subject": "Task 1: Foundation — AuditEvent.ExecutionId + central AuditLog column + repo query", "status": "pending", "blockedBy": [50]},
|
||||
{"id": 52, "subject": "Task 2: Foundation — site SQLite + gRPC DTO", "status": "pending", "blockedBy": [51]},
|
||||
{"id": 53, "subject": "Task 3: Site script-side emitters stamp ExecutionId", "status": "pending", "blockedBy": [52]},
|
||||
{"id": 54, "subject": "Task 4: Cached S&F retry-loop rows carry ExecutionId", "status": "pending", "blockedBy": [53]},
|
||||
{"id": 55, "subject": "Task 5: Central NotifyDeliver rows carry ExecutionId", "status": "pending", "blockedBy": [52]},
|
||||
{"id": 56, "subject": "Task 6: Inbound audit rows carry ExecutionId", "status": "pending", "blockedBy": [52]},
|
||||
{"id": 57, "subject": "Task 7: Central UI — ExecutionId column, filter, drill-in", "status": "pending", "blockedBy": [51]},
|
||||
{"id": 58, "subject": "Task 8: CLI + ManagementService — ExecutionId filter", "status": "pending", "blockedBy": [51]},
|
||||
{"id": 59, "subject": "Task 9: End-to-end integration test + docs", "status": "pending", "blockedBy": [53, 54, 55, 56, 57, 58]}
|
||||
{"id": 50, "subject": "Task 0: Prep — verify branch + baseline", "status": "completed"},
|
||||
{"id": 51, "subject": "Task 1: Foundation — AuditEvent.ExecutionId + central AuditLog column + repo query", "status": "completed", "blockedBy": [50]},
|
||||
{"id": 52, "subject": "Task 2: Foundation — site SQLite + gRPC DTO", "status": "completed", "blockedBy": [51]},
|
||||
{"id": 53, "subject": "Task 3: Site script-side emitters stamp ExecutionId", "status": "completed", "blockedBy": [52]},
|
||||
{"id": 54, "subject": "Task 4: Cached S&F retry-loop rows carry ExecutionId", "status": "completed", "blockedBy": [53]},
|
||||
{"id": 55, "subject": "Task 5: Central NotifyDeliver rows carry ExecutionId", "status": "completed", "blockedBy": [52]},
|
||||
{"id": 56, "subject": "Task 6: Inbound audit rows carry ExecutionId", "status": "completed", "blockedBy": [52]},
|
||||
{"id": 57, "subject": "Task 7: Central UI — ExecutionId column, filter, drill-in", "status": "completed", "blockedBy": [51]},
|
||||
{"id": 58, "subject": "Task 8: CLI + ManagementService — ExecutionId filter", "status": "completed", "blockedBy": [51]},
|
||||
{"id": 59, "subject": "Task 9: End-to-end integration test + docs", "status": "completed", "blockedBy": [53, 54, 55, 56, 57, 58]}
|
||||
],
|
||||
"lastUpdated": "2026-05-21T00:00:00Z"
|
||||
"lastUpdated": "2026-08-01",
|
||||
"note": "2026-08-01 bookkeeping sync: statuses reconciled against merged code"
|
||||
}
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
{
|
||||
"planPath": "docs/plans/2026-05-21-audit-parent-executionid.md",
|
||||
"tasks": [
|
||||
{"id": 0, "subject": "Task 0: Prep — verify branch + baseline", "status": "pending"},
|
||||
{"id": 1, "subject": "Task 1: Foundation — AuditEvent.ParentExecutionId + central AuditLog column", "status": "pending", "blockedBy": [0]},
|
||||
{"id": 2, "subject": "Task 2: Foundation — site SQLite + gRPC DTO", "status": "pending", "blockedBy": [1]},
|
||||
{"id": 3, "subject": "Task 3: Inbound request id minting + RouteToCallRequest.ParentExecutionId", "status": "pending", "blockedBy": [0]},
|
||||
{"id": 4, "subject": "Task 4: Thread ParentExecutionId into routed script ScriptRuntimeContext", "status": "pending", "blockedBy": [3]},
|
||||
{"id": 5, "subject": "Task 5: Site script-side emitters stamp ParentExecutionId", "status": "pending", "blockedBy": [4, 2]},
|
||||
{"id": 6, "subject": "Task 6: Cached S&F retry-loop rows carry ParentExecutionId", "status": "pending", "blockedBy": [5]},
|
||||
{"id": 7, "subject": "Task 7: Central NotifyDeliver rows carry ParentExecutionId", "status": "pending", "blockedBy": [5, 1]},
|
||||
{"id": 8, "subject": "Task 8: Repository — GetExecutionTreeAsync", "status": "pending", "blockedBy": [1]},
|
||||
{"id": 9, "subject": "Task 9: Central UI — ParentExecutionId column, filter, parent drill-in", "status": "pending", "blockedBy": [1]},
|
||||
{"id": 10, "subject": "Task 10: Central UI — execution-chain tree view", "status": "pending", "blockedBy": [8, 9]},
|
||||
{"id": 11, "subject": "Task 11: CLI + ManagementService — ParentExecutionId filter", "status": "pending", "blockedBy": [1]},
|
||||
{"id": 12, "subject": "Task 12: End-to-end integration test + docs", "status": "pending", "blockedBy": [5, 6, 7, 10, 11]}
|
||||
{"id": 0, "subject": "Task 0: Prep — verify branch + baseline", "status": "completed"},
|
||||
{"id": 1, "subject": "Task 1: Foundation — AuditEvent.ParentExecutionId + central AuditLog column", "status": "completed", "blockedBy": [0]},
|
||||
{"id": 2, "subject": "Task 2: Foundation — site SQLite + gRPC DTO", "status": "completed", "blockedBy": [1]},
|
||||
{"id": 3, "subject": "Task 3: Inbound request id minting + RouteToCallRequest.ParentExecutionId", "status": "completed", "blockedBy": [0]},
|
||||
{"id": 4, "subject": "Task 4: Thread ParentExecutionId into routed script ScriptRuntimeContext", "status": "completed", "blockedBy": [3]},
|
||||
{"id": 5, "subject": "Task 5: Site script-side emitters stamp ParentExecutionId", "status": "completed", "blockedBy": [4, 2]},
|
||||
{"id": 6, "subject": "Task 6: Cached S&F retry-loop rows carry ParentExecutionId", "status": "completed", "blockedBy": [5]},
|
||||
{"id": 7, "subject": "Task 7: Central NotifyDeliver rows carry ParentExecutionId", "status": "completed", "blockedBy": [5, 1]},
|
||||
{"id": 8, "subject": "Task 8: Repository — GetExecutionTreeAsync", "status": "completed", "blockedBy": [1]},
|
||||
{"id": 9, "subject": "Task 9: Central UI — ParentExecutionId column, filter, parent drill-in", "status": "completed", "blockedBy": [1]},
|
||||
{"id": 10, "subject": "Task 10: Central UI — execution-chain tree view", "status": "completed", "blockedBy": [8, 9]},
|
||||
{"id": 11, "subject": "Task 11: CLI + ManagementService — ParentExecutionId filter", "status": "completed", "blockedBy": [1]},
|
||||
{"id": 12, "subject": "Task 12: End-to-end integration test + docs", "status": "completed", "blockedBy": [5, 6, 7, 10, 11]}
|
||||
],
|
||||
"lastUpdated": "2026-05-21"
|
||||
"lastUpdated": "2026-08-01",
|
||||
"note": "2026-08-01 bookkeeping sync: statuses reconciled against merged code"
|
||||
}
|
||||
|
||||
@@ -929,12 +929,12 @@ git commit -m "chore(audit): smoke-verify SourceNode end-to-end across cluster"
|
||||
|
||||
## Acceptance Criteria (the whole-plan checklist)
|
||||
|
||||
- [ ] Every audit row written from this commit forward carries `SourceNode` populated (`central-a/b` for central direct-write, `node-a/b` for site rows).
|
||||
- [ ] Every new `Notifications` and `SiteCalls` row carries `SourceNode` from the site.
|
||||
- [ ] `IX_AuditLog_Node_Occurred` exists in central MS SQL.
|
||||
- [ ] Site SQLite `AuditLog` and `OperationTracking` tables both have `SourceNode TEXT NULL`; existing site DBs are upgraded idempotently on startup.
|
||||
- [ ] Proto `AuditEventDto.source_node = 22` and `SiteCallOperationalDto.source_node = 12` exist; no field numbers reused.
|
||||
- [ ] Central UI Audit Log, Notifications, and Site Calls pages all display a "Node" column and support filtering by it.
|
||||
- [ ] All test projects green.
|
||||
- [ ] Cluster comes up clean via `bash docker/deploy.sh`; CLI smoke confirms expected node names land in the central tables.
|
||||
- [ ] Design docs (already committed in Task 0) match the implementation.
|
||||
- [x] Every audit row written from this commit forward carries `SourceNode` populated (`central-a/b` for central direct-write, `node-a/b` for site rows).
|
||||
- [x] Every new `Notifications` and `SiteCalls` row carries `SourceNode` from the site.
|
||||
- [x] `IX_AuditLog_Node_Occurred` exists in central MS SQL.
|
||||
- [x] Site SQLite `AuditLog` and `OperationTracking` tables both have `SourceNode TEXT NULL`; existing site DBs are upgraded idempotently on startup.
|
||||
- [x] Proto `AuditEventDto.source_node = 22` and `SiteCallOperationalDto.source_node = 12` exist; no field numbers reused.
|
||||
- [x] Central UI Audit Log, Notifications, and Site Calls pages all display a "Node" column and support filtering by it.
|
||||
- [x] All test projects green.
|
||||
- [x] Cluster comes up clean via `bash docker/deploy.sh`; CLI smoke confirms expected node names land in the central tables. (`NodeName` is now startup-validated — an empty value fails fast rather than NULLing `SourceNode`, commit `4a0462e4` — and the rig has redeployed many times since.)
|
||||
- [x] Design docs (already committed in Task 0) match the implementation.
|
||||
|
||||
@@ -1,27 +1,28 @@
|
||||
{
|
||||
"planPath": "docs/plans/2026-05-23-audit-source-node.md",
|
||||
"tasks": [
|
||||
{"id": 1, "subject": "Task 0: Branch + Snapshot", "status": "pending"},
|
||||
{"id": 2, "subject": "Task 1: NodeOptions.NodeName + INodeIdentityProvider", "status": "pending", "blockedBy": [1]},
|
||||
{"id": 3, "subject": "Task 2: Add SourceNode to AuditEvent record", "status": "pending", "blockedBy": [2]},
|
||||
{"id": 4, "subject": "Task 3: Add SourceNode to SiteCallOperational + SiteCall entity", "status": "pending", "blockedBy": [2]},
|
||||
{"id": 5, "subject": "Task 4: Add SourceNode to Notification entity + NotificationSubmit", "status": "pending", "blockedBy": [2]},
|
||||
{"id": 6, "subject": "Task 5: Add source_node to proto + update DTO mappers", "status": "pending", "blockedBy": [3, 4]},
|
||||
{"id": 7, "subject": "Task 6: EF migration — SourceNode on AuditLog + IX_AuditLog_Node_Occurred", "status": "pending", "blockedBy": [3]},
|
||||
{"id": 8, "subject": "Task 7: EF migration — SourceNode on Notifications", "status": "pending", "blockedBy": [5]},
|
||||
{"id": 9, "subject": "Task 8: EF migration — SourceNode on SiteCalls", "status": "pending", "blockedBy": [4]},
|
||||
{"id": 10, "subject": "Task 9: Site SQLite AuditLog — add SourceNode (idempotent upgrade)", "status": "pending", "blockedBy": [3]},
|
||||
{"id": 11, "subject": "Task 10: Site SQLite OperationTracking — add SourceNode", "status": "pending", "blockedBy": [4]},
|
||||
{"id": 12, "subject": "Task 11: Stamp SourceNode at site SqliteAuditWriter", "status": "pending", "blockedBy": [2, 10]},
|
||||
{"id": 13, "subject": "Task 12: Stamp SourceNode at CentralAuditWriter + persist via repo", "status": "pending", "blockedBy": [2, 7]},
|
||||
{"id": 14, "subject": "Task 13: Carry SourceNode through Notifications S&F handoff", "status": "pending", "blockedBy": [2, 5, 8]},
|
||||
{"id": 15, "subject": "Task 14: Carry SourceNode through cached-call telemetry → SiteCalls", "status": "pending", "blockedBy": [2, 9, 11]},
|
||||
{"id": 16, "subject": "Task 15: UI — Node column + filter on AuditLog grid", "status": "pending", "blockedBy": [7]},
|
||||
{"id": 17, "subject": "Task 16: UI — Node column + filter on Notifications grid", "status": "pending", "blockedBy": [7]},
|
||||
{"id": 18, "subject": "Task 17: UI — Node column + filter on SiteCalls grid", "status": "pending", "blockedBy": [7]},
|
||||
{"id": 19, "subject": "Task 18: Docker appsettings — NodeName on all 8 nodes", "status": "pending", "blockedBy": [2]},
|
||||
{"id": 20, "subject": "Task 19: Full build + targeted test sweep", "status": "pending", "blockedBy": [12, 13, 14, 15, 16, 17, 18, 19]},
|
||||
{"id": 21, "subject": "Task 20: Docker redeploy + smoke verify", "status": "pending", "blockedBy": [20]}
|
||||
{"id": 1, "subject": "Task 0: Branch + Snapshot", "status": "completed"},
|
||||
{"id": 2, "subject": "Task 1: NodeOptions.NodeName + INodeIdentityProvider", "status": "completed", "blockedBy": [1]},
|
||||
{"id": 3, "subject": "Task 2: Add SourceNode to AuditEvent record", "status": "completed", "blockedBy": [2]},
|
||||
{"id": 4, "subject": "Task 3: Add SourceNode to SiteCallOperational + SiteCall entity", "status": "completed", "blockedBy": [2]},
|
||||
{"id": 5, "subject": "Task 4: Add SourceNode to Notification entity + NotificationSubmit", "status": "completed", "blockedBy": [2]},
|
||||
{"id": 6, "subject": "Task 5: Add source_node to proto + update DTO mappers", "status": "completed", "blockedBy": [3, 4]},
|
||||
{"id": 7, "subject": "Task 6: EF migration — SourceNode on AuditLog + IX_AuditLog_Node_Occurred", "status": "completed", "blockedBy": [3]},
|
||||
{"id": 8, "subject": "Task 7: EF migration — SourceNode on Notifications", "status": "completed", "blockedBy": [5]},
|
||||
{"id": 9, "subject": "Task 8: EF migration — SourceNode on SiteCalls", "status": "completed", "blockedBy": [4]},
|
||||
{"id": 10, "subject": "Task 9: Site SQLite AuditLog — add SourceNode (idempotent upgrade)", "status": "completed", "blockedBy": [3]},
|
||||
{"id": 11, "subject": "Task 10: Site SQLite OperationTracking — add SourceNode", "status": "completed", "blockedBy": [4]},
|
||||
{"id": 12, "subject": "Task 11: Stamp SourceNode at site SqliteAuditWriter", "status": "completed", "blockedBy": [2, 10]},
|
||||
{"id": 13, "subject": "Task 12: Stamp SourceNode at CentralAuditWriter + persist via repo", "status": "completed", "blockedBy": [2, 7]},
|
||||
{"id": 14, "subject": "Task 13: Carry SourceNode through Notifications S&F handoff", "status": "completed", "blockedBy": [2, 5, 8]},
|
||||
{"id": 15, "subject": "Task 14: Carry SourceNode through cached-call telemetry → SiteCalls", "status": "completed", "blockedBy": [2, 9, 11]},
|
||||
{"id": 16, "subject": "Task 15: UI — Node column + filter on AuditLog grid", "status": "completed", "blockedBy": [7]},
|
||||
{"id": 17, "subject": "Task 16: UI — Node column + filter on Notifications grid", "status": "completed", "blockedBy": [7]},
|
||||
{"id": 18, "subject": "Task 17: UI — Node column + filter on SiteCalls grid", "status": "completed", "blockedBy": [7]},
|
||||
{"id": 19, "subject": "Task 18: Docker appsettings — NodeName on all 8 nodes", "status": "completed", "blockedBy": [2]},
|
||||
{"id": 20, "subject": "Task 19: Full build + targeted test sweep", "status": "completed", "blockedBy": [12, 13, 14, 15, 16, 17, 18, 19]},
|
||||
{"id": 21, "subject": "Task 20: Docker redeploy + smoke verify", "status": "completed", "blockedBy": [20]}
|
||||
],
|
||||
"lastUpdated": "2026-05-23T00:00:00Z"
|
||||
"lastUpdated": "2026-08-01",
|
||||
"note": "2026-08-01 bookkeeping sync: statuses reconciled against merged code"
|
||||
}
|
||||
|
||||
@@ -132,17 +132,17 @@ same "options validation" path used for other AuditLog settings.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] `AuditLog:InboundMaxBytes` option exists on the AuditLog options class,
|
||||
- [x] `AuditLog:InboundMaxBytes` option exists on the AuditLog options class,
|
||||
with the documented default and bounds, validated at startup.
|
||||
- [ ] Inbound request middleware writes `RequestSummary` and `ResponseSummary`
|
||||
- [x] Inbound request middleware writes `RequestSummary` and `ResponseSummary`
|
||||
using the inbound ceiling instead of the 8 KB / 64 KB defaults.
|
||||
- [ ] Other channels' rows (e.g. an `ApiOutbound.ApiCall` over the limit) still
|
||||
- [x] Other channels' rows (e.g. an `ApiOutbound.ApiCall` over the limit) still
|
||||
truncate at 8 KB (64 KB on error rows) — regression-tested.
|
||||
- [ ] `PayloadTruncated = 1` on an inbound row iff request body or response
|
||||
- [x] `PayloadTruncated = 1` on an inbound row iff request body or response
|
||||
body exceeded `InboundMaxBytes`.
|
||||
- [ ] Header redaction list and per-target body redactors still apply to
|
||||
- [x] Header redaction list and per-target body redactors still apply to
|
||||
inbound rows.
|
||||
- [ ] Redactor failure on an inbound row still produces `<redacted: redactor
|
||||
- [x] Redactor failure on an inbound row still produces `<redacted: redactor
|
||||
error>` and increments `AuditRedactionFailure`.
|
||||
- [ ] `Component-AuditLog.md` and `Component-InboundAPI.md` updated as
|
||||
- [x] `Component-AuditLog.md` and `Component-InboundAPI.md` updated as
|
||||
described in **Doc Edits**.
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
{
|
||||
"planPath": "docs/plans/2026-05-23-inbound-api-full-response-audit.md",
|
||||
"tasks": [
|
||||
{"id": 1, "subject": "Task 0: Prep — branch, baseline build", "status": "pending"},
|
||||
{"id": 2, "subject": "Task 1: Add InboundMaxBytes to AuditLogOptions (TDD)", "status": "pending", "blockedBy": [1]},
|
||||
{"id": 3, "subject": "Task 2: Wire InboundMaxBytes into DefaultAuditPayloadFilter (TDD)", "status": "pending", "blockedBy": [2]},
|
||||
{"id": 4, "subject": "Task 3: Capture response body in AuditWriteMiddleware (TDD)", "status": "pending", "blockedBy": [3]},
|
||||
{"id": 5, "subject": "Task 4: Update Component-AuditLog.md", "status": "pending", "blockedBy": [4]},
|
||||
{"id": 6, "subject": "Task 5: Update Component-InboundAPI.md", "status": "pending", "blockedBy": [5]},
|
||||
{"id": 7, "subject": "Task 6: Final build + full test run + branch summary", "status": "pending", "blockedBy": [6]}
|
||||
{"id": 1, "subject": "Task 0: Prep — branch, baseline build", "status": "completed"},
|
||||
{"id": 2, "subject": "Task 1: Add InboundMaxBytes to AuditLogOptions (TDD)", "status": "completed", "blockedBy": [1]},
|
||||
{"id": 3, "subject": "Task 2: Wire InboundMaxBytes into DefaultAuditPayloadFilter (TDD)", "status": "completed", "blockedBy": [2]},
|
||||
{"id": 4, "subject": "Task 3: Capture response body in AuditWriteMiddleware (TDD)", "status": "completed", "blockedBy": [3]},
|
||||
{"id": 5, "subject": "Task 4: Update Component-AuditLog.md", "status": "completed", "blockedBy": [4]},
|
||||
{"id": 6, "subject": "Task 5: Update Component-InboundAPI.md", "status": "completed", "blockedBy": [5]},
|
||||
{"id": 7, "subject": "Task 6: Final build + full test run + branch summary", "status": "completed", "blockedBy": [6]}
|
||||
],
|
||||
"lastUpdated": "2026-05-23"
|
||||
"lastUpdated": "2026-08-01",
|
||||
"note": "2026-08-01 bookkeeping sync: statuses reconciled against merged code"
|
||||
}
|
||||
|
||||
@@ -1,28 +1,29 @@
|
||||
{
|
||||
"planPath": "docs/plans/2026-05-28-opcua-tag-browser.md",
|
||||
"tasks": [
|
||||
{"id": 70, "subject": "Task 1: Add DataSourceReferenceOverride to InstanceConnectionBinding entity", "status": "pending"},
|
||||
{"id": 71, "subject": "Task 2: Add override to ConnectionBinding wire record + ManagementActor mapping", "status": "pending"},
|
||||
{"id": 72, "subject": "Task 3: EF mapping for DataSourceReferenceOverride column", "status": "pending"},
|
||||
{"id": 73, "subject": "Task 4: EF Core migration AddInstanceConnectionBindingOverride", "status": "pending", "blockedBy": [72]},
|
||||
{"id": 74, "subject": "Task 5: IBrowsableDataConnection interface + BrowseNode types", "status": "pending"},
|
||||
{"id": 75, "subject": "Task 6: BrowseCommands.cs (BrowseOpcUaNodeCommand + result + failure)", "status": "pending"},
|
||||
{"id": 76, "subject": "Task 7: Add BrowseChildrenAsync to IOpcUaClient", "status": "pending", "blockedBy": [74, 75]},
|
||||
{"id": 77, "subject": "Task 8: Implement BrowseChildrenAsync on RealOpcUaClient", "status": "pending", "blockedBy": [76]},
|
||||
{"id": 78, "subject": "Task 9: Implement IBrowsableDataConnection on OpcUaDataConnection", "status": "pending", "blockedBy": [76]},
|
||||
{"id": 79, "subject": "Task 10: Handle BrowseOpcUaNodeCommand in DataConnectionManagerActor", "status": "pending", "blockedBy": [75, 76]},
|
||||
{"id": 80, "subject": "Task 11: Forward BrowseOpcUaNodeCommand in SiteCommunicationActor", "status": "pending", "blockedBy": [79]},
|
||||
{"id": 81, "subject": "Task 12: Apply override in FlatteningService.ApplyConnectionBindings", "status": "pending", "blockedBy": [70]},
|
||||
{"id": 82, "subject": "Task 13: Revision-hash regression test", "status": "pending", "blockedBy": [81]},
|
||||
{"id": 83, "subject": "Task 14: IOpcUaBrowseService + impl + DI registration", "status": "pending", "blockedBy": [75]},
|
||||
{"id": 84, "subject": "Task 15: Scaffold OpcUaBrowserDialog modal", "status": "pending"},
|
||||
{"id": 85, "subject": "Task 16: Tree rendering + lazy load + selection in dialog", "status": "pending", "blockedBy": [83, 84]},
|
||||
{"id": 86, "subject": "Task 17: Error banner mapping + polish on dialog", "status": "pending", "blockedBy": [85]},
|
||||
{"id": 87, "subject": "Task 18: Add Override column + Browse button to InstanceConfigure.razor", "status": "pending", "blockedBy": [83, 86]},
|
||||
{"id": 88, "subject": "Task 19: End-to-end manual smoke (online + offline)", "status": "pending", "blockedBy": [70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87]},
|
||||
{"id": 89, "subject": "Task 20: Update Component-DataConnectionLayer.md", "status": "pending", "blockedBy": [81]},
|
||||
{"id": 90, "subject": "Task 21: Update Component-TemplateEngine.md", "status": "pending", "blockedBy": [81]},
|
||||
{"id": 91, "subject": "Task 22: Update Component-CentralUI.md", "status": "pending", "blockedBy": [87]}
|
||||
{"id": 70, "subject": "Task 1: Add DataSourceReferenceOverride to InstanceConnectionBinding entity", "status": "completed"},
|
||||
{"id": 71, "subject": "Task 2: Add override to ConnectionBinding wire record + ManagementActor mapping", "status": "completed"},
|
||||
{"id": 72, "subject": "Task 3: EF mapping for DataSourceReferenceOverride column", "status": "completed"},
|
||||
{"id": 73, "subject": "Task 4: EF Core migration AddInstanceConnectionBindingOverride", "status": "completed", "blockedBy": [72]},
|
||||
{"id": 74, "subject": "Task 5: IBrowsableDataConnection interface + BrowseNode types", "status": "completed"},
|
||||
{"id": 75, "subject": "Task 6: BrowseCommands.cs (BrowseOpcUaNodeCommand + result + failure)", "status": "completed"},
|
||||
{"id": 76, "subject": "Task 7: Add BrowseChildrenAsync to IOpcUaClient", "status": "completed", "blockedBy": [74, 75]},
|
||||
{"id": 77, "subject": "Task 8: Implement BrowseChildrenAsync on RealOpcUaClient", "status": "completed", "blockedBy": [76]},
|
||||
{"id": 78, "subject": "Task 9: Implement IBrowsableDataConnection on OpcUaDataConnection", "status": "completed", "blockedBy": [76]},
|
||||
{"id": 79, "subject": "Task 10: Handle BrowseOpcUaNodeCommand in DataConnectionManagerActor", "status": "completed", "blockedBy": [75, 76]},
|
||||
{"id": 80, "subject": "Task 11: Forward BrowseOpcUaNodeCommand in SiteCommunicationActor", "status": "completed", "blockedBy": [79]},
|
||||
{"id": 81, "subject": "Task 12: Apply override in FlatteningService.ApplyConnectionBindings", "status": "completed", "blockedBy": [70]},
|
||||
{"id": 82, "subject": "Task 13: Revision-hash regression test", "status": "completed", "blockedBy": [81]},
|
||||
{"id": 83, "subject": "Task 14: IOpcUaBrowseService + impl + DI registration", "status": "completed", "blockedBy": [75]},
|
||||
{"id": 84, "subject": "Task 15: Scaffold OpcUaBrowserDialog modal", "status": "completed"},
|
||||
{"id": 85, "subject": "Task 16: Tree rendering + lazy load + selection in dialog", "status": "completed", "blockedBy": [83, 84]},
|
||||
{"id": 86, "subject": "Task 17: Error banner mapping + polish on dialog", "status": "completed", "blockedBy": [85]},
|
||||
{"id": 87, "subject": "Task 18: Add Override column + Browse button to InstanceConfigure.razor", "status": "completed", "blockedBy": [83, 86]},
|
||||
{"id": 88, "subject": "Task 19: End-to-end manual smoke (online + offline)", "status": "pending", "blockedBy": [70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87], "notes": "needs live rig — tracked in the 2026-08-01 pending-work task list"},
|
||||
{"id": 89, "subject": "Task 20: Update Component-DataConnectionLayer.md", "status": "completed", "blockedBy": [81]},
|
||||
{"id": 90, "subject": "Task 21: Update Component-TemplateEngine.md", "status": "completed", "blockedBy": [81]},
|
||||
{"id": 91, "subject": "Task 22: Update Component-CentralUI.md", "status": "completed", "blockedBy": [87]}
|
||||
],
|
||||
"lastUpdated": "2026-05-28T00:00:00Z"
|
||||
"status": "2026-08-01 bookkeeping sync: statuses reconciled against merged code",
|
||||
"lastUpdated": "2026-08-01"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# ScadaLink → ZB.MOM.WW.ScadaBridge Rename — Design
|
||||
|
||||
**Date:** 2026-05-28
|
||||
**Status:** Approved, in implementation
|
||||
**Status:** Implemented (commit `7b0b9c73`)
|
||||
**Scope:** Repo-wide rename of the product from "ScadaLink" to "ScadaBridge" and addition of the `ZB.MOM.WW` company prefix to every .NET project. Code, runtime artifacts (containers, network, databases), docs, and CLI config.
|
||||
|
||||
## Decisions
|
||||
@@ -115,7 +115,7 @@ Drop volume + re-seed. `docker compose -f infra/docker-compose.yml down -v` then
|
||||
## Out of Scope
|
||||
|
||||
- Sister repos `~/Desktop/MxAccessGateway`, `~/Desktop/OtOpcUa` — independent codebases, independent timelines.
|
||||
- Repo folder `~/Desktop/scadalink-design` — left as-is to preserve Claude Code memory paths and user shell context.
|
||||
- Repo folder `~/Desktop/scadalink-design` — left as-is to preserve Claude Code memory paths and user shell context. *(2026-08-01: this exclusion was later reversed and completed — see `2026-05-31-folder-repo-rename-scadabridge-plan.md` / `-design.md`, which renamed the folder and the Gitea repo to ScadaBridge.)*
|
||||
- Existing Transport bundle manifests with `SourceEnvironment = "docker-cluster"` — those values are deployment IDs and stay stable. If the user later wants to rename the deployment IDs themselves, that's a separate design.
|
||||
|
||||
## Verification
|
||||
|
||||
@@ -1,34 +1,35 @@
|
||||
{
|
||||
"planPath": "docs/plans/2026-06-03-component-reference-docs.md",
|
||||
"tasks": [
|
||||
{"id": 19, "subject": "Task 0: Scaffold + shared assets", "status": "pending"},
|
||||
{"id": 20, "subject": "Task 1: Pilot exemplar AuditLog.md (approval gate)", "status": "pending", "blockedBy": [19]},
|
||||
{"id": 21, "subject": "Task 2: Commons.md reference doc", "status": "pending", "blockedBy": [20]},
|
||||
{"id": 22, "subject": "Task 3: ConfigurationDatabase.md reference doc", "status": "pending", "blockedBy": [20]},
|
||||
{"id": 23, "subject": "Task 4: Communication.md reference doc", "status": "pending", "blockedBy": [20]},
|
||||
{"id": 24, "subject": "Task 5: ClusterInfrastructure.md reference doc", "status": "pending", "blockedBy": [20]},
|
||||
{"id": 25, "subject": "Task 6: Host.md reference doc", "status": "pending", "blockedBy": [20]},
|
||||
{"id": 26, "subject": "Task 7: Security.md reference doc", "status": "pending", "blockedBy": [20]},
|
||||
{"id": 27, "subject": "Task 8: TemplateEngine.md reference doc", "status": "pending", "blockedBy": [20]},
|
||||
{"id": 28, "subject": "Task 9: DeploymentManager.md reference doc", "status": "pending", "blockedBy": [20]},
|
||||
{"id": 29, "subject": "Task 10: SiteRuntime.md reference doc", "status": "pending", "blockedBy": [20]},
|
||||
{"id": 30, "subject": "Task 11: DataConnectionLayer.md reference doc", "status": "pending", "blockedBy": [20]},
|
||||
{"id": 31, "subject": "Task 12: StoreAndForward.md reference doc", "status": "pending", "blockedBy": [20]},
|
||||
{"id": 32, "subject": "Task 13: ExternalSystemGateway.md reference doc", "status": "pending", "blockedBy": [20]},
|
||||
{"id": 33, "subject": "Task 14: NotificationService.md reference doc", "status": "pending", "blockedBy": [20]},
|
||||
{"id": 34, "subject": "Task 15: NotificationOutbox.md reference doc", "status": "pending", "blockedBy": [20]},
|
||||
{"id": 35, "subject": "Task 16: SiteCallAudit.md reference doc", "status": "pending", "blockedBy": [20]},
|
||||
{"id": 36, "subject": "Task 17: HealthMonitoring.md reference doc", "status": "pending", "blockedBy": [20]},
|
||||
{"id": 37, "subject": "Task 18: SiteEventLogging.md reference doc", "status": "pending", "blockedBy": [20]},
|
||||
{"id": 38, "subject": "Task 19: InboundAPI.md reference doc", "status": "pending", "blockedBy": [20]},
|
||||
{"id": 39, "subject": "Task 20: ManagementService.md reference doc", "status": "pending", "blockedBy": [20]},
|
||||
{"id": 40, "subject": "Task 21: CLI.md reference doc", "status": "pending", "blockedBy": [20]},
|
||||
{"id": 41, "subject": "Task 22: Transport.md reference doc", "status": "pending", "blockedBy": [20]},
|
||||
{"id": 42, "subject": "Task 23: CentralUI.md reference doc", "status": "pending", "blockedBy": [20]},
|
||||
{"id": 43, "subject": "Task 24: TraefikProxy.md reference doc", "status": "pending", "blockedBy": [20]},
|
||||
{"id": 44, "subject": "Task 25: TreeView.md reference doc", "status": "pending", "blockedBy": [20]},
|
||||
{"id": 45, "subject": "Task 26: Index + README link", "status": "pending", "blockedBy": [20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44]},
|
||||
{"id": 46, "subject": "Task 27: Verification & fix pass", "status": "pending", "blockedBy": [45]}
|
||||
{"id": 19, "subject": "Task 0: Scaffold + shared assets", "status": "completed"},
|
||||
{"id": 20, "subject": "Task 1: Pilot exemplar AuditLog.md (approval gate)", "status": "completed", "blockedBy": [19]},
|
||||
{"id": 21, "subject": "Task 2: Commons.md reference doc", "status": "completed", "blockedBy": [20]},
|
||||
{"id": 22, "subject": "Task 3: ConfigurationDatabase.md reference doc", "status": "completed", "blockedBy": [20]},
|
||||
{"id": 23, "subject": "Task 4: Communication.md reference doc", "status": "completed", "blockedBy": [20]},
|
||||
{"id": 24, "subject": "Task 5: ClusterInfrastructure.md reference doc", "status": "completed", "blockedBy": [20]},
|
||||
{"id": 25, "subject": "Task 6: Host.md reference doc", "status": "completed", "blockedBy": [20]},
|
||||
{"id": 26, "subject": "Task 7: Security.md reference doc", "status": "completed", "blockedBy": [20]},
|
||||
{"id": 27, "subject": "Task 8: TemplateEngine.md reference doc", "status": "completed", "blockedBy": [20]},
|
||||
{"id": 28, "subject": "Task 9: DeploymentManager.md reference doc", "status": "completed", "blockedBy": [20]},
|
||||
{"id": 29, "subject": "Task 10: SiteRuntime.md reference doc", "status": "completed", "blockedBy": [20]},
|
||||
{"id": 30, "subject": "Task 11: DataConnectionLayer.md reference doc", "status": "completed", "blockedBy": [20]},
|
||||
{"id": 31, "subject": "Task 12: StoreAndForward.md reference doc", "status": "completed", "blockedBy": [20]},
|
||||
{"id": 32, "subject": "Task 13: ExternalSystemGateway.md reference doc", "status": "completed", "blockedBy": [20]},
|
||||
{"id": 33, "subject": "Task 14: NotificationService.md reference doc", "status": "completed", "blockedBy": [20]},
|
||||
{"id": 34, "subject": "Task 15: NotificationOutbox.md reference doc", "status": "completed", "blockedBy": [20]},
|
||||
{"id": 35, "subject": "Task 16: SiteCallAudit.md reference doc", "status": "completed", "blockedBy": [20]},
|
||||
{"id": 36, "subject": "Task 17: HealthMonitoring.md reference doc", "status": "completed", "blockedBy": [20]},
|
||||
{"id": 37, "subject": "Task 18: SiteEventLogging.md reference doc", "status": "completed", "blockedBy": [20]},
|
||||
{"id": 38, "subject": "Task 19: InboundAPI.md reference doc", "status": "completed", "blockedBy": [20]},
|
||||
{"id": 39, "subject": "Task 20: ManagementService.md reference doc", "status": "completed", "blockedBy": [20]},
|
||||
{"id": 40, "subject": "Task 21: CLI.md reference doc", "status": "completed", "blockedBy": [20]},
|
||||
{"id": 41, "subject": "Task 22: Transport.md reference doc", "status": "completed", "blockedBy": [20]},
|
||||
{"id": 42, "subject": "Task 23: CentralUI.md reference doc", "status": "completed", "blockedBy": [20]},
|
||||
{"id": 43, "subject": "Task 24: TraefikProxy.md reference doc", "status": "completed", "blockedBy": [20]},
|
||||
{"id": 44, "subject": "Task 25: TreeView.md reference doc", "status": "completed", "blockedBy": [20]},
|
||||
{"id": 45, "subject": "Task 26: Index + README link", "status": "completed", "blockedBy": [20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44]},
|
||||
{"id": 46, "subject": "Task 27: Verification & fix pass", "status": "completed", "blockedBy": [45]}
|
||||
],
|
||||
"lastUpdated": "2026-06-03"
|
||||
"lastUpdated": "2026-08-01",
|
||||
"status": "2026-08-01 bookkeeping sync: statuses reconciled against merged code"
|
||||
}
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
{
|
||||
"planPath": "docs/plans/2026-06-05-playwright-coverage-expansion.md",
|
||||
"lastUpdated": "2026-06-05T00:00:00Z",
|
||||
"lastUpdated": "2026-08-01T00:00:00Z",
|
||||
"status": "2026-08-01 bookkeeping sync: statuses reconciled against merged code",
|
||||
"nativeTaskIdBase": 57,
|
||||
"tasks": [
|
||||
{"id": 0, "nativeId": 57, "subject": "Task 0: Add CLI ProjectReference to test project", "status": "pending"},
|
||||
{"id": 1, "nativeId": 58, "subject": "Task 1: CliRunner core + ClusterAvailability probe", "status": "pending", "blockedBy": [0]},
|
||||
{"id": 2, "nativeId": 59, "subject": "Task 2: CliRunner typed fixture helpers", "status": "pending", "blockedBy": [1]},
|
||||
{"id": 3, "nativeId": 60, "subject": "Task 3: Standardize skip policy + skip-count logging", "status": "pending", "blockedBy": [1]},
|
||||
{"id": 4, "nativeId": 61, "subject": "Task 4: DeploymentFixture (ephemeral instance on site-a)", "status": "pending", "blockedBy": [2]},
|
||||
{"id": 5, "nativeId": 62, "subject": "Task 5: DeploymentActionTests.Deploy", "status": "pending", "blockedBy": [4]},
|
||||
{"id": 6, "nativeId": 63, "subject": "Task 6: DeploymentActionTests.Enable + Disable", "status": "pending", "blockedBy": [5]},
|
||||
{"id": 7, "nativeId": 64, "subject": "Task 7: DeploymentActionTests.Delete", "status": "pending", "blockedBy": [6]},
|
||||
{"id": 8, "nativeId": 65, "subject": "Task 8: Notification retry/discard + ParkedMessages query", "status": "pending", "blockedBy": [2]},
|
||||
{"id": 9, "nativeId": 66, "subject": "Task 9: Transport Import round-trip", "status": "pending", "blockedBy": [2]},
|
||||
{"id": 10, "nativeId": 67, "subject": "Task 10: Site CRUD round-trip", "status": "pending", "blockedBy": [2]},
|
||||
{"id": 11, "nativeId": 68, "subject": "Task 11: Template CRUD round-trip", "status": "pending", "blockedBy": [2]},
|
||||
{"id": 12, "nativeId": 69, "subject": "Task 12: LDAP mapping CRUD round-trip", "status": "pending", "blockedBy": [1]},
|
||||
{"id": 13, "nativeId": 70, "subject": "Task 13: Navigation render-assertion hardening", "status": "pending"},
|
||||
{"id": 14, "nativeId": 71, "subject": "Task 14: Health KPI load test", "status": "pending", "blockedBy": [1]},
|
||||
{"id": 15, "nativeId": 72, "subject": "Task 15: Full-suite verification + no-residue check", "status": "pending", "blockedBy": [3, 7, 8, 9, 10, 11, 12, 13, 14]}
|
||||
{"id": 0, "nativeId": 57, "subject": "Task 0: Add CLI ProjectReference to test project", "status": "completed"},
|
||||
{"id": 1, "nativeId": 58, "subject": "Task 1: CliRunner core + ClusterAvailability probe", "status": "completed", "blockedBy": [0]},
|
||||
{"id": 2, "nativeId": 59, "subject": "Task 2: CliRunner typed fixture helpers", "status": "completed", "blockedBy": [1]},
|
||||
{"id": 3, "nativeId": 60, "subject": "Task 3: Standardize skip policy + skip-count logging", "status": "completed", "blockedBy": [1]},
|
||||
{"id": 4, "nativeId": 61, "subject": "Task 4: DeploymentFixture (ephemeral instance on site-a)", "status": "completed", "blockedBy": [2]},
|
||||
{"id": 5, "nativeId": 62, "subject": "Task 5: DeploymentActionTests.Deploy", "status": "completed", "blockedBy": [4]},
|
||||
{"id": 6, "nativeId": 63, "subject": "Task 6: DeploymentActionTests.Enable + Disable", "status": "completed", "blockedBy": [5]},
|
||||
{"id": 7, "nativeId": 64, "subject": "Task 7: DeploymentActionTests.Delete", "status": "completed", "blockedBy": [6]},
|
||||
{"id": 8, "nativeId": 65, "subject": "Task 8: Notification retry/discard + ParkedMessages query", "status": "completed", "blockedBy": [2]},
|
||||
{"id": 9, "nativeId": 66, "subject": "Task 9: Transport Import round-trip", "status": "completed", "blockedBy": [2]},
|
||||
{"id": 10, "nativeId": 67, "subject": "Task 10: Site CRUD round-trip", "status": "completed", "blockedBy": [2]},
|
||||
{"id": 11, "nativeId": 68, "subject": "Task 11: Template CRUD round-trip", "status": "completed", "blockedBy": [2]},
|
||||
{"id": 12, "nativeId": 69, "subject": "Task 12: LDAP mapping CRUD round-trip", "status": "completed", "blockedBy": [1]},
|
||||
{"id": 13, "nativeId": 70, "subject": "Task 13: Navigation render-assertion hardening", "status": "completed"},
|
||||
{"id": 14, "nativeId": 71, "subject": "Task 14: Health KPI load test", "status": "completed", "blockedBy": [1]},
|
||||
{"id": 15, "nativeId": 72, "subject": "Task 15: Full-suite verification + no-residue check", "status": "completed", "blockedBy": [3, 7, 8, 9, 10, 11, 12, 13, 14]}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -78,7 +78,10 @@ Wire up behavior that exists in code but is never started, and fill the event-lo
|
||||
#### M5 — Audit hardening (T1–T8)
|
||||
Hash-chain tamper evidence (off by default, `verify-chain` made real); Parquet export/archival (replace the 501); per-channel retention overrides; tag-cascade for `ParentExecutionId` (thread writing-execution id through trigger-driven runs); ExecutionId/ParentExecutionId + SourceNode backfill on historical rows; per-node stuck-count KPIs; structured response capture (headers/content-type, inbound request headers, per-method opt-out, `AuditInboundCeilingHits` metric); CLI `audit tree`.
|
||||
|
||||
#### M6 — KPI History & Trends (T11 delivered; T9/T10 deferred)
|
||||
#### M6 — KPI History & Trends (T11 delivered; T9/T10 **since DELIVERED 2026-06-19**)
|
||||
|
||||
> **2026-08-01 bookkeeping sync:** the T9/T10 "deferred to the next major version" text below is superseded — **T9/T10 were delivered 2026-06-19 as the SMS (Twilio) adapter** (`SmsNotificationDeliveryAdapter`, `NotificationType.Sms`, Central UI Type selector + SMS recipient input; see `docs/plans/2026-06-19-sms-notifications.md`). **Teams was evaluated and dropped by design decision** — outbound-only backends cannot send 1:1 chat bodies via Graph without a Bot Framework inbound endpoint; SMS is inherently per-person and outbound-only.
|
||||
|
||||
Reshaped during the 2026-06-17 brainstorm (see `docs/plans/2026-06-17-m6-kpi-history-design.md`):
|
||||
- **T11 — DELIVERED** as the reusable **KPI-history backbone** (#26 KpiHistory), promoted from a notifications-only feature. A tall/EAV `KpiSample` store in **central MS SQL** (no new infra — supersedes the original "point-in-time only, no time-series store" stance), a `KpiHistoryRecorderActor` cluster singleton (`kpi-history-recorder`, not readiness-gated, best-effort with per-source isolation) sampling DI-registered `IKpiSampleSource`s every minute, a bucketed `GetRawSeriesAsync` + `KpiSeriesBucketer` query + scoped `KpiHistoryQueryService`, and a reusable custom-SVG `KpiTrendChart` (no third-party charting lib). Trends shipped for **all** current KPI sources — Notification Outbox, Site Call Audit, Audit Log, and Site Health — across four UI surfaces.
|
||||
- **T9 (Teams + other non-Email delivery adapters behind `INotificationDeliveryAdapter`) — DEFERRED to the next major version.** The seam exists; no code now. Transport choice (Incoming Webhook vs Microsoft Graph) and the Teams list-targeting model remain to be designed.
|
||||
@@ -132,7 +135,7 @@ This is the **final milestone** of the system-completion roadmap. All in-scope M
|
||||
## Dependencies & sequencing
|
||||
|
||||
- **M1 → M5** — audit hardening builds on the wired purge/reconciliation.
|
||||
- **M6/T11** — delivered as the #26 KpiHistory backbone; reused **central MS SQL** (a tall/EAV `KpiSample` table) rather than introducing new infra. T9/T10 deferred to the next major version.
|
||||
- **M6/T11** — delivered as the #26 KpiHistory backbone; reused **central MS SQL** (a tall/EAV `KpiSample` table) rather than introducing new infra. T9/T10 since delivered 2026-06-19 as the SMS (Twilio) adapter (see note above).
|
||||
- **M9/T26** — base-template versioning is the largest authoring item; may split.
|
||||
- **M4** — runs anytime; cheap and high-clarity, good to interleave.
|
||||
- **M3** — independent; can run in parallel with M1/M2.
|
||||
|
||||
@@ -27,9 +27,10 @@
|
||||
{"ref": "#19", "subject": "script started/completed events", "status": "done in M1.8"}
|
||||
],
|
||||
"followups": [
|
||||
{"id": 52, "subject": "Investigate 2 partition-purge E2E test failures (AuditLogPurgeActor/PartitionPurge)", "from": "M2.0", "status": "pending"},
|
||||
{"id": 53, "subject": "Dedup alarm-capable protocol predicate (3 copies → AlarmCapableProtocols)", "from": "M2.1", "status": "pending"},
|
||||
{"id": 54, "subject": "Expose ExecutionTimeoutSeconds (+ MinTimeBetweenRuns) in CLI + UI script authoring", "from": "M2.5", "status": "pending"}
|
||||
{"id": 52, "subject": "Investigate 2 partition-purge E2E test failures (AuditLogPurgeActor/PartitionPurge)", "from": "M2.0", "status": "resolved", "note": "RESOLVED — commit 639e331d de-dated the 2 EndToEnd purge tests (M5.7)"},
|
||||
{"id": 53, "subject": "Dedup alarm-capable protocol predicate (3 copies → AlarmCapableProtocols)", "from": "M2.1", "status": "resolved", "note": "RESOLVED — shared Commons/Interfaces/Protocol/AlarmCapableProtocols.cs is now the single predicate"},
|
||||
{"id": 54, "subject": "Expose ExecutionTimeoutSeconds (+ MinTimeBetweenRuns) in CLI + UI script authoring", "from": "M2.5", "status": "resolved", "note": "RESOLVED — CLI --execution-timeout-seconds on template script add/update plus TemplateEdit binding"}
|
||||
],
|
||||
"lastUpdated": "2026-06-15"
|
||||
"lastUpdated": "2026-08-01",
|
||||
"status": "2026-08-01 bookkeeping sync: statuses reconciled against merged code"
|
||||
}
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
{
|
||||
"planPath": "docs/plans/2026-06-15-stillpending-phase1-implementation.md",
|
||||
"tasks": [
|
||||
{"id": 22, "subject": "M1.0: Confirm proto/site surface for audit pull (spike)", "status": "pending"},
|
||||
{"id": 23, "subject": "M1.1: Production gRPC IPullAuditEventsClient", "status": "pending", "blockedBy": [22]},
|
||||
{"id": 24, "subject": "M1.2: Wire reconciliation + purge actors as central singletons", "status": "pending"},
|
||||
{"id": 25, "subject": "M1.3: SiteCallAudit periodic reconciliation pull", "status": "pending"},
|
||||
{"id": 26, "subject": "M1.4: SiteCallAudit daily terminal-row purge scheduler", "status": "pending"},
|
||||
{"id": 27, "subject": "M1.5: SiteEventLog — emit Alarm events", "status": "pending"},
|
||||
{"id": 28, "subject": "M1.6: SiteEventLog — Deployment + Instance-lifecycle events", "status": "pending"},
|
||||
{"id": 29, "subject": "M1.7: SiteEventLog — Store-and-Forward + Notification events", "status": "pending"},
|
||||
{"id": 30, "subject": "M1.8: SiteEventLog — script started/completed (Info)", "status": "pending"},
|
||||
{"id": 31, "subject": "M1.9: M1 integration verification + redeploy", "status": "pending", "blockedBy": [23, 24, 25, 26, 27, 28, 29, 30]},
|
||||
{"id": 13, "subject": "M2 — Correctness & behavioral gaps (Tier 2) [umbrella; split per-item at execution]", "status": "pending"},
|
||||
{"id": 14, "subject": "M3 — Script trust boundary (Tier 1 #1-#2) [umbrella]", "status": "pending"},
|
||||
{"id": 15, "subject": "M4 — Doc reconciliation (Tier 4) [umbrella]", "status": "pending"}
|
||||
{"id": 22, "subject": "M1.0: Confirm proto/site surface for audit pull (spike)", "status": "completed"},
|
||||
{"id": 23, "subject": "M1.1: Production gRPC IPullAuditEventsClient", "status": "completed", "blockedBy": [22]},
|
||||
{"id": 24, "subject": "M1.2: Wire reconciliation + purge actors as central singletons", "status": "completed"},
|
||||
{"id": 25, "subject": "M1.3: SiteCallAudit periodic reconciliation pull", "status": "completed"},
|
||||
{"id": 26, "subject": "M1.4: SiteCallAudit daily terminal-row purge scheduler", "status": "completed"},
|
||||
{"id": 27, "subject": "M1.5: SiteEventLog — emit Alarm events", "status": "completed"},
|
||||
{"id": 28, "subject": "M1.6: SiteEventLog — Deployment + Instance-lifecycle events", "status": "completed"},
|
||||
{"id": 29, "subject": "M1.7: SiteEventLog — Store-and-Forward + Notification events", "status": "completed"},
|
||||
{"id": 30, "subject": "M1.8: SiteEventLog — script started/completed (Info)", "status": "completed"},
|
||||
{"id": 31, "subject": "M1.9: M1 integration verification + redeploy", "status": "completed", "blockedBy": [23, 24, 25, 26, 27, 28, 29, 30]},
|
||||
{"id": 13, "subject": "M2 — Correctness & behavioral gaps (Tier 2) [umbrella; split per-item at execution]", "status": "completed"},
|
||||
{"id": 14, "subject": "M3 — Script trust boundary (Tier 1 #1-#2) [umbrella]", "status": "completed"},
|
||||
{"id": 15, "subject": "M4 — Doc reconciliation (Tier 4) [umbrella]", "status": "completed"}
|
||||
],
|
||||
"lastUpdated": "2026-06-15"
|
||||
"lastUpdated": "2026-08-01",
|
||||
"status": "2026-08-01 bookkeeping sync: statuses reconciled against merged code"
|
||||
}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
{
|
||||
"planPath": "docs/plans/2026-06-16-ipsen-mes-movein.md",
|
||||
"tasks": [
|
||||
{"id": 1, "subject": "Task 1: InboundDatabaseHelper (read-only DB for inbound scripts)", "classification": "standard", "status": "pending"},
|
||||
{"id": 2, "subject": "Task 2: Expose Database to inbound scripts (context + executor)", "classification": "high-risk", "status": "pending", "blockedBy": [1]},
|
||||
{"id": 3, "subject": "Task 3: Build + regression-test ScadaBridge", "classification": "small", "status": "pending", "blockedBy": [2]},
|
||||
{"id": 4, "subject": "Task 4: Commit Component A", "classification": "trivial", "status": "pending", "blockedBy": [3]},
|
||||
{"id": 5, "subject": "Task 5: Deploy ScadaBridge Central (Component A) to wonder-app-vd03", "classification": "high-risk", "status": "pending", "blockedBy": [4]},
|
||||
{"id": 6, "subject": "Task 6: Apply Component B - update inbound IpsenMESMoveIn script", "classification": "high-risk", "status": "pending", "blockedBy": [5]},
|
||||
{"id": 7, "subject": "Task 7: Apply Component C - IpsenMoveIn template script on T1", "classification": "high-risk", "status": "pending", "blockedBy": [5]},
|
||||
{"id": 8, "subject": "Task 8: On-box end-to-end verification", "classification": "standard", "status": "pending", "blockedBy": [6, 7]}
|
||||
{"id": 1, "subject": "Task 1: InboundDatabaseHelper (read-only DB for inbound scripts)", "classification": "standard", "status": "completed"},
|
||||
{"id": 2, "subject": "Task 2: Expose Database to inbound scripts (context + executor)", "classification": "high-risk", "status": "completed", "blockedBy": [1]},
|
||||
{"id": 3, "subject": "Task 3: Build + regression-test ScadaBridge", "classification": "small", "status": "completed", "blockedBy": [2]},
|
||||
{"id": 4, "subject": "Task 4: Commit Component A", "classification": "trivial", "status": "completed", "blockedBy": [3]},
|
||||
{"id": 5, "subject": "Task 5: Deploy ScadaBridge Central (Component A) to wonder-app-vd03", "classification": "high-risk", "status": "completed", "note": "done off-repo on wonder-app-vd03 (live methods authored against Database.* fixed there 2026-06-25)", "blockedBy": [4]},
|
||||
{"id": 6, "subject": "Task 6: Apply Component B - update inbound IpsenMESMoveIn script", "classification": "high-risk", "status": "completed", "note": "done off-repo on wonder-app-vd03 (live methods authored against Database.* fixed there 2026-06-25)", "blockedBy": [5]},
|
||||
{"id": 7, "subject": "Task 7: Apply Component C - IpsenMoveIn template script on T1", "classification": "high-risk", "status": "pending", "note": "NEEDS LIVE vd03 verification — tracked in the 2026-08-01 pending-work task list", "blockedBy": [5]},
|
||||
{"id": 8, "subject": "Task 8: On-box end-to-end verification", "classification": "standard", "status": "pending", "note": "NEEDS LIVE vd03 verification — tracked in the 2026-08-01 pending-work task list", "blockedBy": [6, 7]}
|
||||
],
|
||||
"lastUpdated": "2026-06-16"
|
||||
"lastUpdated": "2026-08-01",
|
||||
"status": "2026-08-01 bookkeeping sync: statuses reconciled against merged code"
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
{
|
||||
"planPath": "docs/plans/2026-06-16-m5-audit-hardening.md",
|
||||
"tasks": [
|
||||
{"id": 119, "subject": "M5.1 (T8): CLI audit tree + tree endpoint", "status": "pending"},
|
||||
{"id": 120, "subject": "M5.2 (T6): Per-node stuck-count KPIs", "status": "pending"},
|
||||
{"id": 121, "subject": "M5.3 (T7): Structured response-capture increments", "status": "pending"},
|
||||
{"id": 122, "subject": "M5.4 (T4): ParentExecutionId tag-cascade", "status": "pending"},
|
||||
{"id": 123, "subject": "M5.5 (T3): Per-channel retention overrides", "status": "pending"},
|
||||
{"id": 124, "subject": "M5.6 (T5): SourceNode sentinel backfill + runbook", "status": "pending", "blockedBy": [119]},
|
||||
{"id": 125, "subject": "M5.7: M5 integration verification + docs", "status": "pending", "blockedBy": [119, 120, 121, 122, 123, 124]}
|
||||
{"id": 119, "subject": "M5.1 (T8): CLI audit tree + tree endpoint", "status": "completed"},
|
||||
{"id": 120, "subject": "M5.2 (T6): Per-node stuck-count KPIs", "status": "completed"},
|
||||
{"id": 121, "subject": "M5.3 (T7): Structured response-capture increments", "status": "completed"},
|
||||
{"id": 122, "subject": "M5.4 (T4): ParentExecutionId tag-cascade", "status": "completed"},
|
||||
{"id": 123, "subject": "M5.5 (T3): Per-channel retention overrides", "status": "completed"},
|
||||
{"id": 124, "subject": "M5.6 (T5): SourceNode sentinel backfill + runbook", "status": "completed", "blockedBy": [119]},
|
||||
{"id": 125, "subject": "M5.7: M5 integration verification + docs", "status": "completed", "blockedBy": [119, 120, 121, 122, 123, 124]}
|
||||
],
|
||||
"lastUpdated": "2026-06-16"
|
||||
"lastUpdated": "2026-08-01",
|
||||
"status": "2026-08-01 bookkeeping sync: statuses reconciled against merged code"
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
{
|
||||
"planPath": "docs/plans/2026-06-16-script-analysis-consolidation.md",
|
||||
"tasks": [
|
||||
{"id": 103, "subject": "M3.0: Spike — compile-surface stub feasibility + package refs", "status": "pending"},
|
||||
{"id": 104, "subject": "M3.1: Build ScriptAnalysis project (policy+validator+compiler+surfaces+tests)", "status": "pending", "blockedBy": [103]},
|
||||
{"id": 105, "subject": "M3.2: TemplateEngine deploy gate → shared analyzer", "status": "pending", "blockedBy": [104]},
|
||||
{"id": 106, "subject": "M3.3: SiteRuntime → shared analyzer + parity test", "status": "pending", "blockedBy": [104]},
|
||||
{"id": 107, "subject": "M3.4: InboundAPI → shared analyzer", "status": "pending", "blockedBy": [104]},
|
||||
{"id": 108, "subject": "M3.5: CentralUI → shared analyzer (keep markers + Test-Run host)", "status": "pending", "blockedBy": [104]},
|
||||
{"id": 109, "subject": "M3.6: Integration verification + docs + fixture cleanup", "status": "pending", "blockedBy": [105, 106, 107, 108]}
|
||||
{"id": 103, "subject": "M3.0: Spike — compile-surface stub feasibility + package refs", "status": "completed"},
|
||||
{"id": 104, "subject": "M3.1: Build ScriptAnalysis project (policy+validator+compiler+surfaces+tests)", "status": "completed", "blockedBy": [103]},
|
||||
{"id": 105, "subject": "M3.2: TemplateEngine deploy gate → shared analyzer", "status": "completed", "blockedBy": [104]},
|
||||
{"id": 106, "subject": "M3.3: SiteRuntime → shared analyzer + parity test", "status": "completed", "blockedBy": [104]},
|
||||
{"id": 107, "subject": "M3.4: InboundAPI → shared analyzer", "status": "completed", "blockedBy": [104]},
|
||||
{"id": 108, "subject": "M3.5: CentralUI → shared analyzer (keep markers + Test-Run host)", "status": "completed", "blockedBy": [104]},
|
||||
{"id": 109, "subject": "M3.6: Integration verification + docs + fixture cleanup", "status": "completed", "blockedBy": [105, 106, 107, 108]}
|
||||
],
|
||||
"lastUpdated": "2026-06-16"
|
||||
"lastUpdated": "2026-08-01",
|
||||
"status": "2026-08-01 bookkeeping sync: statuses reconciled against merged code"
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"planPath": "docs/plans/2026-06-17-debugview-tabs-trees.md",
|
||||
"tasks": [
|
||||
{"id": 170, "subject": "DV-1: Native-binding linkage — additive AlarmStateChanged contract chain", "classification": "high-risk", "status": "pending"},
|
||||
{"id": 171, "subject": "DV-2: Site snapshot — placeholder rows for idle native source bindings", "classification": "standard", "status": "pending", "blockedBy": [170]},
|
||||
{"id": 172, "subject": "DV-3: DebugTreeNode model + attribute-tree builder", "classification": "standard", "status": "pending", "blockedBy": [170]},
|
||||
{"id": 173, "subject": "DV-4: Alarm-tree builder — computed leaves + native binding grouping + roll-up", "classification": "standard", "status": "pending", "blockedBy": [172, 170]},
|
||||
{"id": 174, "subject": "DV-5: DebugView page — tabs + two TreeViews + in-place updates", "classification": "standard", "status": "pending", "blockedBy": [173]},
|
||||
{"id": 175, "subject": "DV-6: Documentation — CentralUI + SiteRuntime + streaming contract", "classification": "small", "status": "pending", "blockedBy": [173]},
|
||||
{"id": 176, "subject": "DV-7: Integration — full build, docker rebuild, Playwright, smoke", "classification": "high-risk", "status": "pending", "blockedBy": [174, 175, 171]}
|
||||
{"id": 170, "subject": "DV-1: Native-binding linkage — additive AlarmStateChanged contract chain", "classification": "high-risk", "status": "completed"},
|
||||
{"id": 171, "subject": "DV-2: Site snapshot — placeholder rows for idle native source bindings", "classification": "standard", "status": "completed", "blockedBy": [170]},
|
||||
{"id": 172, "subject": "DV-3: DebugTreeNode model + attribute-tree builder", "classification": "standard", "status": "completed", "blockedBy": [170]},
|
||||
{"id": 173, "subject": "DV-4: Alarm-tree builder — computed leaves + native binding grouping + roll-up", "classification": "standard", "status": "completed", "blockedBy": [172, 170]},
|
||||
{"id": 174, "subject": "DV-5: DebugView page — tabs + two TreeViews + in-place updates", "classification": "standard", "status": "completed", "blockedBy": [173]},
|
||||
{"id": 175, "subject": "DV-6: Documentation — CentralUI + SiteRuntime + streaming contract", "classification": "small", "status": "completed", "blockedBy": [173]},
|
||||
{"id": 176, "subject": "DV-7: Integration — full build, docker rebuild, Playwright, smoke", "classification": "high-risk", "status": "completed", "blockedBy": [174, 175, 171]}
|
||||
],
|
||||
"waves": [
|
||||
{"wave": 1, "tasks": [170]},
|
||||
@@ -16,5 +16,6 @@
|
||||
{"wave": 4, "tasks": [174, 175]},
|
||||
{"wave": 5, "tasks": [176]}
|
||||
],
|
||||
"lastUpdated": "2026-06-17"
|
||||
"lastUpdated": "2026-08-01",
|
||||
"status": "2026-08-01 bookkeeping sync: statuses reconciled against merged code"
|
||||
}
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
{
|
||||
"planPath": "docs/plans/2026-06-17-m6-kpi-history.md",
|
||||
"tasks": [
|
||||
{"id": 1, "subject": "K1: Foundation contracts (Commons)", "nativeId": 136, "classification": "high-risk", "status": "pending"},
|
||||
{"id": 2, "subject": "K2: Persistence — EF config + repository + migration", "nativeId": 137, "classification": "high-risk", "status": "pending", "blockedBy": [1]},
|
||||
{"id": 3, "subject": "K3: KpiHistory project scaffold + options", "nativeId": 138, "classification": "standard", "status": "pending", "blockedBy": [1]},
|
||||
{"id": 4, "subject": "K4: KpiHistoryRecorderActor", "nativeId": 139, "classification": "high-risk", "status": "pending", "blockedBy": [3]},
|
||||
{"id": 5, "subject": "K5: Host wiring + appsettings", "nativeId": 140, "classification": "high-risk", "status": "pending", "blockedBy": [4]},
|
||||
{"id": 6, "subject": "K6: NotificationOutboxKpiSampleSource", "nativeId": 141, "classification": "small", "status": "pending", "blockedBy": [1]},
|
||||
{"id": 7, "subject": "K7: SiteCallAuditKpiSampleSource", "nativeId": 142, "classification": "small", "status": "pending", "blockedBy": [1]},
|
||||
{"id": 8, "subject": "K8: AuditLogKpiSampleSource", "nativeId": 143, "classification": "small", "status": "pending", "blockedBy": [1]},
|
||||
{"id": 9, "subject": "K9: SiteHealthKpiSampleSource", "nativeId": 144, "classification": "standard", "status": "pending", "blockedBy": [1]},
|
||||
{"id": 10, "subject": "K10: KpiSeriesBucketer (pure helper)", "nativeId": 145, "classification": "small", "status": "pending", "blockedBy": [1]},
|
||||
{"id": 11, "subject": "K11: KpiHistoryQueryService (CentralUI)", "nativeId": 146, "classification": "standard", "status": "pending", "blockedBy": [2, 10]},
|
||||
{"id": 12, "subject": "K12: KpiTrendChart.razor reusable SVG component", "nativeId": 147, "classification": "standard", "status": "pending", "blockedBy": [1]},
|
||||
{"id": 13, "subject": "K13: Notification Outbox page trend section", "nativeId": 148, "classification": "standard", "status": "pending", "blockedBy": [11, 12]},
|
||||
{"id": 14, "subject": "K14: Site Calls page trend section", "nativeId": 149, "classification": "standard", "status": "pending", "blockedBy": [11, 12]},
|
||||
{"id": 15, "subject": "K15: Audit Log page trend section", "nativeId": 150, "classification": "standard", "status": "pending", "blockedBy": [11, 12]},
|
||||
{"id": 16, "subject": "K16: Health dashboard per-site trend panel", "nativeId": 151, "classification": "standard", "status": "pending", "blockedBy": [11, 12]},
|
||||
{"id": 17, "subject": "K17: Integration — docs, deploy, Playwright, full verification", "nativeId": 152, "classification": "high-risk", "status": "pending", "blockedBy": [5, 6, 7, 8, 9, 13, 14, 15, 16]}
|
||||
{"id": 1, "subject": "K1: Foundation contracts (Commons)", "nativeId": 136, "classification": "high-risk", "status": "completed"},
|
||||
{"id": 2, "subject": "K2: Persistence — EF config + repository + migration", "nativeId": 137, "classification": "high-risk", "status": "completed", "blockedBy": [1]},
|
||||
{"id": 3, "subject": "K3: KpiHistory project scaffold + options", "nativeId": 138, "classification": "standard", "status": "completed", "blockedBy": [1]},
|
||||
{"id": 4, "subject": "K4: KpiHistoryRecorderActor", "nativeId": 139, "classification": "high-risk", "status": "completed", "blockedBy": [3]},
|
||||
{"id": 5, "subject": "K5: Host wiring + appsettings", "nativeId": 140, "classification": "high-risk", "status": "completed", "blockedBy": [4]},
|
||||
{"id": 6, "subject": "K6: NotificationOutboxKpiSampleSource", "nativeId": 141, "classification": "small", "status": "completed", "blockedBy": [1]},
|
||||
{"id": 7, "subject": "K7: SiteCallAuditKpiSampleSource", "nativeId": 142, "classification": "small", "status": "completed", "blockedBy": [1]},
|
||||
{"id": 8, "subject": "K8: AuditLogKpiSampleSource", "nativeId": 143, "classification": "small", "status": "completed", "blockedBy": [1]},
|
||||
{"id": 9, "subject": "K9: SiteHealthKpiSampleSource", "nativeId": 144, "classification": "standard", "status": "completed", "blockedBy": [1]},
|
||||
{"id": 10, "subject": "K10: KpiSeriesBucketer (pure helper)", "nativeId": 145, "classification": "small", "status": "completed", "blockedBy": [1]},
|
||||
{"id": 11, "subject": "K11: KpiHistoryQueryService (CentralUI)", "nativeId": 146, "classification": "standard", "status": "completed", "blockedBy": [2, 10]},
|
||||
{"id": 12, "subject": "K12: KpiTrendChart.razor reusable SVG component", "nativeId": 147, "classification": "standard", "status": "completed", "blockedBy": [1]},
|
||||
{"id": 13, "subject": "K13: Notification Outbox page trend section", "nativeId": 148, "classification": "standard", "status": "completed", "blockedBy": [11, 12]},
|
||||
{"id": 14, "subject": "K14: Site Calls page trend section", "nativeId": 149, "classification": "standard", "status": "completed", "blockedBy": [11, 12]},
|
||||
{"id": 15, "subject": "K15: Audit Log page trend section", "nativeId": 150, "classification": "standard", "status": "completed", "blockedBy": [11, 12]},
|
||||
{"id": 16, "subject": "K16: Health dashboard per-site trend panel", "nativeId": 151, "classification": "standard", "status": "completed", "blockedBy": [11, 12]},
|
||||
{"id": 17, "subject": "K17: Integration — docs, deploy, Playwright, full verification", "nativeId": 152, "classification": "high-risk", "status": "completed", "blockedBy": [5, 6, 7, 8, 9, 13, 14, 15, 16]}
|
||||
],
|
||||
"lastUpdated": "2026-06-17"
|
||||
"lastUpdated": "2026-08-01",
|
||||
"status": "2026-08-01 bookkeeping sync: statuses reconciled against merged code"
|
||||
}
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
],
|
||||
"reviewPolishCommit": "8dcc55f",
|
||||
"followUps": [
|
||||
{"id": 162, "subject": "normalize routed GetAttributes List values for cross-process transport (surfaced by WS-4 review)"}
|
||||
{"id": 162, "subject": "normalize routed GetAttributes List values for cross-process transport (surfaced by WS-4 review)", "status": "resolved", "note": "RESOLVED — DeploymentManagerActor.cs RouteInboundApiGetAttributes now normalizes via NormalizeRoutedReturnValue"}
|
||||
],
|
||||
"lastUpdated": "2026-06-17"
|
||||
"lastUpdated": "2026-08-01",
|
||||
"status": "2026-08-01 bookkeeping sync: statuses reconciled against merged code"
|
||||
}
|
||||
|
||||
@@ -239,7 +239,9 @@ fully cover the DELMIA/MES use case.
|
||||
> **Status:** IMPLEMENTED. `Route.To(code).WaitForAttribute(name, targetValue, timeout)` is wired
|
||||
> end-to-end (`RouteToWaitForAttributeRequest/Response` → `IInstanceRouter` → `CommunicationService`
|
||||
> → `SiteCommunicationActor` → `DeploymentManagerActor` → `InstanceActor`), value-equality only
|
||||
> across the wire. NOT wired into the CentralUI Test-Run sandbox — that remains a follow-up.
|
||||
> across the wire. ~~NOT wired into the CentralUI Test-Run sandbox — that remains a follow-up.~~
|
||||
> **Follow-up SHIPPED 2026-07-10** — the CentralUI Test-Run sandbox is wired via
|
||||
> `SandboxInstanceGateway`/`SandboxScriptHost` (deferred-work register row #14 resolved).
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
{
|
||||
"planPath": "docs/plans/2026-06-17-waitfor-deferred-items.md",
|
||||
"tasks": [
|
||||
{"id": 1, "subject": "WD-1: site-local WaitForAsync + WaitResult + quality-gated mode (§3+§4.2)", "classification": "high-risk", "status": "pending", "parallelizableWith": [2]},
|
||||
{"id": 2, "subject": "WD-2a: routed contract + central path (§6 part 1)", "classification": "high-risk", "status": "pending", "parallelizableWith": [1]},
|
||||
{"id": 3, "subject": "WD-2b: site unpacking + DeploymentManager handler (§6 part 2)", "classification": "high-risk", "status": "pending", "blockedBy": [2]},
|
||||
{"id": 4, "subject": "WD-3: integration — docs + full verification", "classification": "standard", "status": "pending", "blockedBy": [1, 2, 3]}
|
||||
{"id": 1, "subject": "WD-1: site-local WaitForAsync + WaitResult + quality-gated mode (§3+§4.2)", "classification": "high-risk", "status": "completed", "parallelizableWith": [2]},
|
||||
{"id": 2, "subject": "WD-2a: routed contract + central path (§6 part 1)", "classification": "high-risk", "status": "completed", "parallelizableWith": [1]},
|
||||
{"id": 3, "subject": "WD-2b: site unpacking + DeploymentManager handler (§6 part 2)", "classification": "high-risk", "status": "completed", "blockedBy": [2]},
|
||||
{"id": 4, "subject": "WD-3: integration — docs + full verification", "classification": "standard", "status": "completed", "blockedBy": [1, 2, 3]}
|
||||
],
|
||||
"lastUpdated": "2026-06-17"
|
||||
"lastUpdated": "2026-08-01",
|
||||
"status": "2026-08-01 bookkeeping sync: statuses reconciled against merged code"
|
||||
}
|
||||
|
||||
@@ -464,7 +464,7 @@ Markup: two `<input type="datetime-local">` with `id="@(IdPrefix)-from"`/`-to`,
|
||||
- Theme-package side-rail dark theming, IF the spike verdict is "rail stays light" (coordination follow-up).
|
||||
|
||||
## Follow-ups logged at delivery (INT findings)
|
||||
- **#207 (pre-existing, open since M6/K14):** `QueryStringDrillInTests` fixture does not register `IKpiHistoryQueryService` — 3 `SiteCallsReport` drill-in tests red on this gap; unrelated to M10.
|
||||
- **#207 — FIXED (2026-08-01 bookkeeping sync).** ~~(pre-existing, open since M6/K14):~~ `QueryStringDrillInTests` now registers the `IKpiHistoryQueryService` substitute in its fixture (lines ~165-167), so the 3 `SiteCallsReport` drill-in tests are green.
|
||||
- **#163 (pre-existing):** `InstanceConfigureListOverrideTests` codec roundtrip red; pre-dates M10.
|
||||
- **NotificationReport `OffsetPager` always-visible:** pager is now always visible when results exist (previously hidden on sub-page-size sets); buttons are correctly disabled on a single page — product decision whether to re-add an `@if (_totalCount > _pageSize)` guard.
|
||||
- **`Deployments.razor` PagerWindow intentionally kept:** windowed numbered-button UX is deliberate; NOT migrated to `OffsetPager`.
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
{
|
||||
"planPath": "docs/plans/2026-06-18-m10-uiux-platform.md",
|
||||
"tasks": [
|
||||
{"id": 269, "subject": "M10-T33a: Modal host ShowAsync<T> + focus trap/restore + backdrop hook", "status": "pending"},
|
||||
{"id": 270, "subject": "M10-T34-spike: Verify ZB.MOM.WW.Theme dark-mode feasibility", "status": "pending"},
|
||||
{"id": 271, "subject": "M10-T41: Alarm-override Playwright trigger-config scenarios", "status": "pending"},
|
||||
{"id": 272, "subject": "M10-T33b: Migrate 5 simple dialogs to ShowAsync<T>", "status": "pending", "blockedBy": [269]},
|
||||
{"id": 273, "subject": "M10-T34a: Dark token layer in site.css + .sb-modal-backdrop", "status": "pending", "blockedBy": [270]},
|
||||
{"id": 274, "subject": "M10-T35a: OffsetPager component", "status": "pending"},
|
||||
{"id": 275, "subject": "M10-T35b: KeysetPager component", "status": "pending"},
|
||||
{"id": 276, "subject": "M10-T35c: DateTimeRangeFilter component", "status": "pending"},
|
||||
{"id": 277, "subject": "M10-T35d: Adopt OffsetPager+DateTimeRangeFilter into NotificationReport (+backdrop token)", "status": "pending", "blockedBy": [274, 276, 273]},
|
||||
{"id": 278, "subject": "M10-T35e: Adopt OffsetPager+DateTimeRangeFilter into ConfigurationAuditLog", "status": "pending", "blockedBy": [274, 276]},
|
||||
{"id": 279, "subject": "M10-T35f: Adopt KeysetPager+DateTimeRangeFilter into SiteCallsReport (+backdrop token)", "status": "pending", "blockedBy": [275, 276, 273]},
|
||||
{"id": 280, "subject": "M10-T35g: Adopt KeysetPager into AuditResultsGrid + DateTimeRangeFilter into AuditFilterBar", "status": "pending", "blockedBy": [275, 276]},
|
||||
{"id": 281, "subject": "M10-T35h: Adopt DateTimeRangeFilter into EventLogs", "status": "pending", "blockedBy": [276]},
|
||||
{"id": 282, "subject": "M10-T34c: Tokenize remaining standalone backdrops + bg-* audit", "status": "pending", "blockedBy": [273]},
|
||||
{"id": 283, "subject": "M10-T34b: Dark-mode toggle + persistence + SSR hydration", "status": "pending", "blockedBy": [270, 273]},
|
||||
{"id": 284, "subject": "M10-T36a: Accessibility pass — TreeView chevron + icon audit + toast verify", "status": "pending"},
|
||||
{"id": 285, "subject": "M10-INT: Integration — build, docker, Playwright, a11y+dark smoke, docs, review", "status": "pending", "blockedBy": [271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284]}
|
||||
{"id": 269, "subject": "M10-T33a: Modal host ShowAsync<T> + focus trap/restore + backdrop hook", "status": "completed"},
|
||||
{"id": 270, "subject": "M10-T34-spike: Verify ZB.MOM.WW.Theme dark-mode feasibility", "status": "completed"},
|
||||
{"id": 271, "subject": "M10-T41: Alarm-override Playwright trigger-config scenarios", "status": "completed"},
|
||||
{"id": 272, "subject": "M10-T33b: Migrate 5 simple dialogs to ShowAsync<T>", "status": "completed", "blockedBy": [269]},
|
||||
{"id": 273, "subject": "M10-T34a: Dark token layer in site.css + .sb-modal-backdrop", "status": "completed", "blockedBy": [270]},
|
||||
{"id": 274, "subject": "M10-T35a: OffsetPager component", "status": "completed"},
|
||||
{"id": 275, "subject": "M10-T35b: KeysetPager component", "status": "completed"},
|
||||
{"id": 276, "subject": "M10-T35c: DateTimeRangeFilter component", "status": "completed"},
|
||||
{"id": 277, "subject": "M10-T35d: Adopt OffsetPager+DateTimeRangeFilter into NotificationReport (+backdrop token)", "status": "completed", "blockedBy": [274, 276, 273]},
|
||||
{"id": 278, "subject": "M10-T35e: Adopt OffsetPager+DateTimeRangeFilter into ConfigurationAuditLog", "status": "completed", "blockedBy": [274, 276]},
|
||||
{"id": 279, "subject": "M10-T35f: Adopt KeysetPager+DateTimeRangeFilter into SiteCallsReport (+backdrop token)", "status": "completed", "blockedBy": [275, 276, 273]},
|
||||
{"id": 280, "subject": "M10-T35g: Adopt KeysetPager into AuditResultsGrid + DateTimeRangeFilter into AuditFilterBar", "status": "completed", "blockedBy": [275, 276]},
|
||||
{"id": 281, "subject": "M10-T35h: Adopt DateTimeRangeFilter into EventLogs", "status": "completed", "blockedBy": [276]},
|
||||
{"id": 282, "subject": "M10-T34c: Tokenize remaining standalone backdrops + bg-* audit", "status": "completed", "blockedBy": [273]},
|
||||
{"id": 283, "subject": "M10-T34b: Dark-mode toggle + persistence + SSR hydration", "status": "completed", "blockedBy": [270, 273]},
|
||||
{"id": 284, "subject": "M10-T36a: Accessibility pass — TreeView chevron + icon audit + toast verify", "status": "completed"},
|
||||
{"id": 285, "subject": "M10-INT: Integration — build, docker, Playwright, a11y+dark smoke, docs, review", "status": "completed", "blockedBy": [271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284]}
|
||||
],
|
||||
"lastUpdated": "2026-06-18"
|
||||
"lastUpdated": "2026-08-01",
|
||||
"status": "2026-08-01 bookkeeping sync: statuses reconciled against merged code"
|
||||
}
|
||||
|
||||
@@ -4,28 +4,28 @@
|
||||
"branch": "worktree-m7-opcua-mxgateway-ux",
|
||||
"baseSha": "254e0e7",
|
||||
"tasks": [
|
||||
{"id": 184, "subject": "M7-A1: Extract AlarmStateBadges shared component", "classification": "standard", "status": "pending"},
|
||||
{"id": 185, "subject": "M7-A2: Operator Alarm Summary page + fan-out service", "classification": "standard", "status": "pending", "blockedBy": [184]},
|
||||
{"id": 186, "subject": "M7-A3: Operator + Verifier roles + policies + LDAP mapping", "classification": "high-risk", "status": "pending"},
|
||||
{"id": 187, "subject": "M7-B1: Browse type-info fields on BrowseNode", "classification": "standard", "status": "pending"},
|
||||
{"id": 188, "subject": "M7-B2: BrowseNext continuation through browse contract", "classification": "high-risk", "status": "pending", "blockedBy": [187]},
|
||||
{"id": 189, "subject": "M7-B3: Thread continuation token through browse plumbing", "classification": "standard", "status": "pending", "blockedBy": [188]},
|
||||
{"id": 190, "subject": "M7-B4: Bounded recursive address-space search — adapter", "classification": "high-risk", "status": "pending", "blockedBy": [188]},
|
||||
{"id": 191, "subject": "M7-B5: Search plumbing — message + actor + comm + service", "classification": "standard", "status": "pending", "blockedBy": [189, 190]},
|
||||
{"id": 192, "subject": "M7-B6: NodeBrowserDialog — load-more + search box + type column", "classification": "standard", "status": "pending", "blockedBy": [189, 191]},
|
||||
{"id": 193, "subject": "M7-B7: Verify-endpoint — message + site probe handler", "classification": "high-risk", "status": "pending", "blockedBy": [191]},
|
||||
{"id": 194, "subject": "M7-B8: Verify-endpoint plumbing + UI", "classification": "standard", "status": "pending", "blockedBy": [193]},
|
||||
{"id": 195, "subject": "M7-B9: Cert trust — per-node CertStore actor + broadcast", "classification": "high-risk", "status": "pending", "blockedBy": [193]},
|
||||
{"id": 196, "subject": "M7-B10: Cert trust plumbing + cert-management UI", "classification": "standard", "status": "pending", "blockedBy": [194, 195]},
|
||||
{"id": 197, "subject": "M7-C1: PendingSecuredWrite entity + persistence + migration", "classification": "high-risk", "status": "pending"},
|
||||
{"id": 198, "subject": "M7-C2: Secured-write commands + submit/reject/list handlers", "classification": "high-risk", "status": "pending", "blockedBy": [197, 186]},
|
||||
{"id": 199, "subject": "M7-C3: Approve → site write relay", "classification": "high-risk", "status": "pending", "blockedBy": [198]},
|
||||
{"id": 200, "subject": "M7-C4: AuditKind.SecuredWrite + audit wiring", "classification": "high-risk", "status": "pending", "blockedBy": [198, 199]},
|
||||
{"id": 201, "subject": "M7-C5: Secured Writes Central UI page", "classification": "standard", "status": "pending", "blockedBy": [198, 199, 186]},
|
||||
{"id": 202, "subject": "M7-D1: OverrideCsvParser pure helper", "classification": "standard", "status": "pending"},
|
||||
{"id": 203, "subject": "M7-D2: InstanceConfigure CSV import UI", "classification": "standard", "status": "pending", "blockedBy": [202]},
|
||||
{"id": 204, "subject": "M7-D3: CLI instance import-overrides --file", "classification": "small", "status": "pending", "blockedBy": [202]},
|
||||
{"id": 205, "subject": "M7-E1: Integration — docs, full build, docker rebuild, Playwright, smoke", "classification": "high-risk", "status": "pending", "blockedBy": [184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204]}
|
||||
{"id": 184, "subject": "M7-A1: Extract AlarmStateBadges shared component", "classification": "standard", "status": "completed"},
|
||||
{"id": 185, "subject": "M7-A2: Operator Alarm Summary page + fan-out service", "classification": "standard", "status": "completed", "blockedBy": [184]},
|
||||
{"id": 186, "subject": "M7-A3: Operator + Verifier roles + policies + LDAP mapping", "classification": "high-risk", "status": "completed"},
|
||||
{"id": 187, "subject": "M7-B1: Browse type-info fields on BrowseNode", "classification": "standard", "status": "completed"},
|
||||
{"id": 188, "subject": "M7-B2: BrowseNext continuation through browse contract", "classification": "high-risk", "status": "completed", "blockedBy": [187]},
|
||||
{"id": 189, "subject": "M7-B3: Thread continuation token through browse plumbing", "classification": "standard", "status": "completed", "blockedBy": [188]},
|
||||
{"id": 190, "subject": "M7-B4: Bounded recursive address-space search — adapter", "classification": "high-risk", "status": "completed", "blockedBy": [188]},
|
||||
{"id": 191, "subject": "M7-B5: Search plumbing — message + actor + comm + service", "classification": "standard", "status": "completed", "blockedBy": [189, 190]},
|
||||
{"id": 192, "subject": "M7-B6: NodeBrowserDialog — load-more + search box + type column", "classification": "standard", "status": "completed", "blockedBy": [189, 191]},
|
||||
{"id": 193, "subject": "M7-B7: Verify-endpoint — message + site probe handler", "classification": "high-risk", "status": "completed", "blockedBy": [191]},
|
||||
{"id": 194, "subject": "M7-B8: Verify-endpoint plumbing + UI", "classification": "standard", "status": "completed", "blockedBy": [193]},
|
||||
{"id": 195, "subject": "M7-B9: Cert trust — per-node CertStore actor + broadcast", "classification": "high-risk", "status": "completed", "blockedBy": [193]},
|
||||
{"id": 196, "subject": "M7-B10: Cert trust plumbing + cert-management UI", "classification": "standard", "status": "completed", "blockedBy": [194, 195]},
|
||||
{"id": 197, "subject": "M7-C1: PendingSecuredWrite entity + persistence + migration", "classification": "high-risk", "status": "completed"},
|
||||
{"id": 198, "subject": "M7-C2: Secured-write commands + submit/reject/list handlers", "classification": "high-risk", "status": "completed", "blockedBy": [197, 186]},
|
||||
{"id": 199, "subject": "M7-C3: Approve → site write relay", "classification": "high-risk", "status": "completed", "blockedBy": [198]},
|
||||
{"id": 200, "subject": "M7-C4: AuditKind.SecuredWrite + audit wiring", "classification": "high-risk", "status": "completed", "blockedBy": [198, 199]},
|
||||
{"id": 201, "subject": "M7-C5: Secured Writes Central UI page", "classification": "standard", "status": "completed", "blockedBy": [198, 199, 186]},
|
||||
{"id": 202, "subject": "M7-D1: OverrideCsvParser pure helper", "classification": "standard", "status": "completed"},
|
||||
{"id": 203, "subject": "M7-D2: InstanceConfigure CSV import UI", "classification": "standard", "status": "completed", "blockedBy": [202]},
|
||||
{"id": 204, "subject": "M7-D3: CLI instance import-overrides --file", "classification": "small", "status": "completed", "blockedBy": [202]},
|
||||
{"id": 205, "subject": "M7-E1: Integration — docs, full build, docker rebuild, Playwright, smoke", "classification": "high-risk", "status": "completed", "blockedBy": [184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204]}
|
||||
],
|
||||
"waves": {
|
||||
"A": [184, 185, 186],
|
||||
@@ -34,5 +34,6 @@
|
||||
"D": [202, 203, 204],
|
||||
"E": [205]
|
||||
},
|
||||
"lastUpdated": "2026-06-18"
|
||||
"lastUpdated": "2026-08-01",
|
||||
"status": "2026-08-01 bookkeeping sync: statuses reconciled against merged code"
|
||||
}
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
{
|
||||
"planPath": "docs/plans/2026-06-18-m8-transport.md",
|
||||
"tasks": [
|
||||
{"id": 210, "subject": "M8-A1: Commons name-map types + preview/selection/summary extensions", "status": "pending"},
|
||||
{"id": 211, "subject": "M8-A2: Transport bundle DTOs — Site/DataConnection/Instance + BundleContentDto", "status": "pending"},
|
||||
{"id": 212, "subject": "M8-A3: LineDiffer — pure Myers per-line diff helper (T20)", "status": "pending"},
|
||||
{"id": 213, "subject": "M8-B1: DependencyResolver site/instance expansion", "status": "pending", "blockedBy": [211]},
|
||||
{"id": 214, "subject": "M8-B2: EntitySerializer site/connection/instance mapping (both directions)", "status": "pending", "blockedBy": [211]},
|
||||
{"id": 215, "subject": "M8-B3: ManifestBuilder + summary counts + schemaVersion 1.1", "status": "pending", "blockedBy": [210]},
|
||||
{"id": 216, "subject": "M8-B4: Export plumbing — selection wiring, command, ManagementActor, CLI", "status": "pending", "blockedBy": [213, 214, 215]},
|
||||
{"id": 217, "subject": "M8-C1: ArtifactDiff — Myers code diff (T20) + Site/Connection/Instance compares", "status": "pending", "blockedBy": [211, 212]},
|
||||
{"id": 218, "subject": "M8-C2: BundleImporter.PreviewAsync — new-type diff + required-mapping detection + blockers", "status": "pending", "blockedBy": [214, 217]},
|
||||
{"id": 219, "subject": "M8-D1: BundleImporter.ApplyAsync — nameMap, resolve-or-create, instance upsert + FK rewire", "status": "pending", "blockedBy": [218]},
|
||||
{"id": 220, "subject": "M8-D2: #16 — real stale-instance enumeration in ImportResult", "status": "pending", "blockedBy": [219]},
|
||||
{"id": 221, "subject": "M8-D3: Import plumbing — name-map through command, ManagementActor, CLI", "status": "pending", "blockedBy": [219, 216]},
|
||||
{"id": 222, "subject": "M8-E1: Export wizard — Sites/Instances selection", "status": "pending", "blockedBy": [216]},
|
||||
{"id": 223, "subject": "M8-E2: Import wizard — Map step + Modified +/- diff render", "status": "pending", "blockedBy": [221]},
|
||||
{"id": 224, "subject": "M8-INT: Docs, full build, docker rebuild, Playwright, live smoke, end-to-end trace", "status": "pending", "blockedBy": [222, 223]}
|
||||
{"id": 210, "subject": "M8-A1: Commons name-map types + preview/selection/summary extensions", "status": "completed"},
|
||||
{"id": 211, "subject": "M8-A2: Transport bundle DTOs — Site/DataConnection/Instance + BundleContentDto", "status": "completed"},
|
||||
{"id": 212, "subject": "M8-A3: LineDiffer — pure Myers per-line diff helper (T20)", "status": "completed"},
|
||||
{"id": 213, "subject": "M8-B1: DependencyResolver site/instance expansion", "status": "completed", "blockedBy": [211]},
|
||||
{"id": 214, "subject": "M8-B2: EntitySerializer site/connection/instance mapping (both directions)", "status": "completed", "blockedBy": [211]},
|
||||
{"id": 215, "subject": "M8-B3: ManifestBuilder + summary counts + schemaVersion 1.1", "status": "completed", "blockedBy": [210]},
|
||||
{"id": 216, "subject": "M8-B4: Export plumbing — selection wiring, command, ManagementActor, CLI", "status": "completed", "blockedBy": [213, 214, 215]},
|
||||
{"id": 217, "subject": "M8-C1: ArtifactDiff — Myers code diff (T20) + Site/Connection/Instance compares", "status": "completed", "blockedBy": [211, 212]},
|
||||
{"id": 218, "subject": "M8-C2: BundleImporter.PreviewAsync — new-type diff + required-mapping detection + blockers", "status": "completed", "blockedBy": [214, 217]},
|
||||
{"id": 219, "subject": "M8-D1: BundleImporter.ApplyAsync — nameMap, resolve-or-create, instance upsert + FK rewire", "status": "completed", "blockedBy": [218]},
|
||||
{"id": 220, "subject": "M8-D2: #16 — real stale-instance enumeration in ImportResult", "status": "completed", "blockedBy": [219]},
|
||||
{"id": 221, "subject": "M8-D3: Import plumbing — name-map through command, ManagementActor, CLI", "status": "completed", "blockedBy": [219, 216]},
|
||||
{"id": 222, "subject": "M8-E1: Export wizard — Sites/Instances selection", "status": "completed", "blockedBy": [216]},
|
||||
{"id": 223, "subject": "M8-E2: Import wizard — Map step + Modified +/- diff render", "status": "completed", "blockedBy": [221]},
|
||||
{"id": 224, "subject": "M8-INT: Docs, full build, docker rebuild, Playwright, live smoke, end-to-end trace", "status": "completed", "blockedBy": [222, 223]}
|
||||
],
|
||||
"lastUpdated": "2026-06-18"
|
||||
"lastUpdated": "2026-08-01",
|
||||
"status": "2026-08-01 bookkeeping sync: statuses reconciled against merged code"
|
||||
}
|
||||
|
||||
@@ -4,23 +4,23 @@
|
||||
"branch": "worktree-m9-templates-authoring",
|
||||
"baseRef": "72aec3b4",
|
||||
"tasks": [
|
||||
{"id": 240, "label": "T22", "subject": "M9-T22: Template tree search box", "classification": "small", "wave": 1, "status": "pending"},
|
||||
{"id": 241, "label": "CLI", "subject": "M9-CLI: cached-call retry/discard CLI group", "classification": "small", "wave": 1, "status": "pending"},
|
||||
{"id": 242, "label": "T28a", "subject": "M9-T28a: Strict expression-trigger kind — backend", "classification": "small", "wave": 1, "status": "pending"},
|
||||
{"id": 243, "label": "T28b", "subject": "M9-T28b: Strict trigger-kind — UI selector + CLI flag", "classification": "small", "wave": 1, "status": "pending", "blockedBy": [242]},
|
||||
{"id": 244, "label": "T23a", "subject": "M9-T23a: Folder sibling reorder — service + command + handler", "classification": "standard", "wave": 2, "status": "pending"},
|
||||
{"id": 245, "label": "T23b", "subject": "M9-T23b: Folder reorder + root context menu — UI", "classification": "standard", "wave": 2, "status": "pending", "blockedBy": [240, 244]},
|
||||
{"id": 246, "label": "T25", "subject": "M9-T25: Connection live-status indicators", "classification": "standard", "wave": 2, "status": "pending"},
|
||||
{"id": 247, "label": "T24a", "subject": "M9-T24a: Move data connection between sites — command + handler + guards", "classification": "high-risk", "wave": 3, "status": "pending"},
|
||||
{"id": 248, "label": "T24b", "subject": "M9-T24b: Move connection — UI dialog + action", "classification": "standard", "wave": 3, "status": "pending", "blockedBy": [247, 246]},
|
||||
{"id": 249, "label": "T32a", "subject": "M9-T32a: SharedSchema entity + EF config + migration + repo", "classification": "high-risk", "wave": 3, "status": "pending"},
|
||||
{"id": 250, "label": "T32b", "subject": "M9-T32b: JSON Schema $ref resolver + deploy-time validation", "classification": "high-risk", "wave": 3, "status": "pending", "blockedBy": [249]},
|
||||
{"id": 251, "label": "T32c", "subject": "M9-T32c: Schema library — CRUD commands + handlers + Central UI page", "classification": "high-risk", "wave": 4, "status": "pending", "blockedBy": [249, 250]},
|
||||
{"id": 252, "label": "T30", "subject": "M9-T30: Schema-driven nested value-entry forms", "classification": "standard", "wave": 4, "status": "pending", "blockedBy": [250]},
|
||||
{"id": 253, "label": "T31", "subject": "M9-T31: Monaco JSON-Schema hover/completion", "classification": "standard", "wave": 4, "status": "pending", "blockedBy": [250, 252]},
|
||||
{"id": 254, "label": "T26a", "subject": "M9-T26a: Inheritance resolve service + query command", "classification": "high-risk", "wave": 4, "status": "pending"},
|
||||
{"id": 255, "label": "T26b", "subject": "M9-T26b: TemplateEdit — full inherited set + staleness banner", "classification": "standard", "wave": 4, "status": "pending", "blockedBy": [254]},
|
||||
{"id": 256, "label": "INT", "subject": "M9-INT: Integration — build, docker, Playwright, smoke, end-to-end trace", "classification": "high-risk", "wave": 5, "status": "pending", "blockedBy": [240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255]}
|
||||
{"id": 240, "label": "T22", "subject": "M9-T22: Template tree search box", "classification": "small", "wave": 1, "status": "completed"},
|
||||
{"id": 241, "label": "CLI", "subject": "M9-CLI: cached-call retry/discard CLI group", "classification": "small", "wave": 1, "status": "completed"},
|
||||
{"id": 242, "label": "T28a", "subject": "M9-T28a: Strict expression-trigger kind — backend", "classification": "small", "wave": 1, "status": "completed"},
|
||||
{"id": 243, "label": "T28b", "subject": "M9-T28b: Strict trigger-kind — UI selector + CLI flag", "classification": "small", "wave": 1, "status": "completed", "blockedBy": [242]},
|
||||
{"id": 244, "label": "T23a", "subject": "M9-T23a: Folder sibling reorder — service + command + handler", "classification": "standard", "wave": 2, "status": "completed"},
|
||||
{"id": 245, "label": "T23b", "subject": "M9-T23b: Folder reorder + root context menu — UI", "classification": "standard", "wave": 2, "status": "completed", "blockedBy": [240, 244]},
|
||||
{"id": 246, "label": "T25", "subject": "M9-T25: Connection live-status indicators", "classification": "standard", "wave": 2, "status": "completed"},
|
||||
{"id": 247, "label": "T24a", "subject": "M9-T24a: Move data connection between sites — command + handler + guards", "classification": "high-risk", "wave": 3, "status": "completed"},
|
||||
{"id": 248, "label": "T24b", "subject": "M9-T24b: Move connection — UI dialog + action", "classification": "standard", "wave": 3, "status": "completed", "blockedBy": [247, 246]},
|
||||
{"id": 249, "label": "T32a", "subject": "M9-T32a: SharedSchema entity + EF config + migration + repo", "classification": "high-risk", "wave": 3, "status": "completed"},
|
||||
{"id": 250, "label": "T32b", "subject": "M9-T32b: JSON Schema $ref resolver + deploy-time validation", "classification": "high-risk", "wave": 3, "status": "completed", "blockedBy": [249]},
|
||||
{"id": 251, "label": "T32c", "subject": "M9-T32c: Schema library — CRUD commands + handlers + Central UI page", "classification": "high-risk", "wave": 4, "status": "completed", "blockedBy": [249, 250]},
|
||||
{"id": 252, "label": "T30", "subject": "M9-T30: Schema-driven nested value-entry forms", "classification": "standard", "wave": 4, "status": "completed", "blockedBy": [250]},
|
||||
{"id": 253, "label": "T31", "subject": "M9-T31: Monaco JSON-Schema hover/completion", "classification": "standard", "wave": 4, "status": "completed", "blockedBy": [250, 252]},
|
||||
{"id": 254, "label": "T26a", "subject": "M9-T26a: Inheritance resolve service + query command", "classification": "high-risk", "wave": 4, "status": "completed"},
|
||||
{"id": 255, "label": "T26b", "subject": "M9-T26b: TemplateEdit — full inherited set + staleness banner", "classification": "standard", "wave": 4, "status": "completed", "blockedBy": [254]},
|
||||
{"id": 256, "label": "INT", "subject": "M9-INT: Integration — build, docker, Playwright, smoke, end-to-end trace", "classification": "high-risk", "wave": 5, "status": "completed", "blockedBy": [240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255]}
|
||||
],
|
||||
"serializationPoints": {
|
||||
"ManagementActor.cs+ManagementCommandRegistry.cs": [244, 247, 251, 254],
|
||||
@@ -30,5 +30,6 @@
|
||||
"TemplateEdit.razor": [243, 255],
|
||||
"ParameterValueForm.razor/value-entry-surface": [252, 253]
|
||||
},
|
||||
"lastUpdated": "2026-06-18"
|
||||
"lastUpdated": "2026-08-01",
|
||||
"status": "2026-08-01 bookkeeping sync: statuses reconciled against merged code"
|
||||
}
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
{
|
||||
"planPath": "docs/plans/2026-06-19-sms-notifications.md",
|
||||
"tasks": [
|
||||
{"id": 299, "subject": "S1: Commons foundation — NotificationType.Sms + recipient phone + SmsConfiguration + repo iface", "status": "pending"},
|
||||
{"id": 300, "subject": "S2: Config-DB — EF mappings + AuthToken encryption + idempotent migration", "status": "pending", "blockedBy": [299]},
|
||||
{"id": 303, "subject": "S5: Management — list-command Type + SMS-config commands/handlers", "status": "pending", "blockedBy": [299]},
|
||||
{"id": 301, "subject": "S3: SmsNotificationDeliveryAdapter (Twilio REST) + classifier + options + DI + tests", "status": "pending", "blockedBy": [299, 300]},
|
||||
{"id": 302, "subject": "S4: NotificationOutboxActor ingest type-stamping from list", "status": "pending", "blockedBy": [299]},
|
||||
{"id": 304, "subject": "S6: CLI — list --type/--phones + notification sms group", "status": "pending", "blockedBy": [303]},
|
||||
{"id": 305, "subject": "S7: Central UI — NotificationListForm adapter-gated Type selector + per-type recipient input", "status": "pending", "blockedBy": [299, 303]},
|
||||
{"id": 306, "subject": "S8: Central UI — NotificationLists Type column", "status": "pending", "blockedBy": [299]},
|
||||
{"id": 307, "subject": "S9: Central UI — SMS configuration page (/notifications/sms)", "status": "pending", "blockedBy": [300, 303]},
|
||||
{"id": 308, "subject": "S10: Transport — recipient PhoneNumber DTO + SmsConfigDto round-trip", "status": "pending", "blockedBy": [299]},
|
||||
{"id": 309, "subject": "S11: INT — build, drift, docker, Playwright, live smoke, docs, whole-branch review", "status": "pending", "blockedBy": [300, 301, 302, 303, 304, 305, 306, 307, 308]}
|
||||
{"id": 299, "subject": "S1: Commons foundation — NotificationType.Sms + recipient phone + SmsConfiguration + repo iface", "status": "completed"},
|
||||
{"id": 300, "subject": "S2: Config-DB — EF mappings + AuthToken encryption + idempotent migration", "status": "completed", "blockedBy": [299]},
|
||||
{"id": 303, "subject": "S5: Management — list-command Type + SMS-config commands/handlers", "status": "completed", "blockedBy": [299]},
|
||||
{"id": 301, "subject": "S3: SmsNotificationDeliveryAdapter (Twilio REST) + classifier + options + DI + tests", "status": "completed", "blockedBy": [299, 300]},
|
||||
{"id": 302, "subject": "S4: NotificationOutboxActor ingest type-stamping from list", "status": "completed", "blockedBy": [299]},
|
||||
{"id": 304, "subject": "S6: CLI — list --type/--phones + notification sms group", "status": "completed", "blockedBy": [303]},
|
||||
{"id": 305, "subject": "S7: Central UI — NotificationListForm adapter-gated Type selector + per-type recipient input", "status": "completed", "blockedBy": [299, 303]},
|
||||
{"id": 306, "subject": "S8: Central UI — NotificationLists Type column", "status": "completed", "blockedBy": [299]},
|
||||
{"id": 307, "subject": "S9: Central UI — SMS configuration page (/notifications/sms)", "status": "completed", "blockedBy": [300, 303]},
|
||||
{"id": 308, "subject": "S10: Transport — recipient PhoneNumber DTO + SmsConfigDto round-trip", "status": "completed", "blockedBy": [299]},
|
||||
{"id": 309, "subject": "S11: INT — build, drift, docker, Playwright, live smoke, docs, whole-branch review", "status": "completed", "blockedBy": [300, 301, 302, 303, 304, 305, 306, 307, 308]}
|
||||
],
|
||||
"lastUpdated": "2026-06-19"
|
||||
"lastUpdated": "2026-08-01",
|
||||
"status": "2026-08-01 bookkeeping sync: statuses reconciled against merged code"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
**Date:** 2026-06-26 · **Status:** APPROVED (design) · **Area:** Deployment Manager / Site Runtime / Cluster Communication
|
||||
**Fixes:** [`docs/known-issues/2026-06-26-deploy-config-exceeds-akka-frame-size.md`](../known-issues/2026-06-26-deploy-config-exceeds-akka-frame-size.md)
|
||||
|
||||
> **2026-08-01 bookkeeping sync:** the central→site ClusterClient hop described below is now **gRPC** (`SiteCommandService`/`GrpcSiteTransport`) after the 2026-07-22 ClusterClient→gRPC migration — the notify-and-fetch behavior described here is unchanged.
|
||||
|
||||
## 1. Problem
|
||||
|
||||
The flattened instance config travels over Akka on **two** hops today, and both are bounded by Akka.Remote's default `maximum-frame-size` (**128 KB / `128000b`**). A large config (e.g. a 3rd composition of the same base template) silently breaks both:
|
||||
@@ -177,7 +179,7 @@ Smoke-tested on the docker cluster (rebuilt from this branch). Validated end-to-
|
||||
|
||||
The smoke surfaced two real bugs in the reconciliation path (missed by unit/integration tests because those didn't have a second concurrent node or a lingering expired row), both fixed:
|
||||
1. **Concurrent-gap omit** — when two nodes were concurrently missing the same instance, the second node's `StagePendingIfAbsentAsync` returned false and the handler *omitted* the item, leaving that node unhealed. Fix: on false, return the **existing** pending row's deploymentId + token (multi-use within TTL) so all concurrently-missing nodes heal in the same round.
|
||||
2. **Expired pending row blocks self-heal** — `StagePendingIfAbsentAsync` checked existence by `InstanceId` ignoring expiry, so an expired-but-unpurged row (the periodic purge is still a deferred TODO) blocked a fresh stage *and* would collide with the snapshot's reused `DeploymentId` on the unique index. Fix: **expiry-aware staging** — delete expired rows for the instance first, then check only live rows; `GetPendingDeploymentByInstanceIdAsync` filters by expiry. This also opportunistically cleans expired rows, reducing reliance on the deferred periodic purge.
|
||||
2. **Expired pending row blocks self-heal** — `StagePendingIfAbsentAsync` checked existence by `InstanceId` ignoring expiry, so an expired-but-unpurged row (the periodic purge was a deferred TODO at the time — **RESOLVED 2026-08-01 bookkeeping sync: `PendingDeploymentPurgeActor` shipped in PLAN-04; deferred-work register row #20 closed**) blocked a fresh stage *and* would collide with the snapshot's reused `DeploymentId` on the unique index. Fix: **expiry-aware staging** — delete expired rows for the instance first, then check only live rows; `GetPendingDeploymentByInstanceIdAsync` filters by expiry. This also opportunistically cleans expired rows, reducing reliance on the deferred periodic purge.
|
||||
|
||||
## 12. Affected files (for the plan)
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
# Plan: Aggregated Live Alarm Stream for Alarm Summary (deferred #10)
|
||||
|
||||
**Status:** Draft plan (not yet executed) — 2026-07-10
|
||||
**Status:** **Delivered 2026-07-10** — shipped as designed (`SiteAlarmAggregatorActor`,
|
||||
`ISiteAlarmLiveCache`, the `SubscribeSite` streaming RPC, and
|
||||
`AlarmSummaryService.BuildFromLiveAlarms` are all in `src/`). The 15 s poll is retained as the
|
||||
NotReporting authority behind the live stream, per the design below.
|
||||
**Register row:** `docs/plans/2026-07-08-deferred-work-register.md` #10
|
||||
**Revisit trigger that fired this plan:** *"Alarm Summary latency complaints or >~50 instances/site."*
|
||||
**Owning component:** Central UI (#9) + Central–Site Communication (#5); no new component.
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
# Plan: KPI History Hourly Rollups (deferred #22)
|
||||
|
||||
**Status:** Draft plan (not yet executed) — 2026-07-10
|
||||
**Status:** **Delivered 2026-07-10** — shipped as designed: the `KpiRollupHourly` entity +
|
||||
EF migration `20260710153953_AddKpiRollupHourlyTable`, `KpiMetricAggregationCatalog`
|
||||
(per-metric gauge-vs-rate fold), and the raw-vs-rollup query routing on `RollupThresholdHours`
|
||||
are all in `src/`.
|
||||
**Register row:** `docs/plans/2026-07-08-deferred-work-register.md` #22
|
||||
**Revisit trigger that fired this plan:** *"KpiSample query latency on dashboards."*
|
||||
**Owning component:** KPI History (#26); touches Configuration Database (#17),
|
||||
|
||||
@@ -191,5 +191,5 @@
|
||||
}
|
||||
],
|
||||
"lastUpdated": "2026-07-19",
|
||||
"phase1Status": "COMPLETE - all 15 tasks done, live gate PASS. Branch feat/localdb-phase1 NOT merged/pushed."
|
||||
"phase1Status": "COMPLETE - all 15 tasks done, live gate PASS. MERGED to main as 28ca04d7 ('LocalDb adoption Phase 1 + 2: consolidate the site database, delete the bespoke replicators (#23)') - Phase 1 and Phase 2 landed together in that one merge."
|
||||
}
|
||||
@@ -89,7 +89,7 @@
|
||||
{"id": 18, "subject": "Task 18: Two-node convergence suite for the Phase 2 tables", "status": "completed", "classification": "high-risk", "blockedBy": [17], "note": "DEVIATION: landed as a NEW file LocalDbPhase2ConvergenceTests.cs rather than extending Phase 1's LocalDbSitePairConvergenceTests.cs, and drives the REAL SiteStorageService instead of hand-written SQL - possible only post-cutover, and what makes the cascade scenario test the shipped transaction rather than a re-creation of it. Scenario 4 was retargeted onto shared_scripts/external_systems/static_attribute_overrides because the plan's version overlapped LocalDbConfigConvergenceTests' N1 scenario almost exactly; the union-survives property is per-table, so re-proving it on untouched tables is the non-redundant half. Cascade test carries a never-removed control instance (absence assertions otherwise cannot distinguish 'cascade converged' from 'node B lost these tables'). Non-vacuity PROVEN as mandated: with the 8 RegisterReplicated calls commented out, 4 failed / 0 passed; restored, 20/20 across the three LocalDb suites.", "commit": "15013156"},
|
||||
{"id": 19, "subject": "Task 19: Rig configuration (MaxOplogRows/MaxOplogAge + MaxBatchSize per D6)", "status": "completed", "classification": "small", "blockedBy": [17], "note": "MaxBatchSize 500->16 (D6: row-count batching x ~70 KB production config_json vs the 4 MB gRPC cap; 16 => ~1.1 MB worst case). MaxOplogRows 1M->250,000 and MaxOplogAge 7d->2d from the soak's 0.80 rows/sec (~69k/day). Tighter-than-default is SAFE because a cap breach prunes + sets needs_snapshot (graceful snapshot resync), not data loss - the Task 1 finding that the plan's stop condition was weaker than written. site-b/site-c left unreplicated so default-OFF stays proven side by side.", "commit": "921edab4"},
|
||||
{"id": 20, "subject": "Task 20: Live gate on the docker rig", "status": "completed", "classification": "high-risk", "blockedBy": [18, 19], "note": "ALL 10 CHECKS PASS. Evidence: docs/plans/2026-07-19-localdb-phase2-live-gate.md. Key blocker found and fixed mid-run: external systems reach a site ONLY via ArtifactDeploymentService, which `instance deploy` never invokes - `deploy artifacts` was needed both to deliver the probe harness and to propagate the owed ExternalSystemDefinitions restore. THREE METHOD CORRECTIONS to the plan: (1) its instruction to run DB checks host-side against the bind mounts is UNSAFE - host sqlite3 poisons the container WAL; copy the db/-wal/-shm triplet and query the copy. (2) `docker exec ... curl` cannot scrape metrics (no curl in aspnet:10.0) and with 2>/dev/null the failure is silent - it nearly became a false 'metrics missing' finding; use a network-sharing curl sidecar. (3) checks needing S&F load need CachedCall, not Call. CAVEATS recorded not glossed: check 2's zero-count is vacuous alone (legacy source was also empty) and rests on the ABSENCE of CDC triggers; check 7's native_alarm_state leg was empty live and is covered only offline; check 10's sampling is coarse and the real rise/drain evidence comes from check 6's 0->4->0.", "commit": "158e79bb"},
|
||||
{"id": 21, "subject": "Task 21: Documentation truth pass", "status": "completed", "classification": "standard", "blockedBy": [20], "note": "Both CLAUDE.md files + Component-StoreAndForward.md:83 (normative resync paragraph rewritten for CDC, stating the duplicate-delivery bound explicitly: limited to messages the OLD primary delivered whose status change had not yet replicated when the gate flipped - one flush interval plus in-flight ack, and it does NOT grow with backlog depth or absence duration) + components/{StoreAndForward,SiteRuntime,Host}.md + the frame-size known-issue (amended: Phase 2 deleted notify-and-fetch itself, so the 128KB Akka frame constraint is gone from the intra-site hop entirely; successor ceiling is the 4MB gRPC cap via MaxBatchSize, and note the failure mode differs - oversized gRPC is REJECTED, not silently dropped) + deployment topology-guide.md and installation-guide.md (D5 stop-both-together, D2 TombstoneRetention resurrection bound). DoD closed: build 0 warnings, all 10 suites green (3509 tests, 0 failures). DoD grep nuance recorded in the plan: 4 matches remain in src/ and are all deliberate COMMENT prose explaining what was deleted - a literal 'no matches' would delete the explanations that stop someone re-introducing the old design.", "commit": null}
|
||||
{"id": 21, "subject": "Task 21: Documentation truth pass", "status": "completed", "classification": "standard", "blockedBy": [20], "note": "Both CLAUDE.md files + Component-StoreAndForward.md:83 (normative resync paragraph rewritten for CDC, stating the duplicate-delivery bound explicitly: limited to messages the OLD primary delivered whose status change had not yet replicated when the gate flipped - one flush interval plus in-flight ack, and it does NOT grow with backlog depth or absence duration) + components/{StoreAndForward,SiteRuntime,Host}.md + the frame-size known-issue (amended: Phase 2 deleted notify-and-fetch itself, so the 128KB Akka frame constraint is gone from the intra-site hop entirely; successor ceiling is the 4MB gRPC cap via MaxBatchSize, and note the failure mode differs - oversized gRPC is REJECTED, not silently dropped) + deployment topology-guide.md and installation-guide.md (D5 stop-both-together, D2 TombstoneRetention resurrection bound). DoD closed: build 0 warnings, all 10 suites green (3509 tests, 0 failures). DoD grep nuance recorded in the plan: 4 matches remain in src/ and are all deliberate COMMENT prose explaining what was deleted - a literal 'no matches' would delete the explanations that stop someone re-introducing the old design.", "commit": "7b5a5a6f"}
|
||||
],
|
||||
"knownFlakes": [
|
||||
{
|
||||
@@ -102,5 +102,5 @@
|
||||
}
|
||||
],
|
||||
"lastUpdated": "2026-07-20",
|
||||
"phase2Status": "UNBLOCKED - Task 1 gate CLOSED (verdict PROCEED) and Task 2 DONE, 2026-07-20. Task 1's original STOP verdict is SUPERSEDED: the 'Phase 1 disk I/O defect' was OBSERVER-INDUCED (host-side sqlite3 against live bind-mounted WAL files resets the WAL across virtiofs and permanently poisons the container's connections) - NOT a product defect. See docs/known-issues/2026-07-20-localdb-disk-io-error-under-load.md. Both earlier isolation claims were confounded: one sampling pass had already poisoned BOTH nodes, and a poisoned standby looks healthy only because it issues almost no statements; 'LocalDb-specific' was sampling-selection bias (only the LocalDb file had ever been host-read). Clean re-run 2026-07-20 with the copy-based snap() helper on restarted nodes, 6 consecutive 60s intervals: sf_messages 48 rows/min = 0.80 rows/sec dead steady, retry-UPDATE rate 0/sec (SUM(retry_count) flat at 200), oplog 0, native_alarm_state 0, max payload_json 76 B, max config_json 721 B, ZERO SQLite errors across 30 min, both site-a nodes converged at 3564 rows. Honest gap: 0.80/s is ~1.6% of the 50/s ceiling and the retry-UPDATE path was never exercised - acceptable because that ceiling is structural (SweepBatchLimit/RetryTimerInterval), not empirical. Rig config rows (721 B) are NOT representative; D6 sizing rests on the documented ~60-70 KB production config_json. Plan-premise corrections stand: D6 (MaxBatchSize 500 -> 16, the one firmly evidence-backed number; Task 19 sets it), D4 (alarm writes bounded by per-SourceReference coalescing at a 100 ms flush, NOT unbounded), sf_messages hard ceiling 50 rows/sec, oplog cap overrun = graceful snapshot resync (needs_snapshot), not data loss. NEXT: Wave 1 = Tasks 3 + 4, dispatchable in parallel (disjoint Files blocks). Tasks 3-21 untouched; no plan code written yet. Rig cleanup still owed before Task 20: restore ExternalSystemDefinitions id 1 to http://scadabridge-restapi:5200 (currently http://127.0.0.1:9), and remove SoakGenerator template 2021 + instances soakgen-1..4 (ids 5-8), still deployed and generating load."
|
||||
"phase2Status": "COMPLETE - all 21 tasks done, live gate PASS (docs/plans/2026-07-19-localdb-phase2-live-gate.md), MERGED to main as 28ca04d7 ('LocalDb adoption Phase 1 + 2 ... (#23)'), which carried Phase 1 in the same merge. History below retained for context. Task 1 gate CLOSED (verdict PROCEED) and Task 2 DONE, 2026-07-20. Task 1's original STOP verdict is SUPERSEDED: the 'Phase 1 disk I/O defect' was OBSERVER-INDUCED (host-side sqlite3 against live bind-mounted WAL files resets the WAL across virtiofs and permanently poisons the container's connections) - NOT a product defect. See docs/known-issues/2026-07-20-localdb-disk-io-error-under-load.md. Both earlier isolation claims were confounded: one sampling pass had already poisoned BOTH nodes, and a poisoned standby looks healthy only because it issues almost no statements; 'LocalDb-specific' was sampling-selection bias (only the LocalDb file had ever been host-read). Clean re-run 2026-07-20 with the copy-based snap() helper on restarted nodes, 6 consecutive 60s intervals: sf_messages 48 rows/min = 0.80 rows/sec dead steady, retry-UPDATE rate 0/sec (SUM(retry_count) flat at 200), oplog 0, native_alarm_state 0, max payload_json 76 B, max config_json 721 B, ZERO SQLite errors across 30 min, both site-a nodes converged at 3564 rows. Honest gap: 0.80/s is ~1.6% of the 50/s ceiling and the retry-UPDATE path was never exercised - acceptable because that ceiling is structural (SweepBatchLimit/RetryTimerInterval), not empirical. Rig config rows (721 B) are NOT representative; D6 sizing rests on the documented ~60-70 KB production config_json. Plan-premise corrections stand: D6 (MaxBatchSize 500 -> 16, the one firmly evidence-backed number; Task 19 sets it), D4 (alarm writes bounded by per-SourceReference coalescing at a 100 ms flush, NOT unbounded), sf_messages hard ceiling 50 rows/sec, oplog cap overrun = graceful snapshot resync (needs_snapshot), not data loss. Tasks 3-21 were subsequently executed and all landed. Rig cleanup that was owed before Task 20 is DONE: ExternalSystemDefinitions id 1 restored to http://scadabridge-restapi:5200 (propagation to both site nodes confirmed), and SoakGenerator template 2021 + instances soakgen-1..4 deleted - see 'Rig state as left' in the live-gate doc."
|
||||
}
|
||||
|
||||
@@ -195,7 +195,9 @@ was down and back to 0 after rejoin.
|
||||
- **Deleting an instance orphans its buffered messages.** Removing the `soakgen-*` instances left
|
||||
their 11,804 `sf_messages` with no tracking snapshot, producing a continuous
|
||||
`Cached-telemetry drain: no tracking snapshot for …` warning flood. Also pre-existing, and worth
|
||||
a cleanup path.
|
||||
a cleanup path. **RESOLVED** by `62cddcfa` ("let an unresolvable cached row leave the drain
|
||||
queue", merged `663d03e8`) — an unresolvable row is now dropped from the drain queue instead of
|
||||
being retried forever, so the hot loop and the warning flood are gone.
|
||||
|
||||
## Rig state as left
|
||||
|
||||
|
||||
@@ -72,7 +72,11 @@ Sustained rate observed: ~**2.9 HTTP attempts/sec** (688–864 connection-refuse
|
||||
image does not implement. After `reseed.sh` drops the volume (`docker compose down -v`),
|
||||
nothing recreates `ScadaBridgeConfig` or the `scadabridge_app` login, so `reseed.sh` hangs
|
||||
forever on its "Waiting for setup.sql to create ScadaBridgeConfig" poll. Worked around by
|
||||
applying the three init scripts by hand. **NOT yet fixed in the repo.**
|
||||
applying the three init scripts by hand. **Since FIXED in the repo:** `infra/reseed.sh` now
|
||||
applies all three scripts explicitly via `sqlcmd` (`mssql/setup.sql`,
|
||||
`mssql/machinedata_seed.sql`, `mssql/setup-env2.sql`), with a comment recording that the
|
||||
`mssql/server` image has no initdb hook; the `/docker-entrypoint-initdb.d/` mounts in
|
||||
`infra/docker-compose.yml` are documented as **informational only**.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -97,12 +97,17 @@ mis-explanation in the repo ("the alone-oldest is dead and cannot down itself").
|
||||
|
||||
## Residual operational notes
|
||||
|
||||
- **Seed-node bootstrap constraint still applies to boot-alone**: only the first seed
|
||||
may self-form a cluster. Auto-down removes the active-crash outage (the survivor never
|
||||
restarts), but a node that must BOOT alone while its peer is dead (cold start of only
|
||||
the non-first-seed VM, or the survivor crashing while the peer is still down) still
|
||||
waits in `InitJoin` for its peer. Operator recovery unchanged (restart first seed, or
|
||||
self-first seed override).
|
||||
- ~~**Seed-node bootstrap constraint still applies to boot-alone**~~ — **SUPERSEDED
|
||||
(2026-07-22), no longer a residual.** As written: only the first seed may self-form a
|
||||
cluster, so a node that must BOOT alone while its peer is dead (cold start of only the
|
||||
non-first-seed VM, or the survivor crashing while the peer is still down) waits in
|
||||
`InitJoin` for its peer. This was **closed** by the self-first seed-node ordering change
|
||||
(`docs/plans/2026-07-22-selfform-fallback-and-manual-failover.md`): every node now lists
|
||||
ITSELF first in `seed-nodes`, so any node can cold-start alone and become operational
|
||||
unattended, and `StartupValidator` fails the boot if the ordering is broken — regression
|
||||
coverage in `SelfFirstSeedBootstrapTests`. The residual cost of self-first-on-both (a
|
||||
*truly simultaneous* cold start racing `FirstSeedNodeProcess` on both nodes) is in turn
|
||||
covered by the opt-in `ScadaBridge:Cluster:BootstrapGuard` (Gitea #33, default OFF).
|
||||
- Monitoring already surfaces dual-active if it ever happens: both nodes report
|
||||
`IsActive` in heartbeats / both `/health/active` = 200 — the Health dashboard shows
|
||||
two Primaries.
|
||||
|
||||
@@ -118,6 +118,9 @@ Server's retrying strategy refuses to run a split query inside a caller's transa
|
||||
**bundle import fails against real MS SQL**. The fix is the one the exception names: wrap the
|
||||
transaction in `Database.CreateExecutionStrategy().ExecuteAsync(...)`.
|
||||
|
||||
> **FIXED (Gitea #28, commit `c4dcd9bc`)** — bundle import now runs inside the execution
|
||||
> strategy, with a regression test.
|
||||
|
||||
Why the whole unit/integration suite is green on it: those tests use the in-memory EF provider,
|
||||
which has no retrying execution strategy — and `BeginTransactionAsync` is a no-op there. The
|
||||
comment directly above line 1298 documents that divergence without drawing the conclusion. Only
|
||||
@@ -134,13 +137,18 @@ This has been failing since 2026-07-10, and it matters more than a red line: eve
|
||||
the toast assertion — including **the secret-non-leak assertion that the Auth Token value never
|
||||
reaches the page HTML** — has not executed since. Fix is a valid 32-hex SID in the fixture.
|
||||
|
||||
> **FIXED (Gitea #29, commit `8524a7f7`)** — the fixture now uses a valid 32-hex Twilio SID, so
|
||||
> the create path (and the secret-non-leak assertion behind it) executes again.
|
||||
|
||||
### Not covered by this gate
|
||||
|
||||
- Streaming subscriptions were exercised in-process (TestServer), not over the rig. The
|
||||
interceptor is path-scoped, not method-scoped, so the rig's `PullAuditEvents` evidence covers
|
||||
the same code path — but a live `SubscribeInstance` under load is untested here.
|
||||
- Key **rotation** on a live pair.
|
||||
- `docker-env2` was updated with its own key but not redeployed or gated.
|
||||
- `docker-env2` was updated with its own key but not redeployed or gated. — **CLOSED
|
||||
(Gitea #31, commit `e0f105c3`)**: `docker-env2` was rebuilt on the PSK build and gated,
|
||||
**PASS 3/3**; see the "Env2 gate" section at the end of this document.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -132,11 +132,12 @@
|
||||
"id": "P1A.DoD",
|
||||
"phase": "1A",
|
||||
"subject": "1A DoD: rig site-a on Grpc proves all 5 site->central paths while site-b/c stay Akka; PR merged before 1B",
|
||||
"status": "pending",
|
||||
"status": "completed",
|
||||
"activeForm": "Verifying the 1A DoD",
|
||||
"blockedBy": [
|
||||
"T1A.4"
|
||||
]
|
||||
],
|
||||
"notes": "PASS - ticked in the plan MD. Rig site-a on CentralTransport=Grpc proved the site->central paths (heartbeat/health/reconcile + Akka coexistence on site-b/c); PR #26 merged as aa60f438. Notification/audit deferred to the Phase 2 soak (no deployed instance at the time). The rig caught and fixed a central :5000 HTTP-drop regression (0e162cb2). Gate: docs/plans/2026-07-22-clusterclient-to-grpc-live-gate.md."
|
||||
},
|
||||
{
|
||||
"id": "T1B.1",
|
||||
@@ -187,12 +188,13 @@
|
||||
"id": "P1B.DoD",
|
||||
"phase": "1B",
|
||||
"subject": "1B DoD: rig central on Grpc for site-a proves full command matrix incl. standby parked retry; rebased on 1A; PR merged",
|
||||
"status": "pending",
|
||||
"status": "completed",
|
||||
"activeForm": "Verifying the 1B DoD",
|
||||
"blockedBy": [
|
||||
"T1B.4",
|
||||
"P1A.DoD"
|
||||
]
|
||||
],
|
||||
"notes": "PASS (proportionate) - ticked in the plan MD (2fa5e93c). Rig central on SiteTransport=Grpc proved central->site rides the authenticated SiteCommandService for all 3 sites (ExecuteQuery/ExecuteParked/ExecuteLifecycle -> 200, per-site PSK, 0 auth failures); rebased on 1A. Instance-dependent commands (tag ops/lifecycle/standby parked retry) + TriggerSiteFailover deferred to Phase 3 (no deployed instance / destructive UI-only); command-plane coexistence not expressible because SiteTransport is central-wide. Phases 2-5 completed downstream. Gate: docs/plans/2026-07-22-clusterclient-to-grpc-live-gate.md."
|
||||
},
|
||||
{
|
||||
"id": "P2",
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
# OtOpcUa v3.0 dual-namespace cutover — scoping (Gitea #14)
|
||||
|
||||
**Date:** 2026-07-23 · **Status:** SCOPING (decisions needed — see §6) · **Tracked:** Gitea
|
||||
[#14](https://gitea.dohertylan.com/dohertj2/ScadaBridge/issues/14) · **Area:** Data Connection
|
||||
Layer (OPC UA adapter) · **Upstream:** OtOpcUa v3.0 (merged `master` 2026-07-16, PR #472, merge
|
||||
`ec6598ce`); design `~/Desktop/OtOpcUa/docs/plans/2026-07-15-raw-uns-two-subtree-v3-design.md`.
|
||||
**Date:** 2026-07-23 · **Status:** **DECIDED — D-1/D-2/D-3 all resolved inline in §6; phase 2
|
||||
(`nsu=` hardening + browse UX, items D/E/F) SHIPPED** (`0eb44314`, `97afa84f`, `e04c2617`; this
|
||||
scope doc + the phase-2 plan committed as `266f001a`). Live gates PASS: raw-path
|
||||
`2026-07-23-otopcua-v3-raw-path-live-gate.md`, alarms
|
||||
`2026-07-23-otopcua-v3-alarm-bc-live-gate.md`. Only **item A** (re-author legacy `ns=2`
|
||||
bindings — data/operational, greenfield) remains open. · **Tracked:** Gitea
|
||||
[#14](https://gitea.dohertylan.com/dohertj2/ScadaBridge/issues/14) (CLOSED 2026-07-24) · **Area:**
|
||||
Data Connection Layer (OPC UA adapter) · **Upstream:** OtOpcUa v3.0 (merged `master` 2026-07-16,
|
||||
PR #472, merge `ec6598ce`); design
|
||||
`~/Desktop/OtOpcUa/docs/plans/2026-07-15-raw-uns-two-subtree-v3-design.md`.
|
||||
|
||||
## 1. Headline
|
||||
|
||||
@@ -74,7 +80,7 @@ Grounded in the DCL code (`src/ZB.MOM.WW.ScadaBridge.DataConnectionLayer/`):
|
||||
|---|------|------|---------------|------------|
|
||||
| A | **Legacy `ns=<index>` bindings collide with v3.** v2's sole custom namespace and v3's `raw` both land at `ns=2`, so a stored `ns=2;s=…` resolves **without error** but now means a raw-tree node. Every stored binding must be re-authored (or migrated) to the new address space, ideally in `nsu=` form. | **Data** | `TemplateAttribute.DataSourceReference`, `InstanceConnectionBinding.DataSourceReferenceOverride` (config DB); heartbeat `TagPath`; Transport bundles | High (inspection) |
|
||||
| B | **Native-alarm dedup across the raw+uns fan-out.** `RealOpcUaClient.HandleAlarmEvent` keys on RawPath+ConditionName with **no `ConditionId`-based dedup**; it assumes one condition arrives on one feed. If a v3 server delivers the same condition through two notifier paths into the same feed, two `EventFieldList`s each call `onTransition`. Must verify the Server-object aggregate feed collapses to one copy (the v3 doc says the SDK dedups Server-object subscribers, so this *should* hold — but it is the biggest genuine gap and needs a live check; add ConditionId-based dedup only if the live server double-delivers). | **Code + live validation** | `Adapters/RealOpcUaClient.cs` (HandleAlarmEvent), `Adapters/OpcUaAlarmMapper.cs` | Medium (needs live v3) |
|
||||
| C | **Alarm routing for UNS-bound sources (confirmed by D-1 = ingest both).** `DataConnectionActor` routes transitions by `SourceReference.StartsWith(bindingRef)`, but a v3 condition's `SourceName` is the **RawPath** regardless of subtree — so a **UNS-bound** alarm source won't `StartsWith`-match. Needs a routing change: resolve a UNS-bound source to its backing RawPath (via the browseable UNS→Raw `Organizes` reference) or map identities. Confirmed scope; validate the exact `SourceName`/`SourceNode` payload on the live rig before finalizing the mapping. | **Code + live validation** | `Actors/DataConnectionActor.cs` (alarm routing), `Adapters/RealOpcUaClient.cs` | Medium (needs live v3) |
|
||||
| C | **~~Alarm routing for UNS-bound sources~~ — OBSOLETE (2026-07-23); no code change needed.** The premise below is superseded by Gitea #17: `OpcUaAlarmMapper.BuildIdentity` stamps `SourceObjectReference` = the **subscribed node reference**, and `DataConnectionActor` routes on *that*, not on the event `SourceName`. Confirmed live — alarm B/C live gate **PASS** 2026-07-23 (`2026-07-23-otopcua-v3-alarm-bc-live-gate.md`): a UNS-node-scoped binding received and routed the condition correctly, and `SourceName` turned out to be the ScriptedAlarmId (not the RawPath) without breaking anything. *Original scoping text:* **Alarm routing for UNS-bound sources (confirmed by D-1 = ingest both).** `DataConnectionActor` routes transitions by `SourceReference.StartsWith(bindingRef)`, but a v3 condition's `SourceName` is the **RawPath** regardless of subtree — so a **UNS-bound** alarm source won't `StartsWith`-match. Needs a routing change: resolve a UNS-bound source to its backing RawPath (via the browseable UNS→Raw `Organizes` reference) or map identities. Confirmed scope; validate the exact `SourceName`/`SourceNode` payload on the live rig before finalizing the mapping. | **Code + live validation** | `Actors/DataConnectionActor.cs` (alarm routing), `Adapters/RealOpcUaClient.cs` | Medium (needs live v3) |
|
||||
| D | **Browse / search UX now shows two subtrees.** `BrowseChildrenAsync` / `AddressSpaceSearch.SearchAsync` start at the standard Objects root and will enumerate **both** raw and uns as sibling roots. Functional, not breaking — but the picker shows two roots, the manual-entry placeholder still nudges `ns=2;s=…`, and the deeper raw hierarchy stresses `VisitedNodeCeiling`/`maxDepth`. | **UX** | `CentralUI/Components/Dialogs/NodeBrowserDialog.razor`, `IOpcUaClient` search caps | High (inspection) |
|
||||
| E | **`nsu=` hardening (optional, recommended).** The picker already emits `nsu=` via `ToDurable`, but the seam still **accepts** bare `ns=<index>`. Decide whether to warn/reject bare `ns=` on save (closes A permanently) or leave it permissive. | **Code** | `OpcUaNodeReference`, binding validation, picker | High (inspection) |
|
||||
| F | **Doc drift.** `ns=` examples in code comments + `Component-DataConnectionLayer.md`; update to `nsu=`. Update the scadaproj umbrella index (cutover step 4). | **Docs** | doc comments, `docs/requirements/Component-DataConnectionLayer.md`, `../scadaproj/CLAUDE.md` | High |
|
||||
|
||||
@@ -250,9 +250,12 @@ git -C ../scadaproj add CLAUDE.md && git -C ../scadaproj commit -m "docs: note S
|
||||
|
||||
Tracked in the scope doc (§5 phases 3–5, §7). Do not attempt without a re-seeded v3 server:
|
||||
|
||||
- **Item A** — re-author existing dev/test bindings across both subtrees via the picker (data, greenfield; D-3 = no migration code).
|
||||
- **Item B** — native-alarm dedup across the raw+uns notifier fan-out; add `ConditionId` dedup **only if** the live server double-delivers.
|
||||
- **Item C** — UNS-bound alarm routing: a v3 condition's `SourceName` is always the RawPath, so a UNS-bound source won't `StartsWith`-match — resolve UNS→Raw via the browseable `Organizes` reference. Build against the real `SourceName`/`SourceNode` payload captured on the rig.
|
||||
- **Item A** — **STILL OPEN** — re-author existing dev/test bindings across both subtrees via the picker (data, greenfield; D-3 = no migration code).
|
||||
- ~~**Item B**~~ — **CLOSED 2026-07-23** by the native-alarm B/C live gate (**PASS** — `docs/plans/2026-07-23-otopcua-v3-alarm-bc-live-gate.md`): the live v3 server does **not** double-deliver — one subscription per connection delivers each retained condition once, to the most-specific monitored notifier — so no `ConditionId` dedup was added. *(Original: native-alarm dedup across the raw+uns notifier fan-out; add `ConditionId` dedup only if the live server double-delivers.)*
|
||||
- ~~**Item C**~~ — **CLOSED 2026-07-23** by the same gate (**PASS**): the premise is obsolete. ScadaBridge routes native alarms by the **subscribed node reference** (`OpcUaAlarmMapper.BuildIdentity` → `SourceObjectReference`), not the event `SourceName` (Gitea #17), so a UNS-bound source routes correctly with no code change. *(Original: a v3 condition's `SourceName` is always the RawPath, so a UNS-bound source won't `StartsWith`-match — resolve UNS→Raw via the browseable `Organizes` reference.)*
|
||||
|
||||
Also still open (tracked, needs a live v3 rig): calibrating the address-space search `maxDepth: 6`
|
||||
for the deeper raw hierarchy — see the note under Task 2.
|
||||
|
||||
## Verification (whole-plan, after Task 3)
|
||||
|
||||
|
||||
@@ -63,14 +63,16 @@ client (`IEndpointDiscovery`). Commit `a1abbff7` on `fix/opcua-discovery-host-re
|
||||
|
||||
## Deferred (unchanged — need OtOpcUa-side authoring on the dev cluster)
|
||||
|
||||
- **Item A** — re-author legacy `ns=2` bindings to `nsu=` (data; the collision is confirmed real here).
|
||||
- **Item B** — native-alarm dedup across raw+uns fan-out: **no native alarms exist** to drive it.
|
||||
- **Item C** — UNS-bound → RawPath alarm routing: **no UNS nodes and no alarms exist** to drive it.
|
||||
- **Item A** — **STILL OPEN** — re-author legacy `ns=2` bindings to `nsu=` (data; the collision is confirmed real here).
|
||||
- ~~**Item B**~~ — **CLOSED 2026-07-23**, see below.
|
||||
- ~~**Item C**~~ — **CLOSED 2026-07-23**, see below.
|
||||
|
||||
To gate B/C, the OtOpcUa dev cluster needs UNS Equipment authored (populate ns=3, referencing raw
|
||||
tags) and at least one alarm-configured tag raising a Part-9 condition that fans to raw + equipment
|
||||
notifiers.
|
||||
|
||||
**That authoring was done the same day and B/C were gated: `docs/plans/2026-07-23-otopcua-v3-alarm-bc-live-gate.md` — PASS, no ScadaBridge code change needed.** B: one subscription per connection delivers each retained condition once, to the most-specific monitored notifier, so overlapping bindings do not double-mirror. C: the premise was obsolete — routing keys off the **subscribed node reference** (`OpcUaAlarmMapper.BuildIdentity` → `SourceObjectReference`), not the event `SourceName` (Gitea #17). Item **A** remains the only open #14 work.
|
||||
|
||||
## Rig artifacts created (non-default rig state)
|
||||
|
||||
- ScadaBridge site-a-a / site-a-b attached to `otopcua-dev_default` (dropped on next `docker/deploy.sh`).
|
||||
|
||||
@@ -50,5 +50,5 @@
|
||||
| Q-P3C-2 | Max parked messages per remote query? | Paginated, 100 per page, consistent with Site Event Logging pattern. | 2026-03-16 |
|
||||
| Q-P3C-3 | Per-instance operation lock — in-memory or persisted? | In-memory. Released on failover. Site state query resolves any ambiguity. | 2026-03-16 |
|
||||
| Q-P4-1 | API key values — auto-generated or user-provided? | Auto-generated with copy-to-clipboard. User can regenerate. | 2026-03-16 |
|
||||
| Q-P4-2 | Health dashboard refresh interval? | Updates on every report arrival (server push). No UI-side polling. | 2026-03-16 |
|
||||
| Q-P4-2 | Health dashboard refresh interval? | Updates on every report arrival (server push). No UI-side polling. **Superseded (2026-08-01): as shipped, the Health dashboard refreshes on a 10s UI polling timer — the server-push answer recorded here was not the implemented behavior.** | 2026-03-16 |
|
||||
| Q-P4-3 | Area deletion — cascade or bottom-up? | Cascade delete child areas if no instances are assigned to any area in the subtree. | 2026-03-16 |
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# Requirements Traceability Matrix
|
||||
|
||||
> **Note (2026-08-01)**: The Status column is frozen at plan-generation time (March 2026). "Pending" means the phase plan had not yet been generated at that time — **not** that a requirement is unimplemented. See `phase-N-checklist.md` for delivery status. All phases have since been generated and delivered.
|
||||
|
||||
**Purpose**: Ensures every requirement from docs/requirements/HighLevelReqs.md, every REQ-* identifier, and every design constraint from CLAUDE.md and docs/requirements/Component-*.md maps to at least one work package in an implementation phase plan. Updated as plan documents are generated.
|
||||
|
||||
**Traceability levels**:
|
||||
|
||||
Reference in New Issue
Block a user