docs(transport): document CLI surface, blocker-scan heuristic, Admin import gating

Reflect this session's implementation work in the Transport (#24)
component spec:

- New 'CLI' section covering bundle export / preview / import
  commands, the base64-over-JSON wire format, the 200 MB request-body
  cap, and the 5-minute per-command timeout. Authorization table +
  Interactions section updated to mention ManagementActor handlers.
- Import wizard nav placement corrected from Design to Admin (already
  the case in code; the spec lagged).
- Blocker-scan heuristic boundaries documented under Import Flow:
  the '.' skip, the DataSourceReference exclusion, and the
  KnownNonReferenceNames denylist. Both DetectBlockersAsync and
  RunSemanticValidationAsync Pass 1 share the filter.
This commit is contained in:
Joseph Doherty
2026-05-24 09:03:58 -04:00
parent f6cd097c62
commit 6731845473

View File

@@ -155,7 +155,7 @@ Authorization: `RequireDesign` on both the Razor page and `IBundleExporter.Expor
## Import Flow
### UI — 5-Step Wizard (Design nav group)
### UI — 5-Step Wizard (Admin nav group)
**Step 1 — Upload.** Drag-and-drop or browse. On selection, the manifest is parsed and displayed (source env, exporter, timestamp, content count, SHA-256, encrypted yes/no). The manifest hash is validated against the `content` blob.
@@ -171,6 +171,8 @@ Bulk "Apply to all" at the top (Overwrite / Skip / Rename), overridable per row.
Bundle references that cannot be satisfied in either the bundle or the target DB (e.g., a template references a shared script that is neither in the bundle nor pre-existing) appear as **blocker rows** — Apply is disabled until they are resolved (typically by skipping the dependent artifact or re-exporting with dependencies).
**Blocker-scan heuristic boundaries.** The scanner walks `TemplateScript.Code`, `TemplateAttribute.Value`, and `ApiMethod.Script` looking for top-level `Identifier(` or `Identifier.` tokens. To keep the heuristic usable on real script bodies it (a) skips identifiers preceded by `.` (member access — `obj.Method()` does not flag `Method`); (b) does NOT scan `TemplateAttribute.DataSourceReference` (an OPC UA address path, never script source); and (c) filters out a small `KnownNonReferenceNames` denylist of .NET stdlib types (`Convert`, `DateTimeOffset`, `ToString`, `Dispose`, `UtcNow`, …), ScadaLink runtime API roots (`Notify`, `Database`, `ExternalSystem`, `Scripts`, `Instance`, `Parameters`, `Attributes`, `Route`, …), and common SQL keywords that appear inside string literals (`COUNT`, `SELECT`, `FROM`, …). Both the diff-step `DetectBlockersAsync` and the Apply-time `RunSemanticValidationAsync` Pass 1 share this filter, so the diff preview and the Apply gate agree.
**Step 4 — Confirm.** Final summary plus a "N instances will become stale" warning enumerating affected instances. User types the source environment name to confirm (typo-resistant gate at the prod boundary).
**Step 5 — Result.** Counts, link to the `BundleImported` audit row, link to the Deployments page filtered to the newly stale instances.
@@ -264,13 +266,35 @@ Import flows through the same audited repository methods the UI and CLI use, so
**Transactional guarantee:** the EF transaction wraps both the entity writes and the audit log writes (existing pattern). A rollback removes both. The `BundleImported` summary row is the final write inside the transaction, so partial audit trails are impossible. A failed import emits a single `BundleImportFailed` row outside the rolled-back transaction.
## CLI
Three commands surface the same Transport operations as the Central UI wizards, designed for test automation. The bundle bytes travel as base64 inside the existing `/management` JSON envelope — no new HTTP endpoints — and the per-request body cap is raised to 200 MB to cover the 100 MB raw-bundle ceiling once base64-inflated.
```bash
scadalink bundle export --output FILE --passphrase X [--all | --templates A,B ...] \
[--shared-scripts ...] [--external-systems ...] [--db-connections ...] \
[--notification-lists ...] [--smtp-configs ...] [--api-keys ...] \
[--api-methods ...] [--include-dependencies] [--source-environment NAME]
scadalink bundle preview --input FILE --passphrase X
# prints PreviewBundleResult JSON: per-row items + add/modified/identical/blocker counts
scadalink bundle import --input FILE --passphrase X [--on-conflict skip|overwrite|rename]
# one-shot load + preview + apply with a single global policy for Modified rows
# Identical → Skip, New → Add, Blocker → abort
```
Selection uses entity **names** rather than IDs so scripts are portable across environments. The CLI per-command timeout is 5 minutes (vs the default 30 s for other commands) to comfortably cover large bundles. CLI commands route through `ManagementActor`'s `ExportBundleCommand` / `PreviewBundleCommand` / `ImportBundleCommand` handlers, which delegate to the same `IBundleExporter` / `IBundleImporter` services as the UI.
Exit codes follow the project convention: `0` = success, `1` = command failure (validation, blockers, wrong passphrase), `2` = authorization failure.
## Authorization
| Operation | Required role | Enforced at |
|---|---|---|
| Open Export page | `RequireDesign` | Razor page authorize attribute |
| Open Export page / `bundle export` CLI | `RequireDesign` | Razor page authorize attribute + `ManagementActor.GetRequiredRole` |
| `IBundleExporter.ExportAsync` | `RequireDesign` | Service entrypoint |
| Open Import page | `RequireAdmin` | Razor page authorize attribute |
| Open Import page / `bundle preview` + `bundle import` CLI | `RequireAdmin` | Razor page authorize attribute + `ManagementActor.GetRequiredRole` |
| `IBundleImporter.LoadAsync` / `PreviewAsync` / `ApplyAsync` | `RequireAdmin` | Service entrypoint |
| Configuration Audit Log "Bundle Import" filter | `RequireAdmin` or `Audit` | Existing audit page logic |
@@ -282,7 +306,8 @@ Import flows through the same audited repository methods the UI and CLI use, so
## Interactions
- **Central UI** — Hosts the Export Bundle (`/design/transport/export`) and Import Bundle (`/design/transport/import`) wizard pages under the Design nav group. The result page links to the Deployments page and to the filtered Configuration Audit Log Viewer.
- **Central UI** — Hosts the Export Bundle (`/design/transport/export`) page under the Design nav group and the Import Bundle (`/design/transport/import`) page under the Admin nav group. The import result page links to the Deployments page and to the filtered Configuration Audit Log Viewer.
- **Management Service / CLI** — `ManagementActor` registers three Transport command handlers (`ExportBundleCommand`, `PreviewBundleCommand`, `ImportBundleCommand`) and the CLI ships `bundle export` / `bundle preview` / `bundle import` subcommands. Bundle bytes ride the existing `/management` JSON envelope as base64.
- **Deployment Manager** — Never directly invoked by Transport. Transport-driven template changes propagate to deployed instances through the existing revision-hash drift detection in `DeploymentService.CompareAsync`; the Deployments page surfaces affected instances as stale automatically.
- **Security & Auth** — Provides `RequireDesign` and `RequireAdmin` policies from `ScadaLink.Security`, enforced at both the page and service layers.
- **Audit Log (Configuration)** — Writes `BundleExported` / `BundleImported` / `BundleImportFailed` / `UnencryptedBundleExport` / `BundleImportUnlockFailed` rows via `IAuditService`, plus per-import name-resolution warnings `BundleImportAlarmScriptUnresolved` and `BundleImportCompositionUnresolved`; per-entity rows from audited repositories are correlated by `BundleImportId` via `IAuditCorrelationContext`.