docs(m4.4): clear stale deferred/no-op markers for shipped features (relay, bundle-import audit, M5 redaction, audit drill-in, Transport CLI, traceability)
- 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)
This commit is contained in:
@@ -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 \
|
--templates Pump,Pump.WaterPump \
|
||||||
--shared-scripts PumpUtils \
|
--shared-scripts PumpUtils \
|
||||||
--out bundle.scadabundle \
|
--out bundle.scadabundle \
|
||||||
--passphrase-file /run/secrets/p
|
--passphrase mypassphrase
|
||||||
|
|
||||||
scadabridge transport import bundle.scadabundle \
|
scadabridge bundle preview bundle.scadabundle \
|
||||||
--passphrase-file /run/secrets/p \
|
--passphrase mypassphrase
|
||||||
--on-conflict overwrite|skip|rename \
|
|
||||||
--dry-run
|
scadabridge bundle import bundle.scadabundle \
|
||||||
|
--passphrase mypassphrase \
|
||||||
|
--on-conflict overwrite|skip|rename
|
||||||
```
|
```
|
||||||
|
|
||||||
Same auth model via the Management API.
|
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.
|
- **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.
|
- **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.
|
- **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.
|
- **Differential bundles** ("only what changed since last export"). YAGNI for v1.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -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 add a feature flag.
|
||||||
- Do NOT touch site-side projects (`ZB.MOM.WW.ScadaBridge.SiteRuntime` etc.).
|
- 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 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 add bundle signing — content hash in manifest is sufficient for v1.
|
||||||
- Do NOT retain bundles server-side after export download or import apply.
|
- Do NOT retain bundles server-side after export download or import apply.
|
||||||
|
|||||||
@@ -1,36 +1,36 @@
|
|||||||
{
|
{
|
||||||
"planPath": "docs/plans/2026-05-24-transport.md",
|
"planPath": "docs/plans/2026-05-24-transport.md",
|
||||||
"tasks": [
|
"tasks": [
|
||||||
{"id": 7, "subject": "T0: Bundle DTOs in Commons", "status": "pending"},
|
{"id": 7, "subject": "T0: Bundle DTOs in Commons", "status": "done"},
|
||||||
{"id": 8, "subject": "T1: Transport interfaces", "status": "pending"},
|
{"id": 8, "subject": "T1: Transport interfaces", "status": "done"},
|
||||||
{"id": 9, "subject": "T2: BundleImportId column on AuditLogEntry", "status": "pending"},
|
{"id": 9, "subject": "T2: BundleImportId column on AuditLogEntry", "status": "done"},
|
||||||
{"id": 10, "subject": "T3: EF migration AddBundleImportIdToAuditLog", "status": "pending", "blockedBy": [9]},
|
{"id": 10, "subject": "T3: EF migration AddBundleImportIdToAuditLog", "status": "done", "blockedBy": [9]},
|
||||||
{"id": 11, "subject": "T4: IAuditCorrelationContext scoped service", "status": "pending"},
|
{"id": 11, "subject": "T4: IAuditCorrelationContext scoped service", "status": "done"},
|
||||||
{"id": 12, "subject": "T5: AuditService reads correlation context", "status": "pending", "blockedBy": [9, 11]},
|
{"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": "pending"},
|
{"id": 13, "subject": "T6: ZB.MOM.WW.ScadaBridge.Transport project skeleton", "status": "done"},
|
||||||
{"id": 14, "subject": "T7: TransportOptions", "status": "pending", "blockedBy": [13]},
|
{"id": 14, "subject": "T7: TransportOptions", "status": "done", "blockedBy": [13]},
|
||||||
{"id": 15, "subject": "T8: BundleSecretEncryptor (AES-256-GCM + PBKDF2)", "status": "pending", "blockedBy": [13]},
|
{"id": 15, "subject": "T8: BundleSecretEncryptor (AES-256-GCM + PBKDF2)", "status": "done", "blockedBy": [13]},
|
||||||
{"id": 16, "subject": "T9: ManifestBuilder + ManifestValidator", "status": "pending", "blockedBy": [13]},
|
{"id": 16, "subject": "T9: ManifestBuilder + ManifestValidator", "status": "done", "blockedBy": [13]},
|
||||||
{"id": 17, "subject": "T10: EntitySerializer + DTOs + secret carving", "status": "pending", "blockedBy": [13]},
|
{"id": 17, "subject": "T10: EntitySerializer + DTOs + secret carving", "status": "done", "blockedBy": [13]},
|
||||||
{"id": 18, "subject": "T11: BundleSerializer (ZIP packer + reader)", "status": "pending", "blockedBy": [15, 16, 17]},
|
{"id": 18, "subject": "T11: BundleSerializer (ZIP packer + reader)", "status": "done", "blockedBy": [15, 16, 17]},
|
||||||
{"id": 19, "subject": "T12: DependencyResolver", "status": "pending", "blockedBy": [13]},
|
{"id": 19, "subject": "T12: DependencyResolver", "status": "done", "blockedBy": [13]},
|
||||||
{"id": 20, "subject": "T13: BundleSessionStore", "status": "pending", "blockedBy": [13]},
|
{"id": 20, "subject": "T13: BundleSessionStore", "status": "done", "blockedBy": [13]},
|
||||||
{"id": 21, "subject": "T14: BundleExporter.ExportAsync", "status": "pending", "blockedBy": [18, 19]},
|
{"id": 21, "subject": "T14: BundleExporter.ExportAsync", "status": "done", "blockedBy": [18, 19]},
|
||||||
{"id": 22, "subject": "T15: BundleImporter.LoadAsync", "status": "pending", "blockedBy": [18, 20]},
|
{"id": 22, "subject": "T15: BundleImporter.LoadAsync", "status": "done", "blockedBy": [18, 20]},
|
||||||
{"id": 23, "subject": "T16: BundleImporter.PreviewAsync (diff engine)", "status": "pending", "blockedBy": [22]},
|
{"id": 23, "subject": "T16: BundleImporter.PreviewAsync (diff engine)", "status": "done", "blockedBy": [22]},
|
||||||
{"id": 24, "subject": "T17: BundleImporter.ApplyAsync (transactional)", "status": "pending", "blockedBy": [12, 23]},
|
{"id": 24, "subject": "T17: BundleImporter.ApplyAsync (transactional)", "status": "done", "blockedBy": [12, 23]},
|
||||||
{"id": 25, "subject": "T18: Host registration + appsettings binding", "status": "pending", "blockedBy": [24]},
|
{"id": 25, "subject": "T18: Host registration + appsettings binding", "status": "done", "blockedBy": [24]},
|
||||||
{"id": 26, "subject": "T19: TreeView checkbox-selection mode", "status": "pending"},
|
{"id": 26, "subject": "T19: TreeView checkbox-selection mode", "status": "done"},
|
||||||
{"id": 27, "subject": "T20: TemplateFolderTree wrapper + Templates.razor refactor", "status": "pending", "blockedBy": [26]},
|
{"id": 27, "subject": "T20: TemplateFolderTree wrapper + Templates.razor refactor", "status": "done", "blockedBy": [26]},
|
||||||
{"id": 28, "subject": "T21: TransportExport.razor wizard", "status": "pending", "blockedBy": [21, 27]},
|
{"id": 28, "subject": "T21: TransportExport.razor wizard", "status": "done", "blockedBy": [21, 27]},
|
||||||
{"id": 29, "subject": "T22: TransportImport.razor wizard", "status": "pending", "blockedBy": [24, 27]},
|
{"id": 29, "subject": "T22: TransportImport.razor wizard", "status": "done", "blockedBy": [24, 27]},
|
||||||
{"id": 30, "subject": "T23: NavMenu Export/Import entries", "status": "pending", "blockedBy": [28, 29]},
|
{"id": 30, "subject": "T23: NavMenu Export/Import entries", "status": "done", "blockedBy": [28, 29]},
|
||||||
{"id": 31, "subject": "T24: Audit log Bundle Import filter", "status": "pending", "blockedBy": [9]},
|
{"id": 31, "subject": "T24: Audit log Bundle Import filter", "status": "done", "blockedBy": [9]},
|
||||||
{"id": 32, "subject": "T25: Integration round-trip test", "status": "pending", "blockedBy": [24]},
|
{"id": 32, "subject": "T25: Integration round-trip test", "status": "done", "blockedBy": [24]},
|
||||||
{"id": 33, "subject": "T26: Integration conflict + rollback tests", "status": "pending", "blockedBy": [24]},
|
{"id": 33, "subject": "T26: Integration conflict + rollback tests", "status": "done", "blockedBy": [24]},
|
||||||
{"id": 34, "subject": "T27: Component-Transport.md design doc", "status": "pending"},
|
{"id": 34, "subject": "T27: Component-Transport.md design doc", "status": "done"},
|
||||||
{"id": 35, "subject": "T28: README + cross-reference updates", "status": "pending"},
|
{"id": 35, "subject": "T28: README + cross-reference updates", "status": "done"},
|
||||||
{"id": 36, "subject": "T29: Manual cluster verification checklist", "status": "pending", "blockedBy": [25, 29, 30, 31, 32, 33, 34, 35]}
|
{"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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,9 +13,8 @@ namespace ZB.MOM.WW.ScadaBridge.CentralUI.Components.Pages.Audit;
|
|||||||
/// wires up <c>AuditFilterBar</c> and <c>AuditResultsGrid</c>: the page owns the
|
/// wires up <c>AuditFilterBar</c> and <c>AuditResultsGrid</c>: the page owns the
|
||||||
/// active <see cref="AuditLogQueryFilter"/> and re-pushes a fresh instance to the
|
/// active <see cref="AuditLogQueryFilter"/> and re-pushes a fresh instance to the
|
||||||
/// grid on every Apply (the grid uses reference identity as its "reload"
|
/// grid on every Apply (the grid uses reference identity as its "reload"
|
||||||
/// trigger). Row clicks land in <see cref="HandleRowSelected"/> — Bundle C wires
|
/// trigger). Row clicks land in <see cref="HandleRowSelected"/>, which pins
|
||||||
/// this to the drilldown drawer; for now it is a no-op seam so test stubs do
|
/// the selected row and opens the drilldown drawer.
|
||||||
/// not error.
|
|
||||||
///
|
///
|
||||||
/// <para>
|
/// <para>
|
||||||
/// Bundle D (M7-T10..T12) adds query-string drill-in parsing so other pages can
|
/// Bundle D (M7-T10..T12) adds query-string drill-in parsing so other pages can
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ namespace ZB.MOM.WW.ScadaBridge.SiteCallAudit;
|
|||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// <para>
|
/// <para>
|
||||||
/// Binds <see cref="SiteCallAuditOptions"/> (stuck-call detection + KPI
|
/// Binds <see cref="SiteCallAuditOptions"/> (stuck-call detection + KPI
|
||||||
/// windowing for the read-side query/KPI handlers). The reconciliation puller
|
/// windowing for the read-side query/KPI handlers). The central→site
|
||||||
/// and central→site Retry/Discard relay are still deferred to later follow-ups.
|
/// Retry/Discard relay is wired in <see cref="SiteCallAuditActor"/>.
|
||||||
/// </para>
|
/// </para>
|
||||||
/// <para>
|
/// <para>
|
||||||
/// The repository (<c>ISiteCallAuditRepository</c>) is registered by
|
/// The repository (<c>ISiteCallAuditRepository</c>) is registered by
|
||||||
|
|||||||
@@ -463,8 +463,9 @@ internal sealed class AuditingDbCommand : DbCommand
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RequestSummary captures the SQL statement + parameter values by
|
// RequestSummary captures the SQL statement + parameter values by
|
||||||
// default per the alog.md M4 acceptance criteria (M5 will add
|
// default per the alog.md M4 acceptance criteria. Per-target SQL
|
||||||
// per-connection redaction opt-in).
|
// parameter redaction is configured via AuditLogOptions.PerTargetOverrides
|
||||||
|
// (RedactSqlParamsMatching) and applied at write time by the redactor.
|
||||||
string? requestSummary = BuildRequestSummary();
|
string? requestSummary = BuildRequestSummary();
|
||||||
|
|
||||||
// Extra carries the op discriminator + row count per the vocabulary
|
// Extra carries the op discriminator + row count per the vocabulary
|
||||||
@@ -509,7 +510,8 @@ internal sealed class AuditingDbCommand : DbCommand
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Compose a JSON request summary capturing the SQL statement and
|
/// Compose a JSON request summary capturing the SQL statement and
|
||||||
/// parameter values. Parameter values are captured by default per the
|
/// 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 <c>AuditLogOptions.PerTargetOverrides.RedactSqlParamsMatching</c>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private string? BuildRequestSummary()
|
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
|
// Hand-roll the JSON so we don't pull in System.Text.Json for a
|
||||||
// shape this small. Values are stringified with ToString() — fully
|
// shape this small. Values are stringified with ToString().
|
||||||
// structured serialisation arrives with the redaction work in M5.
|
|
||||||
var sb = new System.Text.StringBuilder(sql.Length + 64);
|
var sb = new System.Text.StringBuilder(sql.Length + 64);
|
||||||
sb.Append("{\"sql\":");
|
sb.Append("{\"sql\":");
|
||||||
AppendJsonString(sb, sql);
|
AppendJsonString(sb, sql);
|
||||||
|
|||||||
@@ -2013,8 +2013,9 @@ public class ScriptRuntimeContext
|
|||||||
// null per Bundle B's pattern rather than fail the emission.
|
// null per Bundle B's pattern rather than fail the emission.
|
||||||
Guid? correlationId = Guid.TryParse(notificationId, out var parsed) ? parsed : (Guid?)null;
|
Guid? correlationId = Guid.TryParse(notificationId, out var parsed) ? parsed : (Guid?)null;
|
||||||
|
|
||||||
// M4 captures the request summary verbatim — {"subject": "...", "body": "..."}.
|
// Capture the request summary — {"subject": "...", "body": "..."}.
|
||||||
// M5 will layer redaction / payload-cap enforcement on top.
|
// Payload cap and per-target body redaction are applied at write
|
||||||
|
// time by the audit redactor (AuditLogOptions / PerTargetRedactionOverride).
|
||||||
var requestSummary = JsonSerializer.Serialize(new
|
var requestSummary = JsonSerializer.Serialize(new
|
||||||
{
|
{
|
||||||
subject = subject,
|
subject = subject,
|
||||||
|
|||||||
@@ -26,9 +26,8 @@ namespace ZB.MOM.WW.ScadaBridge.Transport.Import;
|
|||||||
/// bundle envelope (manifest + content hash + decryption) and opens a
|
/// bundle envelope (manifest + content hash + decryption) and opens a
|
||||||
/// session; <see cref="PreviewAsync"/> diffs the bundle's DTOs against the
|
/// session; <see cref="PreviewAsync"/> diffs the bundle's DTOs against the
|
||||||
/// current target database; <see cref="ApplyAsync"/> writes the chosen
|
/// current target database; <see cref="ApplyAsync"/> writes the chosen
|
||||||
/// resolutions through the audited repositories. Only LoadAsync is
|
/// resolutions through the audited repositories. All three phases are
|
||||||
/// implemented in this slice — the other two are wired into DI now so
|
/// fully implemented.
|
||||||
/// follow-up tasks can fill them in without churning the constructor.
|
|
||||||
/// <para>
|
/// <para>
|
||||||
/// Audit-row responsibility: repository mutation methods in
|
/// Audit-row responsibility: repository mutation methods in
|
||||||
/// <c>ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Repositories</c> are thin EF wrappers
|
/// <c>ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Repositories</c> are thin EF wrappers
|
||||||
|
|||||||
Reference in New Issue
Block a user