diff --git a/docs/components/CLI.md b/docs/components/CLI.md index 137972eb..5ab38780 100644 --- a/docs/components/CLI.md +++ b/docs/components/CLI.md @@ -1,12 +1,12 @@ # CLI -The CLI is a standalone command-line tool that provides scripting and automation access to all ScadaBridge administrative operations. It connects to the central cluster's HTTP Management API and dispatches commands to the `ManagementActor` via `POST /management`. Authentication and role resolution happen server-side; the CLI sends LDAP credentials as HTTP Basic Auth on every request. +The CLI is a standalone command-line tool that provides scripting and automation access to all HTTP-eligible ManagementActor operations. It connects to the central cluster's HTTP Management API and dispatches commands to the `ManagementActor` via `POST /management`. Authentication and role resolution happen server-side; the CLI sends LDAP credentials as HTTP Basic Auth on every request. ## Overview -The CLI component lives in `src/ZB.MOM.WW.ScadaBridge.CLI/` and builds to a self-contained `scadabridge` executable (or `scadabridge.exe` on Windows). It is not part of the Host binary — it deploys on any machine with HTTP access to a central node. +The CLI component lives in `src/ZB.MOM.WW.ScadaBridge.CLI/` and builds to a .NET 10 `scadabridge` executable (or `scadabridge.exe` on Windows); self-contained distribution requires an explicit publish configuration. It is not part of the Host binary — it deploys on any machine with HTTP access to a central node. -The tool is built on `System.CommandLine` and exposes a hierarchical command tree organized by management domain (`template`, `instance`, `site`, `data-connection`, and so on). Every command follows the same pattern: resolve the management URL and credentials, construct a command object whose type maps to a `ManagementCommandRegistry` entry, serialize it into the `{ command, payload }` JSON envelope, and `POST` it to `/management`. The server response — JSON success body or `{ error, code }` error envelope — is printed to stdout or stderr respectively. +The tool is built on `System.CommandLine` and exposes a hierarchical command tree organized by management domain (`template`, `instance`, `site`, `data-connection`, and so on). Management commands follow the same pattern: resolve the management URL and credentials, construct a command object whose type maps to a `ManagementCommandRegistry` entry, serialize it into the `{ command, payload }` JSON envelope, and `POST` it to `/management`. The server response — JSON success body or `{ error, code }` error envelope — is printed to stdout or stderr respectively. The CLI is the preferred automation interface. When setting up system state (sites, templates, data connections, deployments, security) without the Central UI, use `scadabridge` commands rather than direct database manipulation. @@ -20,7 +20,11 @@ Direct-to-node access (`http://localhost:9001` for `central-a`, `http://localhos ### Command dispatch -Most commands serialize to a named management command and reach the `ManagementActor` through the `POST /management` endpoint. The `audit query` and `audit export` commands are the exception — they call plain REST endpoints (`GET /api/audit/query`, `GET /api/audit/export`) introduced by Audit Log (#23) and therefore use `SendGetAsync`/`SendGetStreamAsync` on `ManagementHttpClient` rather than `SendCommandAsync`. The streaming `audit export` path uses `HttpCompletionOption.ResponseHeadersRead` to avoid buffering large payloads in memory. +Management commands use `POST /management`. Audit query, export, and tree use +`GET /api/audit/query`, `/api/audit/export`, and `/api/audit/tree`; source-node +backfill uses `POST /api/audit/backfill-source-node`. `audit verify-chain` is a +local placeholder that performs no verification. `debug stream` uses a persistent +SignalR connection. Audit export streams the response without buffering it all. ### Authentication and roles @@ -30,46 +34,37 @@ Role enforcement is applied by the `ManagementActor`. Operations require the app | Role | Covers | |------|--------| -| `Admin` | Security settings, site management, SMTP config, audit-config queries | -| `Design` | Templates, shared scripts, external systems, DB connections, API methods, notification lists | -| `Deployment` | Instance lifecycle (deploy, enable, disable, delete), data connection bindings, bundle import/export | -| `OperationalAudit` | Reading the Audit Log (`audit query`) | -| `AuditExport` | Exporting the Audit Log (`audit export`) | +| `Administrator` | Security settings, site mutations, SMTP/SMS provider changes, bundle preview/import, certificate management, audit-config queries | +| `Designer` | Template/schema/script authoring, external systems, DB connections, API methods, notification lists, connection design, bundle export | +| `Deployer` | Instance lifecycle and overrides, bindings, deployment, runtime debugging, parked-call decisions | +| `Operator` | Submit secured writes | +| `Verifier` | Approve/reject secured writes from a different principal | +| Operational audit roles | Audit query/tree and Flow Debugger replay | +| Audit export roles | Export the audit log | + +These are operation-level gates, not blanket restrictions on every read in a +group. Many management reads require authentication only; site scope and +operation-specific checks still apply. Secured-write listing accepts Operator, +Verifier, or Administrator. See the [server authorization matrix](../../src/ZB.MOM.WW.ScadaBridge.ManagementService/ManagementActor.cs). A request lacking the required role exits with code `2` (authorization failure). A bad-credential response (HTTP 401) exits with code `1`. ### Output formats -Every command accepts `--format json` (default) or `--format table`. For JSON output, successful server responses are written to stdout verbatim — the server controls the JSON shape and no re-serialization is applied. Locally-constructed output (errors, `debug stream` events) is serialized by `OutputFormatter` with indentation and camelCase. Table output renders a padded plain-text table derived from the response JSON — arrays become rows, single objects become a two-column `Property / Value` table. Errors are always written as `{ "error": "...", "code": "..." }` to stderr, regardless of format. +Management commands accept `--format json` (default) or `--format table`. Audit export accepts its own export formats. For JSON output, successful server responses are written to stdout verbatim — the server controls the JSON shape and no re-serialization is applied. Locally-constructed output (errors, `debug stream` events) is serialized by `OutputFormatter` with indentation and camelCase. Table output renders a padded plain-text table derived from the response JSON — arrays become rows, single objects become a two-column `Property / Value` table. Errors are always written as `{ "error": "...", "code": "..." }` to stderr, regardless of format. ## Architecture -`Program.cs` invokes `CliApplication.BuildRoot()`, which builds the `RootCommand` tree, attaches the four global options (`--url`, `--username`, `--password`, `--format`), and registers each command group: +[`Program.cs`](../../src/ZB.MOM.WW.ScadaBridge.CLI/Program.cs) invokes +[`CliApplication.BuildRoot()`](../../src/ZB.MOM.WW.ScadaBridge.CLI/CliApplication.cs), +which registers the global options and all 20 command groups, including the +connection browse/certificate extensions. The executable and parity tests use +this same tree. -```csharp -rootCommand.Add(TemplateCommands.Build(urlOption, formatOption, usernameOption, passwordOption)); -rootCommand.Add(InstanceCommands.Build(urlOption, formatOption, usernameOption, passwordOption)); -rootCommand.Add(SiteCommands.Build(urlOption, formatOption, usernameOption, passwordOption)); -rootCommand.Add(DeployCommands.Build(urlOption, formatOption, usernameOption, passwordOption)); -rootCommand.Add(DataConnectionCommands.Build(urlOption, formatOption, usernameOption, passwordOption)); -rootCommand.Add(ExternalSystemCommands.Build(urlOption, formatOption, usernameOption, passwordOption)); -rootCommand.Add(NotificationCommands.Build(urlOption, formatOption, usernameOption, passwordOption)); -rootCommand.Add(SecurityCommands.Build(urlOption, formatOption, usernameOption, passwordOption)); -rootCommand.Add(AuditLogCommands.Build(urlOption, formatOption, usernameOption, passwordOption)); -rootCommand.Add(AuditCommands.Build(urlOption, formatOption, usernameOption, passwordOption)); -rootCommand.Add(HealthCommands.Build(urlOption, formatOption, usernameOption, passwordOption)); -rootCommand.Add(DebugCommands.Build(urlOption, formatOption, usernameOption, passwordOption)); -rootCommand.Add(SharedSchemaCommands.Build(urlOption, formatOption, usernameOption, passwordOption)); -rootCommand.Add(SharedScriptCommands.Build(urlOption, formatOption, usernameOption, passwordOption)); -rootCommand.Add(DbConnectionCommands.Build(urlOption, formatOption, usernameOption, passwordOption)); -rootCommand.Add(ApiMethodCommands.Build(urlOption, formatOption, usernameOption, passwordOption)); -rootCommand.Add(BundleCommands.Build(urlOption, formatOption, usernameOption, passwordOption)); -rootCommand.Add(CachedCallCommands.Build(urlOption, formatOption, usernameOption, passwordOption)); -rootCommand.Add(SecuredWriteCommands.Build(urlOption, formatOption, usernameOption, passwordOption)); -rootCommand.Add(FlowDebugCommands.Build(urlOption, formatOption, usernameOption, passwordOption)); -``` - -Every command's action delegates to `CommandHelpers.ExecuteCommandAsync`, which owns the URL/credential resolution, `ManagementHttpClient` lifetime, and exit-code mapping. This consolidation means the authorization-failure exit code (`2`) is enforced uniformly — including for the `bundle` group, which uses `ExecuteCommandAsync` with a longer 5-minute timeout and a per-command `onSuccess` handler rather than a separate HTTP path. +Management actions use `CommandHelpers.ExecuteCommandAsync` for URL/credential +resolution, HTTP dispatch, formatting, and exit codes. Deployment aliases defer +the overall deadline to the server; bundles and Flow Debugger have explicit +policies. Audit REST and SignalR actions use their respective transports. `CliConfig.Load()` is called at the start of every invocation. It merges `~/.scadabridge/config.json`, environment variables, and any defaults. A malformed or unreadable config file emits a warning to stderr and falls through to the environment variable / command-line precedence chain without crashing. @@ -100,7 +95,7 @@ export SCADABRIDGE_USERNAME=multi-role export SCADABRIDGE_PASSWORD=password scadabridge template create --name "PumpStation" --description "Standard pump station" -scadabridge template attribute add --template-id 3 --name Speed --data-type Float --default-value 0 +scadabridge template attribute add --template-id 3 --name Speed --data-type Float --value 0 scadabridge instance create --name "PS-01" --template-id 3 --site-id 1 scadabridge instance deploy --id 7 ``` @@ -109,26 +104,26 @@ scadabridge instance deploy --id 7 | Group | Subcommands | Role required | |-------|-------------|---------------| -| `template` | `list`, `get`, `create`, `update`, `delete`, `validate`, `resolved-members`, `move`; `attribute add/update/delete`; `alarm add/update/delete`; `script add/update/delete`; `composition add/delete`; `native-alarm-source add/list/update/remove` | `Design` | -| `instance` | `list`, `get`, `create`, `set-bindings`, `set-overrides`, `alarm-override set/delete/list`, `native-alarm-source set/clear/list/import`, `attribute-override delete`, `set-area`, `diff`, `deploy`, `enable`, `disable`, `delete` | `Deployment` | -| `site` | `list`, `get`, `create`, `delete`, `deploy-artifacts`; `area list/create/update/delete` | `Admin` | -| `deploy` | `instance`, `artifacts`, `status` | `Deployment` | -| `data-connection` | `list`, `get`, `create`, `update`, `delete`, `move`; `browse`, `search`, `verify-endpoint`, `certs` | `Design` / `Deployment` | -| `external-system` | `list`, `get`, `create`, `update`, `delete` | `Design` | -| `notification` | `list`, `get`, `create`, `update`, `delete`, `rename`; `recipient add/delete`; `smtp list/create/update`; `sms list/create/update` | `Design` / `Admin` | +| `template` | `list`, `get`, `create`, `update`, `delete`, `validate`, `resolved-members`, `move`; `attribute list/add/update/delete`; `alarm add/update/delete`; `script add/update/delete`; `composition add/delete`; `native-alarm-source add/list/update/remove`; `folder list/create/rename/move/reorder/delete`; `resync-members` | Designer mutations; reads generally authenticated | +| `instance` | `list`, `get`, `create`, `set-bindings`, `bind-module`, `set-overrides`, `import-overrides`, `alarm-override set/delete/list`, `native-alarm-source set/clear/list/import`, `attribute-override delete`, `set-area`, `diff`, `deploy`, `enable`, `disable`, `delete` | `Deployer` | +| `site` | `list`, `get`, `create`, `update`, `delete`, `deploy-artifacts`; `area list/create/update/move/delete` | Administrator site mutations; Designer/Deployer area mutations; Deployer artifact deployment | +| `deploy` | `instance`, `site`, `artifacts`, `status` | `Deployer` | +| `data-connection` | `list`, `get`, `create`, `update`, `delete`, `move`; `browse`, `search`, `verify-endpoint`, `certs` | `Designer` / `Deployer` | +| `external-system` | `list`, `get`, `create`, `update`, `delete`; `method list/get/create/update/delete` | `Designer` | +| `notification` | `list`, `get`, `create`, `update`, `delete`, `rename`; `recipient add/delete`; `smtp list/create/update`; `sms list/create/update` | `Designer` / `Administrator` | | `security` | `api-key list/create/update/delete/set-methods`; `role-mapping list/create/update/delete`; `scope-rule list/add/delete` | `Admin` | | `shared-schema` | `list`, `get`, `create`, `update`, `delete` | Read: authenticated; mutations: Designer | | `secured-write` | `list`, `submit`, `approve`, `reject` | Operator / Verifier / Administrator, enforced per operation | -| `cached-call` | `list`, `retry`, `discard` | Server-enforced operational roles and site scope | +| `cached-call` | `retry`, `discard` (list parked messages with `health parked-messages`) | Server-enforced operational roles and site scope | | `flow-debug` | `run-method`, `run-script`, `run-value-change`, `replay` | Deployer; replay also accepts OperationalAudit roles | -| `shared-script` | `list`, `get`, `create`, `update`, `delete` | `Design` | -| `db-connection` | `list`, `get`, `create`, `update`, `delete` | `Design` | -| `api-method` | `list`, `get`, `create`, `update`, `delete` | `Design` | -| `bundle` | `export`, `preview`, `import` | `Deployment` | -| `audit` | `query`, `export`, `verify-chain` | `OperationalAudit` / `AuditExport` | +| `shared-script` | `list`, `get`, `create`, `update`, `delete` | `Designer` | +| `db-connection` | `list`, `get`, `create`, `update`, `delete` | `Designer` | +| `api-method` | `list`, `get`, `create`, `update`, `delete` | `Designer` | +| `bundle` | `export`, `preview`, `import` | Designer export; Administrator preview/import | +| `audit` | `query`, `export`, `tree`, `backfill-source-node`, `verify-chain` | Audit roles; Administrator backfill; verify-chain is a local placeholder | | `audit-config` | `query` (config-change audit trail; was `audit-log` pre-M8) | `Admin` | -| `health` | `summary`, `site`, `event-log`, `parked-messages` | `Deployment` | -| `debug` | `snapshot`, `stream` | `Deployment` | +| `health` | `summary`, `site`, `event-log`, `parked-messages` | Authenticated reads with server-side scope checks | +| `debug` | `snapshot`, `stream` | `Deployer` | ### Selected examples @@ -198,10 +193,10 @@ The management URL is resolved in this order: `--url` flag → `SCADABRIDGE_MANA ## Dependencies & Interactions -- [Management Service (#18)](./ManagementService.md) — the server-side counterpart. Every CLI command (except `audit query`/`audit export`) translates to a named management command dispatched through `POST /management` to the `ManagementActor`. Role enforcement and LDAP authentication are applied there. The `ManagementCommandRegistry` in Commons maps command types to their names; both sides must stay in sync. +- [Management Service (#18)](./ManagementService.md) — the server-side counterpart. Every CLI command (except the audit REST/placeholder and SignalR stream operations) translates to a named management command dispatched through `POST /management` to the `ManagementActor`. Role enforcement and LDAP authentication are applied there. The `ManagementCommandRegistry` in Commons maps command types to their names; both sides must stay in sync. - [Traefik Proxy (#20)](./TraefikProxy.md) — the recommended connection target. Pointing `--url` at the Traefik address ensures requests reach the active central node without per-command failover logic in the CLI. The `debug stream` command's WebSocket connection (SignalR `/hubs/debug-stream`) also traverses Traefik, which proxies the WebSocket upgrade natively. -- [Audit Log (#23)](./AuditLog.md) — the `audit` command group targets the `GET /api/audit/query` and `GET /api/audit/export` REST endpoints exposed by the Audit Log component, bypassing the management command envelope. The `audit-config` group (formerly `audit-log`) targets the configuration-change audit trail (`IAuditService`) via the standard management envelope. -- [Security & Auth (#10)](./Security.md) — the server resolves LDAP credentials and maps group memberships to ScadaBridge roles (`Admin`, `Design`, `Deployment`, `OperationalAudit`, `AuditExport`). The CLI does not interact with LDAP directly. +- [Audit Log (#23)](./AuditLog.md) — the `audit` command group targets the query, export, tree, and backfill REST endpoints exposed by the Audit Log component, bypassing the management command envelope; `verify-chain` is a local placeholder. The `audit-config` group (formerly `audit-log`) targets the configuration-change audit trail (`IAuditService`) via the standard management envelope. +- [Security & Auth (#10)](./Security.md) — the server resolves LDAP credentials and maps group memberships to ScadaBridge roles (including `Administrator`, `Designer`, `Deployer`, `Operator`, `Verifier`, and audit roles). The CLI does not interact with LDAP directly. - [Commons (#16)](./Commons.md) — owns the management command record types and the `ManagementCommandRegistry` that maps each type to its wire name. The CLI project references Commons for these contracts. - [Transport (#24)](./Transport.md) — the `bundle` command group drives the Transport feature: `bundle export` requests a base64-encoded bundle from the server and streams it to a local `.scadabundle` file; `bundle preview` uploads a file and returns the diff manifest; `bundle import` uploads a file and applies it with a configurable conflict policy. - Design spec: [Component-CLI.md](../requirements/Component-CLI.md). @@ -226,7 +221,7 @@ The central node or Traefik is not reachable at the configured URL. Verify the c ### Authorization failure (exit 2) -The server returned HTTP 403 or an error code of `FORBIDDEN`/`UNAUTHORIZED`. The authenticated user's LDAP groups do not map to a role with permission for the requested operation. Use `security role-mapping list` (requires `Admin`) to inspect role mappings. The `multi-role` test user (`password`) holds `Admin`, `Design`, and `Deployment` roles. +The server returned HTTP 403 or an error code of `FORBIDDEN`/`UNAUTHORIZED`. The authenticated user's LDAP groups do not map to a role with permission for the requested operation. Use `security role-mapping list` (requires `Administrator`) to inspect role mappings. The `multi-role` test user (`password`) holds `Administrator`, `Designer`, and `Deployer` roles. ### Malformed config file warning diff --git a/docs/plans/2026-09-10-management-api-cli-parity-review.md b/docs/plans/2026-09-10-management-api-cli-parity-review.md index 8d8adebf..e7223ff6 100644 --- a/docs/plans/2026-09-10-management-api-cli-parity-review.md +++ b/docs/plans/2026-09-10-management-api-cli-parity-review.md @@ -278,3 +278,10 @@ No device writes or live configuration mutations were used for smoke verificatio Final local logs: `/tmp/parity-final-build.log`, `/tmp/parity-main-cli.log`, `/tmp/parity-deploy.log`, `/tmp/parity-rig-before.json`, `/tmp/parity-rig-after.json`, and `/tmp/parity-live-smoke.json`. + +### CLI documentation follow-up — complete (2026-09-10) + +- [x] Integrated all new operations into the main CLI command reference and synchronized the component overview and requirements. +- [x] Documented the five existing external-system method commands that were missing from the README. +- [x] Corrected stale examples, SMS options, secured-write paging, authorization descriptions, and deployment deadline guidance. +- [x] Checked the executable help tree: all **159 leaf commands** appear in the reference. Documented example option names and local Markdown links/anchors passed validation. Documentation-only follow-up; runtime verification above remains applicable. diff --git a/docs/requirements/Component-CLI.md b/docs/requirements/Component-CLI.md index 4c0a175d..5264fb22 100644 --- a/docs/requirements/Component-CLI.md +++ b/docs/requirements/Component-CLI.md @@ -48,9 +48,10 @@ The CLI uses a hierarchical subcommand structure mirroring the Management Servic scadabridge [options] ``` -All entities are identified by their integer **ID** (via `--id`, `--template-id`, -`--site-id`, etc.), not by name. Create/update commands take individual flags — there -is no `--file` option. The authoritative, always-current reference is the in-repo +Commands identify targets by integer ID, site identifier, connection name, or +canonical member name as shown in their help. Most edits use individual flags; +CSV imports use `--file`, shared schemas use `--schema-file`, and bundles use +`--input`/`--output`. The authoritative, always-current reference is the in-repo `src/ZB.MOM.WW.ScadaBridge.CLI/README.md`; the command lists below mirror the implemented command tree at the time of writing. @@ -58,6 +59,8 @@ tree at the time of writing. ``` scadabridge template list [--detail] [--skip ] [--take ] scadabridge template get --id +scadabridge template resolved-members --id +scadabridge template move --id [--folder-id ] scadabridge template create --name [--description ] [--parent-id ] scadabridge template update --id [--name ] [--description ] [--parent-id ] scadabridge template validate --id @@ -75,6 +78,7 @@ scadabridge template composition add --template-id --instance-name - scadabridge template composition delete --id scadabridge template native-alarm-source add --template-id --name --connection --source-ref [--filter ] [--description ] [--locked] scadabridge template native-alarm-source list --template-id +scadabridge template native-alarm-source update --id --name --connection --source-ref [--filter ] [--description ] [--locked] scadabridge template native-alarm-source remove --id ``` @@ -86,6 +90,8 @@ scadabridge instance create --name --template-id --site-id [--a scadabridge instance set-bindings --id --bindings scadabridge instance bind-module --id --module --connection --object [--dry-run] scadabridge instance set-overrides --id --overrides +scadabridge instance attribute-override delete --instance-id --attribute +scadabridge instance native-alarm-source list --instance-id scadabridge instance import-overrides --id --file scadabridge instance alarm-override set --instance-id --alarm [--trigger-config ] [--priority ] scadabridge instance alarm-override delete --instance-id --alarm @@ -172,6 +178,7 @@ scadabridge data-connection get --id scadabridge data-connection create --site-id --name --protocol [--primary-config ] [--backup-config ] [--failover-retry-count ] scadabridge data-connection update --id --name --protocol [--primary-config ] [--backup-config ] [--failover-retry-count ] scadabridge data-connection delete --id +scadabridge data-connection move --id --site-id ``` #### Site-routed OPC UA operations (browse / search / verify-endpoint / certs) @@ -210,12 +217,33 @@ scadabridge external-system method delete --id scadabridge notification list scadabridge notification get --id scadabridge notification create --name --emails -scadabridge notification update --id [--name ] [--emails ] +scadabridge notification update --id --name --emails +scadabridge notification create --name --type sms --phones +scadabridge notification update --id --name --type sms --phones +scadabridge notification rename --id --name +scadabridge notification recipient add --list-id --name [--email ] [--phone ] +scadabridge notification recipient delete --id scadabridge notification delete --id scadabridge notification smtp list scadabridge notification smtp update --id --server --port --auth-mode --from-address [--tls-mode ] [--credentials ] ``` +Notification rename and individual recipient edits preserve unrelated IDs and +names. Whole-list update replaces recipients. Recipient add requires at least +one contact; the server validates it against the owning list channel. + +```sh +scadabridge notification smtp create --server --port --auth-mode --from-address [--tls-mode ] [--credentials ] [--oauth2-authority ] [--oauth2-scope ] [--transport ] +scadabridge notification sms list +scadabridge notification sms create --account-sid --auth-token [--from-number ] [--messaging-service-sid ] [--api-base-url ] [--connection-timeout-seconds ] [--max-retries ] [--retry-delay-seconds ] +scadabridge notification sms update --id --account-sid [--from-number ] [--messaging-service-sid ] [--api-base-url ] [--auth-token ] [--connection-timeout-seconds ] [--max-retries ] [--retry-delay-seconds ] +``` + +SMS requires at least one sender: a From number or a Messaging Service SID. +Omitted timeout/retry settings preserve update values; creation uses defaults. +Omitted Auth Token preserves the stored token on update; omitted Messaging +Service SID clears it. Provider create/update require Administrator access. + ### Security Commands ``` scadabridge security api-key list @@ -332,6 +360,20 @@ Key behaviors: Unlike `debug snapshot` (which uses the HTTP Management API), `debug stream` uses `Microsoft.AspNetCore.SignalR.Client` as a dependency for its WebSocket transport. +### Shared Schema Commands + +```sh +scadabridge shared-schema list +scadabridge shared-schema get --id +scadabridge shared-schema create --name --schema-file [--scope ] +scadabridge shared-schema update --id --name --schema-file [--scope ] +scadabridge shared-schema delete --id +``` + +Create/update validate JSON syntax locally and schema structure on the server. +Update replaces all three fields; omitted scope clears it. Mutations require +Designer access; reads require authentication. + ### Shared Script Commands ``` scadabridge shared-script list @@ -393,22 +435,31 @@ table). ### Secured-Write Commands (two-person MxGateway writes) -Query two-person (MxGateway) secured writes — the pending queue plus terminal history. -Read-only; gated any-of **Operator** / **Verifier** / **Administrator** (the history -exposes process-sensitive tag values, so it is not open to every authenticated user). +Manage two-person MxGateway writes. Listing is gated any-of **Operator**, +**Verifier**, or **Administrator** because the history exposes process-sensitive +tag values. Submission requires Operator; approval/rejection require Verifier, +with a different authenticated principal from the submitter. ``` scadabridge secured-write list [--status ] [--site ] [--skip ] [--take ] +scadabridge secured-write submit --site --connection --tag-path --value-json --value-type [--comment ] +scadabridge secured-write approve --id [--comment ] +scadabridge secured-write reject --id [--comment ] ``` `--status` / `--site` filter the results (omit to match all). `--skip` / `--take` provide -offset paging (arch-review P3); omitting them uses the historical unpaged default +offset paging (arch-review P3); omitting them uses the default page (`--skip 0 --take 200`). `--take` is clamped to `1..500` server-side. The result carries a `totalCount` (rows matching the filters, ignoring paging) alongside the returned page. Overdue `Pending` rows are swept to `Expired` opportunistically each time the list is queried (server-side TTL, arch-review S2). -The `--format json|table` option is recursive and accepted on every command above. +Approval can execute a real device write. Only a server response with status +`Executed` exits zero; `Failed` or an invalid approval outcome exits one, with +JSON/table details retained. Authorization denial exits two. The server enforces +site scope, expiry, separation of duties, and concurrent decisions. + +The `--format json|table` option is recursive and accepted on every management command above; audit export has its own output formats. ## Configuration diff --git a/src/ZB.MOM.WW.ScadaBridge.CLI/README.md b/src/ZB.MOM.WW.ScadaBridge.CLI/README.md index 5a09083c..3a0ccd87 100644 --- a/src/ZB.MOM.WW.ScadaBridge.CLI/README.md +++ b/src/ZB.MOM.WW.ScadaBridge.CLI/README.md @@ -22,13 +22,13 @@ Every command requires a connection to a running Central node. The management UR scadabridge --url http://central-host:5000 ``` -**Docker:** With the Docker setup, the Central UI (and management API) is available at `http://localhost:9001`: +**Docker:** Use Traefik at `http://localhost:9000` to reach the active central node: ```sh -scadabridge --url http://localhost:9001 +scadabridge --url http://localhost:9000 ``` -For HA failover, use a load balancer URL. The management API is stateless (Basic Auth per request), so any central node can handle any request without sticky sessions. +The management API is stateless (Basic Auth per request), so sticky sessions are unnecessary. Direct node URLs (`9001`/`9002`) are useful for diagnostics; use Traefik for active-node routing and failover. ## Global Options @@ -107,6 +107,24 @@ scadabridge --url --format json template list # unmodified JS | `--skip` | no | Offset paging: number of items to skip (default 0) | | `--take` | no | Offset paging: max items to return (clamped 1..1000; omit for unlimited) | +#### `template resolved-members` + +Inspect effective template members, including inheritance. This does not apply composition or instance overrides. + +```sh +scadabridge template resolved-members --id +``` + +#### `template move` + +Move a template into a folder, or to the root when `--folder-id` is omitted. + +```sh +scadabridge template move --id [--folder-id ] +``` + +Requires Designer access; server-side folder and dependency checks apply. + #### `template get` Get a single template by ID. Unlike `template list`, this returns the **full** template @@ -333,6 +351,16 @@ List the native alarm source bindings on a template. scadabridge --url template native-alarm-source list --template-id ``` +#### `template native-alarm-source update` + +Replace the fields of an existing native alarm source binding. + +```sh +scadabridge template native-alarm-source update --id --name --connection --source-ref [--filter ] [--description ] [--locked] +``` + +The ID, name, connection, and source reference are required. Omitted filter and description clear those values; omitted `--locked` means unlocked. Requires Designer access; the server enforces locking rules. + #### `template native-alarm-source remove` Remove a native alarm source binding from a template (by its ID). @@ -566,6 +594,24 @@ Create Instance page can bind the module groups at creation time (design 2026-08-21); the CLI equivalent is a follow-up `instance set-bindings` / `instance bind-module` against the new instance id. +#### `instance native-alarm-source list` + +List the native alarm source overrides for an instance. + +```sh +scadabridge instance native-alarm-source list --instance-id +``` + +#### `instance attribute-override delete` + +Delete one attribute override while preserving other overrides. + +```sh +scadabridge instance attribute-override delete --instance-id --attribute +``` + +Use a composed name such as `Module.Speed` where applicable. Requires Deployer access and passes through server-side site-scope checks. + #### `instance native-alarm-source set` Override an inherited native alarm source binding for a single instance (upsert). Blank @@ -1143,6 +1189,16 @@ scadabridge --url data-connection update --id --name --prot | `--backup-config` | no | Backup protocol-specific configuration as a JSON string | | `--failover-retry-count` | no | Retries before failover to the backup configuration (default: `3`) | +#### `data-connection move` + +Move a data connection to a destination site. + +```sh +scadabridge data-connection move --id --site-id +``` + +Both IDs are required. Requires Designer access; the server validates the destination and any dependency restrictions. + #### `data-connection delete` Delete a data connection. @@ -1326,6 +1382,48 @@ scadabridge --url external-system delete --id --- +#### `external-system method list` + +List the methods belonging to an external system. + +```sh +scadabridge external-system method list --external-system-id +``` + +#### `external-system method get` + +Read one method definition. + +```sh +scadabridge external-system method get --id +``` + +#### `external-system method create` + +Create an external HTTP method. Requires Designer access. + +```sh +scadabridge external-system method create --external-system-id --name --http-method --path [--params ] [--return ] +``` + +#### `external-system method update` + +Update an external HTTP method. Requires Designer access. + +```sh +scadabridge external-system method update --id [--name ] [--http-method ] [--path ] [--params ] [--return ] +``` + +#### `external-system method delete` + +Delete a method by ID. Requires Designer access. + +```sh +scadabridge external-system method delete --id +``` + +--- + ### `notification` — Manage notification lists #### `notification get` @@ -1392,6 +1490,36 @@ scadabridge --url notification update --id --name --type sm | `--emails` | conditional | Comma-separated recipient email addresses. **Required for `--type email`; rejected for `--type sms`.** | | `--phones` | conditional | Comma-separated recipient phone numbers in E.164. **Required for `--type sms`; rejected for `--type email`.** | +#### `notification rename` + +Rename a list without replacing its recipients. + +```sh +scadabridge notification rename --id --name +``` + +Requires Designer access. Unrelated recipient IDs, names, and contacts are preserved. + +#### `notification recipient add` + +Add an individual named recipient to an existing list. + +```sh +scadabridge notification recipient add --list-id --name [--email ] [--phone ] +``` + +Provide at least one of `--email` or `--phone`. The server checks the contact against the owning list channel. Requires Designer access; other recipients remain unchanged. + +#### `notification recipient delete` + +Delete one recipient by recipient ID. + +```sh +scadabridge notification recipient delete --id +``` + +Requires Designer access. Only the specified recipient is removed. + #### `notification delete` Delete a notification list. @@ -1412,6 +1540,16 @@ Show the current SMTP configuration. scadabridge --url notification smtp list ``` +#### `notification smtp create` + +Create a new SMTP configuration. Requires Administrator access. + +```sh +scadabridge notification smtp create --server --port --auth-mode --from-address [--tls-mode ] [--credentials ] [--oauth2-authority ] [--oauth2-scope ] [--transport ] +``` + +Uses the same settings as `smtp update`, without `--id`. This sends the distinct create contract; update with ID zero is not a creation operation. The server validates transport/authentication compatibility. + #### `notification smtp update` Update the SMTP configuration. @@ -1445,22 +1583,35 @@ Show the current SMS (Twilio) configuration. The Twilio **Auth Token is never re scadabridge --url notification sms list ``` +#### `notification sms create` + +Create a new Twilio configuration. Requires Administrator access. + +```sh +scadabridge notification sms create --account-sid --auth-token [--from-number ] [--messaging-service-sid ] [--api-base-url ] [--connection-timeout-seconds ] [--max-retries ] [--retry-delay-seconds ] +``` + +Account SID and auth token are required. Supply at least one sender: `--from-number` or `--messaging-service-sid`. Timeout must be positive; retries/delay must be nonnegative. Omitted policy settings use creation defaults. Explicit zero retry settings retain the outbox fallback semantics; they do not disable retries. + #### `notification sms update` Update the SMS (Twilio) configuration. ```sh -scadabridge --url notification sms update --id --account-sid --from-number [--messaging-service-sid ] [--api-base-url ] [--auth-token ] +scadabridge --url notification sms update --id --account-sid [--from-number ] [--messaging-service-sid ] [--api-base-url ] [--auth-token ] [--connection-timeout-seconds ] [--max-retries ] [--retry-delay-seconds ] ``` | Option | Required | Description | |--------|----------|-------------| | `--id` | yes | SMS config ID | | `--account-sid` | yes | Twilio Account SID | -| `--from-number` | yes | Sender phone number (E.164) | +| `--from-number` | conditional | Sender phone number (E.164); provide this and/or `--messaging-service-sid` | | `--messaging-service-sid` | no | Twilio Messaging Service SID. **Omitting it CLEARS the stored value** (the update overwrites it). | | `--api-base-url` | no | API base URL override (preserves existing if omitted) | | `--auth-token` | no | Twilio Auth Token. **Omitting it PRESERVES the stored token.** Never printed back. | +| `--connection-timeout-seconds` | no | Positive timeout; omission preserves the existing value | +| `--max-retries` | no | Nonnegative retry setting; omission preserves it; zero retains outbox fallback semantics | +| `--retry-delay-seconds` | no | Nonnegative delay; omission preserves it; zero retains outbox fallback semantics | --- @@ -1952,6 +2103,56 @@ single `Property`/`Value` cell (deferred-work register row 57). --- +### `shared-schema` — Manage the shared JSON schema library + +Reads require authentication; create/update/delete require Designer access. + +#### `shared-schema list` + +List all shared schemas. + +```sh +scadabridge shared-schema list +``` + +#### `shared-schema get` + +Get one shared schema by ID. + +```sh +scadabridge shared-schema get --id +``` + +#### `shared-schema create` + +Create a schema from a UTF-8 JSON file. + +```sh +scadabridge shared-schema create --name --schema-file [--scope ] +``` + +The CLI validates file readability and JSON syntax; the server validates schema structure and uniqueness. + +#### `shared-schema update` + +Replace a schema's name, scope, and document. + +```sh +scadabridge shared-schema update --id --name --schema-file [--scope ] +``` + +The CLI validates file readability and JSON syntax; the server validates schema structure and uniqueness. Omit `--scope` to clear it on update. + +#### `shared-schema delete` + +Delete a shared schema by ID. + +```sh +scadabridge shared-schema delete --id +``` + +--- + ### `shared-script` — Manage shared scripts #### `shared-script list` @@ -2270,9 +2471,9 @@ scadabridge --url cached-call discard --site-id --tracked-operati --- -### `secured-write` — Query two-person write requests +### `secured-write` — Manage two-person write requests -`secured-write list` reads the MxGateway secured-write queue and terminal history. It requires any of Operator, Verifier or Administrator and applies the caller's site scope. This group also exposes `submit`, `approve`, and `reject` through the same server-side authorization and separation-of-duties checks as the UI; see the parity additions below. +`secured-write list` reads the MxGateway secured-write queue and terminal history. It requires any of Operator, Verifier or Administrator and applies the caller's site scope. This group also exposes `submit`, `approve`, and `reject` through the same server-side authorization and separation-of-duties checks as the UI. ```sh scadabridge secured-write list --status Pending --site site-a --skip 0 --take 50 @@ -2280,6 +2481,38 @@ scadabridge secured-write list --status Pending --site site-a --skip 0 --take 50 `--status` and `--site` are optional filters. `--skip` defaults to 0; `--take` defaults to 200 and is clamped server-side to 1–500. Global connection, authentication and output-format options apply. +#### `secured-write submit` + +Submit a pending MxGateway write as an Operator. A distinct Verifier must approve or reject it. + +```sh +scadabridge secured-write submit --site --connection --tag-path --value-json --value-type [--comment ] +``` + +The first five options are required. JSON strings must include JSON quotes, for example `--value-json '"Auto"' --value-type String`. The server validates the type/value pairing and site scope and derives identity from authentication. + +#### `secured-write approve` + +Approve and execute a pending write as a Verifier. This can write to the real device. + +```sh +scadabridge secured-write approve --id [--comment ] +``` + +The 64-bit request ID is required. The server enforces a different principal from the submitter, expiry, site scope, and concurrent decision handling. Exit 0 requires `status: Executed`; a failed execution or invalid response exits 1, preserving response details. Authorization denial exits 2. + +#### `secured-write reject` + +Reject a pending write as a Verifier. + +```sh +scadabridge secured-write reject --id [--comment ] +``` + +The 64-bit request ID is required. The server enforces separation of duties and site scope. A rejection does not execute the device write. + +--- + ### `flow-debug` — Trace an inbound API, script, or value-change flow end to end The CLI face of the **Flow Debugger** (design `docs/plans/2026-09-05-flow-debugger-design.md`, @@ -2411,6 +2644,15 @@ run's outbound API / DB / notification rows, which are always audited. --- +## Deployment deadlines + +`deploy instance`, `instance deploy`, `deploy artifacts`, `site deploy-artifacts`, +and `deploy site` defer the overall deadline to the Management API. They keep the +bounded socket-connect timeout but do not impose the ordinary 30-second client +request deadline on a deployment that may still be applying. The API's configured +long-running timeout remains authoritative. Ordinary commands retain a 30-second +client deadline; explicit bundle/Flow Debugger timeouts retain their own policies. + ## Architecture Notes The CLI connects to the Central cluster over **HTTP** (`ManagementHttpClient`), posting to the `/management` endpoints at the configured `managementUrl` — normally the Traefik load balancer, which routes to the active central node. It carries HTTP **Basic** credentials from `--username`/`--password`. Central's endpoint handler asks the `ManagementActor` in-process via `ManagementActorHolder`. @@ -2419,109 +2661,21 @@ There is no Akka dependency in the CLI at all: it does not join the cluster, doe An `HttpClient` is created per command invocation and disposed when the command completes. -Role enforcement is applied by the ManagementActor on the server side. The CLI authenticates against LDAP using `--username` / `--password`, resolves LDAP group memberships, then maps groups to ScadaBridge roles (Admin, Design, Deployment) via role mappings configured in the security settings. Operations require the appropriate role — for example, creating templates requires `Design`, deploying requires `Deployment`. In the test environment, use the `multi-role` user (password: `password`) which has all three roles. +Role enforcement is applied by the ManagementActor on the server side. The CLI sends `--username` / `--password` as HTTP Basic credentials. The server performs LDAP authentication, resolves group memberships, and maps them to roles such as Administrator, Designer, and Deployer. Operations require the appropriate role — for example, creating templates requires `Designer`, deploying requires `Deployer`. In the test environment, use the `multi-role` user (password: `password`) which has all three roles. ## Issues & Missing Features If you encounter bugs, unexpected behavior, or missing features in the CLI, log them in [`cli_issues.md`](../../cli_issues.md) in the project root. Include a brief description, the command involved, and any relevant error output. - ## Management parity additions (2026-09-10) -The production CLI exposes all 149 HTTP-eligible management contracts across 20 -command groups. `CliApplication.BuildRoot()` is used both by the executable and -by the HTTP-dispatch parity test. Site-only `ReadTagValues` and the retired -`ResolveRoles` flow are not accepted by `POST /management`; binding tests continue -using the site's existing transport. +All 149 HTTP-eligible management contracts are exposed across 20 command groups. +The new operations are documented in their main command sections above, including +[shared schemas](#shared-schema--manage-the-shared-json-schema-library), +[notifications](#notification--manage-notification-lists), and +[secured writes](#secured-write--manage-two-person-write-requests). +[Deployment deadlines](#deployment-deadlines) apply to all deployment aliases. -### Shared schema library - -```sh -scadabridge shared-schema list -scadabridge shared-schema get --id 7 -scadabridge shared-schema create --name PumpPayload --schema-file ./pump.schema.json --scope Pump -scadabridge shared-schema update --id 7 --name PumpPayload --schema-file ./pump.schema.json -scadabridge shared-schema delete --id 7 -``` - -Create/update read UTF-8 JSON from `--schema-file`; the server validates schema -structure and uniqueness. Update replaces the name, schema, and scope; omitting -`--scope` clears it. Mutations require Designer access. - -### Individual notification edits and provider creation - -```sh -scadabridge notification rename --id 4 --name "Shift engineers" -scadabridge notification recipient add --list-id 4 --name "On-call engineer" --email engineer@example.com -scadabridge notification recipient delete --id 18 -scadabridge notification smtp create --server smtp.example.com --port 587 --auth-mode None --from-address alerts@example.com -scadabridge notification sms create --account-sid AC0123456789abcdef0123456789abcdef --from-number +15551234567 --auth-token "" -``` - -Rename and individual recipient edits preserve unrelated recipient IDs and names. -Recipient add accepts `--email` and/or `--phone`; the server checks them against -its owning list's channel. Existing `notification update` retains its whole-list -recipient replacement semantics. - -SMTP/SMS create accept their update options except `--id` and send distinct create -contracts. SMS create requires an auth token and a sender (`--from-number` and/or -`--messaging-service-sid`). Provider creation/update requires Administrator access. - -SMS create and update also accept: - -| Option | Meaning | -|---|---| -| `--connection-timeout-seconds` | Positive connection timeout | -| `--max-retries` | Nonnegative retry setting | -| `--retry-delay-seconds` | Nonnegative retry delay | - -Omission preserves existing update values or uses creation defaults. Explicit -zero retry settings retain the existing outbox fallback semantics; they do not -promise that delivery will never retry. Existing secret-preservation and -messaging-service-ID clearing behavior on update is unchanged. - -### Template, instance, and connection edits - -```sh -scadabridge template resolved-members --id 3 -scadabridge template move --id 3 --folder-id 8 -scadabridge template native-alarm-source update --id 5 --name Pressure --connection Plc --source-ref Pressure --locked -scadabridge instance attribute-override delete --instance-id 7 --attribute Module.Speed -scadabridge instance native-alarm-source list --instance-id 7 -scadabridge data-connection move --id 9 --site-id 2 -``` - -`resolved-members` inspects effective inherited template members, without applying -composition or instance overrides. Omit `--folder-id` to move a template to the -root. Native-alarm-source update replaces all contract fields: optional `--filter` -and `--description` clear when omitted, and omitted `--locked` means false. -Individual attribute-override deletion preserves other overrides. Server-side -role, site-scope, locking, and dependency checks remain authoritative. - -### Secured-write lifecycle - -```sh -scadabridge secured-write submit --site site-a --connection Plc --tag-path Pump.Setpoint --value-json 42 --value-type Int32 --comment "Approved operating procedure" -scadabridge secured-write approve --id 123 --comment "Verified setpoint" -scadabridge secured-write reject --id 124 --comment "Incorrect target" -``` - -Submit requires Operator access; approve/reject require Verifier access. The -server derives identity from authentication and enforces a different principal -for the verification decision. Approval can execute a real device write. -`--value-json` must be valid JSON; the server validates the value/type pairing. -IDs for approve/reject are 64-bit integers; `--comment` is optional. - -Approval exits zero only for an `Executed` response. A server-reported execution -failure exits one while retaining the response details; malformed or unexpected -approval responses also fail. Authorization denial exits two. JSON/table output -continues to use the standard formatter. - -### Deployment deadline policy - -`deploy instance`, `instance deploy`, `deploy artifacts`, `site deploy-artifacts`, -and `deploy site` defer the overall deadline to the Management API. They keep the -bounded socket-connect timeout but do not impose the ordinary 30-second client -request deadline on a deployment that may still be applying. The API's configured -long-running timeout remains authoritative. Ordinary commands retain a 30-second -client deadline; explicit bundle/Flow Debugger timeouts retain their own policies. +`CliApplication.BuildRoot()` constructs the executable command tree and the tree +used by the HTTP parity test. Site-only `ReadTagValues` and retired `ResolveRoles` +are excluded from `POST /management`; site binding tests retain their transport.