From 0780c2e49e44e8bf16e9a40c2db11945e42bf5a1 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Tue, 16 Jun 2026 20:30:29 -0400 Subject: [PATCH] docs(m4.4): clear stale deferred/no-op markers for shipped features (relay, bundle-import audit, M5 redaction, audit drill-in, Transport CLI, traceability) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SiteCallAudit/ServiceCollectionExtensions.cs: drop "still deferred" note on relay; point to SiteCallAuditActor where it lives - Transport/Import/BundleImporter.cs: update "Only LoadAsync implemented" to reflect all three phases shipped - SiteRuntime/Scripts/AuditingDbCommand.cs: replace two M5-deferred redaction comments with accurate references to AuditLogOptions.PerTargetOverrides - SiteRuntime/Scripts/ScriptRuntimeContext.cs: replace "M5 will layer redaction" note with accurate description of shipped redactor - CentralUI/AuditLogPage.razor.cs: replace "Bundle C wires… no-op seam" with accurate description of HandleRowSelected implementation - docs/plans/2026-05-24-transport-design.md §13: update from "CLI Deferred / not built in v1" to reflect shipped BundleCommands.cs; update Open Questions entry - docs/plans/2026-05-24-transport.md: convert Out-of-Scope "Do NOT build CLI" reminder to a factual note that it shipped - docs/plans/2026-05-24-transport.md.tasks.json: flip all 30 tasks from pending → done (entire Transport feature shipped) --- docs/plans/2026-05-24-transport-design.md | 20 +++--- docs/plans/2026-05-24-transport.md | 2 +- docs/plans/2026-05-24-transport.md.tasks.json | 62 +++++++++---------- .../Pages/Audit/AuditLogPage.razor.cs | 5 +- .../ServiceCollectionExtensions.cs | 4 +- .../Scripts/AuditingDbCommand.cs | 11 ++-- .../Scripts/ScriptRuntimeContext.cs | 5 +- .../Import/BundleImporter.cs | 5 +- 8 files changed, 58 insertions(+), 56 deletions(-) diff --git a/docs/plans/2026-05-24-transport-design.md b/docs/plans/2026-05-24-transport-design.md index 2788b991..0473d546 100644 --- a/docs/plans/2026-05-24-transport-design.md +++ b/docs/plans/2026-05-24-transport-design.md @@ -332,21 +332,23 @@ Import flows through the **same audited repository methods** the UI and CLI use, --- -## 13. CLI (Deferred) +## 13. CLI -The `ZB.MOM.WW.ScadaBridge.Transport` library is callable from both Razor pages and `ZB.MOM.WW.ScadaBridge.CLI`. CLI commands are **not** built in v1 but the design leaves a clean path: +The `ZB.MOM.WW.ScadaBridge.Transport` library is callable from both Razor pages and `ZB.MOM.WW.ScadaBridge.CLI`. CLI commands are implemented in `BundleCommands.cs` under the `bundle` sub-command group (note: shipped as `bundle` rather than `transport` to match the script-facing vocabulary): ``` -scadabridge transport export \ +scadabridge bundle export \ --templates Pump,Pump.WaterPump \ --shared-scripts PumpUtils \ --out bundle.scadabundle \ - --passphrase-file /run/secrets/p + --passphrase mypassphrase -scadabridge transport import bundle.scadabundle \ - --passphrase-file /run/secrets/p \ - --on-conflict overwrite|skip|rename \ - --dry-run +scadabridge bundle preview bundle.scadabundle \ + --passphrase mypassphrase + +scadabridge bundle import bundle.scadabundle \ + --passphrase mypassphrase \ + --on-conflict overwrite|skip|rename ``` Same auth model via the Management API. @@ -400,7 +402,7 @@ Same auth model via the Management API. - **Site-scoped artifact transport** (Instances, Areas, bindings, DataConnections). Requires a name-mapping subsystem (source-env-site → target-env-site). Deferred until concrete demand. - **Direct cluster-to-cluster pull** as an alternative to file handoff. Same `ZB.MOM.WW.ScadaBridge.Transport` library can back it; needs cross-env auth design. - **Bundle signing** with an asymmetric keypair (separate from passphrase encryption) for stronger non-repudiation. Manifest content hash is sufficient for v1 tamper detection. -- **CLI commands** (`scadabridge transport export/import`). Shape pre-decided in §13; not built in v1. +- **CLI commands** (`scadabridge bundle export/preview/import`). Shipped in `BundleCommands.cs`; see §13. - **Differential bundles** ("only what changed since last export"). YAGNI for v1. --- diff --git a/docs/plans/2026-05-24-transport.md b/docs/plans/2026-05-24-transport.md index 8e2a63de..9ab1119a 100644 --- a/docs/plans/2026-05-24-transport.md +++ b/docs/plans/2026-05-24-transport.md @@ -1660,6 +1660,6 @@ All must pass. Then proceed to Task 29 manual checklist against the docker clust - Do NOT add a feature flag. - Do NOT touch site-side projects (`ZB.MOM.WW.ScadaBridge.SiteRuntime` etc.). - Do NOT add a stale-mark column on `Instance` — the existing `DeploymentService.CompareAsync` already detects this. -- Do NOT build CLI commands — those are deferred per design doc §13. +- CLI commands shipped as `scadabridge bundle export/preview/import` (see `BundleCommands.cs` and design doc §13 — originally deferred but subsequently built). - Do NOT add bundle signing — content hash in manifest is sufficient for v1. - Do NOT retain bundles server-side after export download or import apply. diff --git a/docs/plans/2026-05-24-transport.md.tasks.json b/docs/plans/2026-05-24-transport.md.tasks.json index cc59b69e..c1bd01af 100644 --- a/docs/plans/2026-05-24-transport.md.tasks.json +++ b/docs/plans/2026-05-24-transport.md.tasks.json @@ -1,36 +1,36 @@ { "planPath": "docs/plans/2026-05-24-transport.md", "tasks": [ - {"id": 7, "subject": "T0: Bundle DTOs in Commons", "status": "pending"}, - {"id": 8, "subject": "T1: Transport interfaces", "status": "pending"}, - {"id": 9, "subject": "T2: BundleImportId column on AuditLogEntry", "status": "pending"}, - {"id": 10, "subject": "T3: EF migration AddBundleImportIdToAuditLog", "status": "pending", "blockedBy": [9]}, - {"id": 11, "subject": "T4: IAuditCorrelationContext scoped service", "status": "pending"}, - {"id": 12, "subject": "T5: AuditService reads correlation context", "status": "pending", "blockedBy": [9, 11]}, - {"id": 13, "subject": "T6: ZB.MOM.WW.ScadaBridge.Transport project skeleton", "status": "pending"}, - {"id": 14, "subject": "T7: TransportOptions", "status": "pending", "blockedBy": [13]}, - {"id": 15, "subject": "T8: BundleSecretEncryptor (AES-256-GCM + PBKDF2)", "status": "pending", "blockedBy": [13]}, - {"id": 16, "subject": "T9: ManifestBuilder + ManifestValidator", "status": "pending", "blockedBy": [13]}, - {"id": 17, "subject": "T10: EntitySerializer + DTOs + secret carving", "status": "pending", "blockedBy": [13]}, - {"id": 18, "subject": "T11: BundleSerializer (ZIP packer + reader)", "status": "pending", "blockedBy": [15, 16, 17]}, - {"id": 19, "subject": "T12: DependencyResolver", "status": "pending", "blockedBy": [13]}, - {"id": 20, "subject": "T13: BundleSessionStore", "status": "pending", "blockedBy": [13]}, - {"id": 21, "subject": "T14: BundleExporter.ExportAsync", "status": "pending", "blockedBy": [18, 19]}, - {"id": 22, "subject": "T15: BundleImporter.LoadAsync", "status": "pending", "blockedBy": [18, 20]}, - {"id": 23, "subject": "T16: BundleImporter.PreviewAsync (diff engine)", "status": "pending", "blockedBy": [22]}, - {"id": 24, "subject": "T17: BundleImporter.ApplyAsync (transactional)", "status": "pending", "blockedBy": [12, 23]}, - {"id": 25, "subject": "T18: Host registration + appsettings binding", "status": "pending", "blockedBy": [24]}, - {"id": 26, "subject": "T19: TreeView checkbox-selection mode", "status": "pending"}, - {"id": 27, "subject": "T20: TemplateFolderTree wrapper + Templates.razor refactor", "status": "pending", "blockedBy": [26]}, - {"id": 28, "subject": "T21: TransportExport.razor wizard", "status": "pending", "blockedBy": [21, 27]}, - {"id": 29, "subject": "T22: TransportImport.razor wizard", "status": "pending", "blockedBy": [24, 27]}, - {"id": 30, "subject": "T23: NavMenu Export/Import entries", "status": "pending", "blockedBy": [28, 29]}, - {"id": 31, "subject": "T24: Audit log Bundle Import filter", "status": "pending", "blockedBy": [9]}, - {"id": 32, "subject": "T25: Integration round-trip test", "status": "pending", "blockedBy": [24]}, - {"id": 33, "subject": "T26: Integration conflict + rollback tests", "status": "pending", "blockedBy": [24]}, - {"id": 34, "subject": "T27: Component-Transport.md design doc", "status": "pending"}, - {"id": 35, "subject": "T28: README + cross-reference updates", "status": "pending"}, - {"id": 36, "subject": "T29: Manual cluster verification checklist", "status": "pending", "blockedBy": [25, 29, 30, 31, 32, 33, 34, 35]} + {"id": 7, "subject": "T0: Bundle DTOs in Commons", "status": "done"}, + {"id": 8, "subject": "T1: Transport interfaces", "status": "done"}, + {"id": 9, "subject": "T2: BundleImportId column on AuditLogEntry", "status": "done"}, + {"id": 10, "subject": "T3: EF migration AddBundleImportIdToAuditLog", "status": "done", "blockedBy": [9]}, + {"id": 11, "subject": "T4: IAuditCorrelationContext scoped service", "status": "done"}, + {"id": 12, "subject": "T5: AuditService reads correlation context", "status": "done", "blockedBy": [9, 11]}, + {"id": 13, "subject": "T6: ZB.MOM.WW.ScadaBridge.Transport project skeleton", "status": "done"}, + {"id": 14, "subject": "T7: TransportOptions", "status": "done", "blockedBy": [13]}, + {"id": 15, "subject": "T8: BundleSecretEncryptor (AES-256-GCM + PBKDF2)", "status": "done", "blockedBy": [13]}, + {"id": 16, "subject": "T9: ManifestBuilder + ManifestValidator", "status": "done", "blockedBy": [13]}, + {"id": 17, "subject": "T10: EntitySerializer + DTOs + secret carving", "status": "done", "blockedBy": [13]}, + {"id": 18, "subject": "T11: BundleSerializer (ZIP packer + reader)", "status": "done", "blockedBy": [15, 16, 17]}, + {"id": 19, "subject": "T12: DependencyResolver", "status": "done", "blockedBy": [13]}, + {"id": 20, "subject": "T13: BundleSessionStore", "status": "done", "blockedBy": [13]}, + {"id": 21, "subject": "T14: BundleExporter.ExportAsync", "status": "done", "blockedBy": [18, 19]}, + {"id": 22, "subject": "T15: BundleImporter.LoadAsync", "status": "done", "blockedBy": [18, 20]}, + {"id": 23, "subject": "T16: BundleImporter.PreviewAsync (diff engine)", "status": "done", "blockedBy": [22]}, + {"id": 24, "subject": "T17: BundleImporter.ApplyAsync (transactional)", "status": "done", "blockedBy": [12, 23]}, + {"id": 25, "subject": "T18: Host registration + appsettings binding", "status": "done", "blockedBy": [24]}, + {"id": 26, "subject": "T19: TreeView checkbox-selection mode", "status": "done"}, + {"id": 27, "subject": "T20: TemplateFolderTree wrapper + Templates.razor refactor", "status": "done", "blockedBy": [26]}, + {"id": 28, "subject": "T21: TransportExport.razor wizard", "status": "done", "blockedBy": [21, 27]}, + {"id": 29, "subject": "T22: TransportImport.razor wizard", "status": "done", "blockedBy": [24, 27]}, + {"id": 30, "subject": "T23: NavMenu Export/Import entries", "status": "done", "blockedBy": [28, 29]}, + {"id": 31, "subject": "T24: Audit log Bundle Import filter", "status": "done", "blockedBy": [9]}, + {"id": 32, "subject": "T25: Integration round-trip test", "status": "done", "blockedBy": [24]}, + {"id": 33, "subject": "T26: Integration conflict + rollback tests", "status": "done", "blockedBy": [24]}, + {"id": 34, "subject": "T27: Component-Transport.md design doc", "status": "done"}, + {"id": 35, "subject": "T28: README + cross-reference updates", "status": "done"}, + {"id": 36, "subject": "T29: Manual cluster verification checklist", "status": "done", "blockedBy": [25, 29, 30, 31, 32, 33, 34, 35]} ], - "lastUpdated": "2026-05-24T00:00:00Z" + "lastUpdated": "2026-06-16T00:00:00Z" } diff --git a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Audit/AuditLogPage.razor.cs b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Audit/AuditLogPage.razor.cs index 093ad06d..30cdae08 100644 --- a/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Audit/AuditLogPage.razor.cs +++ b/src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Audit/AuditLogPage.razor.cs @@ -13,9 +13,8 @@ namespace ZB.MOM.WW.ScadaBridge.CentralUI.Components.Pages.Audit; /// wires up AuditFilterBar and AuditResultsGrid: the page owns the /// active and re-pushes a fresh instance to the /// grid on every Apply (the grid uses reference identity as its "reload" -/// trigger). Row clicks land in — Bundle C wires -/// this to the drilldown drawer; for now it is a no-op seam so test stubs do -/// not error. +/// trigger). Row clicks land in , which pins +/// the selected row and opens the drilldown drawer. /// /// /// Bundle D (M7-T10..T12) adds query-string drill-in parsing so other pages can diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteCallAudit/ServiceCollectionExtensions.cs b/src/ZB.MOM.WW.ScadaBridge.SiteCallAudit/ServiceCollectionExtensions.cs index 0d456554..29746917 100644 --- a/src/ZB.MOM.WW.ScadaBridge.SiteCallAudit/ServiceCollectionExtensions.cs +++ b/src/ZB.MOM.WW.ScadaBridge.SiteCallAudit/ServiceCollectionExtensions.cs @@ -8,8 +8,8 @@ namespace ZB.MOM.WW.ScadaBridge.SiteCallAudit; /// /// /// Binds (stuck-call detection + KPI -/// windowing for the read-side query/KPI handlers). The reconciliation puller -/// and central→site Retry/Discard relay are still deferred to later follow-ups. +/// windowing for the read-side query/KPI handlers). The central→site +/// Retry/Discard relay is wired in . /// /// /// The repository (ISiteCallAuditRepository) is registered by diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/AuditingDbCommand.cs b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/AuditingDbCommand.cs index 424ab2e6..478869bc 100644 --- a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/AuditingDbCommand.cs +++ b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/AuditingDbCommand.cs @@ -463,8 +463,9 @@ internal sealed class AuditingDbCommand : DbCommand } // RequestSummary captures the SQL statement + parameter values by - // default per the alog.md M4 acceptance criteria (M5 will add - // per-connection redaction opt-in). + // default per the alog.md M4 acceptance criteria. Per-target SQL + // parameter redaction is configured via AuditLogOptions.PerTargetOverrides + // (RedactSqlParamsMatching) and applied at write time by the redactor. string? requestSummary = BuildRequestSummary(); // Extra carries the op discriminator + row count per the vocabulary @@ -509,7 +510,8 @@ internal sealed class AuditingDbCommand : DbCommand /// /// Compose a JSON request summary capturing the SQL statement and /// parameter values. Parameter values are captured by default per the - /// M4 acceptance criteria — redaction is opt-in and deferred to M5. + /// M4 acceptance criteria; per-target SQL-parameter redaction is applied + /// at write time via AuditLogOptions.PerTargetOverrides.RedactSqlParamsMatching. /// private string? BuildRequestSummary() { @@ -520,8 +522,7 @@ internal sealed class AuditingDbCommand : DbCommand } // Hand-roll the JSON so we don't pull in System.Text.Json for a - // shape this small. Values are stringified with ToString() — fully - // structured serialisation arrives with the redaction work in M5. + // shape this small. Values are stringified with ToString(). var sb = new System.Text.StringBuilder(sql.Length + 64); sb.Append("{\"sql\":"); AppendJsonString(sb, sql); diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/ScriptRuntimeContext.cs b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/ScriptRuntimeContext.cs index 86b88e0b..f8b2d4ab 100644 --- a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/ScriptRuntimeContext.cs +++ b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/ScriptRuntimeContext.cs @@ -2013,8 +2013,9 @@ public class ScriptRuntimeContext // null per Bundle B's pattern rather than fail the emission. Guid? correlationId = Guid.TryParse(notificationId, out var parsed) ? parsed : (Guid?)null; - // M4 captures the request summary verbatim — {"subject": "...", "body": "..."}. - // M5 will layer redaction / payload-cap enforcement on top. + // Capture the request summary — {"subject": "...", "body": "..."}. + // Payload cap and per-target body redaction are applied at write + // time by the audit redactor (AuditLogOptions / PerTargetRedactionOverride). var requestSummary = JsonSerializer.Serialize(new { subject = subject, diff --git a/src/ZB.MOM.WW.ScadaBridge.Transport/Import/BundleImporter.cs b/src/ZB.MOM.WW.ScadaBridge.Transport/Import/BundleImporter.cs index 43750632..0cfb2d99 100644 --- a/src/ZB.MOM.WW.ScadaBridge.Transport/Import/BundleImporter.cs +++ b/src/ZB.MOM.WW.ScadaBridge.Transport/Import/BundleImporter.cs @@ -26,9 +26,8 @@ namespace ZB.MOM.WW.ScadaBridge.Transport.Import; /// bundle envelope (manifest + content hash + decryption) and opens a /// session; diffs the bundle's DTOs against the /// current target database; writes the chosen -/// resolutions through the audited repositories. Only LoadAsync is -/// implemented in this slice — the other two are wired into DI now so -/// follow-up tasks can fill them in without churning the constructor. +/// resolutions through the audited repositories. All three phases are +/// fully implemented. /// /// Audit-row responsibility: repository mutation methods in /// ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Repositories are thin EF wrappers