fix(cli): resolve CLI-014..016 — re-triage update-command contract, doc-surface drift, table-column union

This commit is contained in:
Joseph Doherty
2026-05-17 03:18:16 -04:00
parent 0ba4e49e11
commit f82bcbed7c
6 changed files with 341 additions and 61 deletions

View File

@@ -8,7 +8,7 @@
| Last reviewed | 2026-05-17 |
| Reviewer | claude-agent |
| Commit reviewed | `39d737e` |
| Open findings | 3 |
| Open findings | 0 |
## Summary
@@ -575,11 +575,27 @@ The CLI test suite went from 42 to 77 passing tests.
| | |
|--|--|
| Severity | Medium |
| Category | Correctness & logic bugs |
| Status | Open |
| Severity | Low |
| Category | Design-document adherence |
| Status | Resolved |
| Location | `src/ScadaLink.CLI/Commands/TemplateCommands.cs:77`, `src/ScadaLink.CLI/Commands/SiteCommands.cs:86`, `src/ScadaLink.CLI/Commands/ExternalSystemCommands.cs:40-42`, `src/ScadaLink.CLI/Commands/DataConnectionCommands.cs:39-40`, `src/ScadaLink.CLI/Commands/NotificationCommands.cs:40-41`, `src/ScadaLink.CLI/Commands/ApiMethodCommands.cs:79` |
**Re-triage 2026-05-17:** the finding was originally filed as a Medium "Correctness &
logic bugs" issue, but verification against the Commons message contracts shows the
**code is correct** and the defect is purely documentation drift. Every `Update*Command`
record (`UpdateTemplateCommand`, `UpdateSiteCommand`, `UpdateDataConnectionCommand`,
`UpdateExternalSystemCommand`, `UpdateNotificationListCommand`, `UpdateApiMethodCommand`,
`UpdateTemplateAttribute/Alarm/ScriptCommand`, `UpdateRoleMappingCommand`,
`UpdateSharedScriptCommand`, `UpdateDatabaseConnectionDefCommand`, `UpdateAreaCommand`)
carries **non-nullable** "core" fields — they are *whole-entity replace* records, not
sparse patches. Marking the corresponding `--name` / `--protocol` / `--script` flags
`Required = true` is therefore the *correct* CLI behaviour: making them optional would
let an omitted flag send `null`/empty and silently blank the field server-side. Adopting
the sparse-patch model (recommendation option a) would require changing the Commons
records and the server-side Management handlers — both outside this module's editable
surface. Re-triaged to Low / Design-document adherence; resolved per recommendation
option (b).
**Description**
The design doc presents `update` commands with all non-`--id` fields as optional, e.g.
@@ -618,7 +634,20 @@ entity. Option (a) matches the documented surface and the typical CLI expectatio
**Resolution**
_Unresolved._
Resolved 2026-05-17 (commit pending) per recommendation option (b). Verification of the
Commons `Update*Command` records confirmed whole-replace is the intentional contract, so
the CLI's `Required = true` flags are correct and were left unchanged. The in-repo
`src/ScadaLink.CLI/README.md` — which is authoritative and previously listed every
`update` core field as optional `[--name]` — was corrected: the core flags
(`--name`/`--protocol`/`--script`/`--code`/`--emails`/`--endpoint-url`/`--auth-type`/
`--data-type`/`--trigger-type`/`--priority`/`--connection-string`/`--ldap-group`/`--role`)
are now marked `required: yes`, the command synopses drop the `[...]`, and each `update`
section states that an update replaces the whole entity. Added
`UpdateCommandContractTests` (10 tests) pinning the whole-replace contract — every
listed `update` command's core flags must be `Required`, and the genuinely-sparse
`external-system method update` must remain optional. Note: `docs/requirements/Component-CLI.md`
also still shows these flags as optional, but it is outside this module's editable
surface — that doc-side correction is owned by the docs surface.
### CLI-015 — `Component-CLI.md` command surface has drifted again in two places
@@ -626,8 +655,16 @@ _Unresolved._
|--|--|
| Severity | Low |
| Category | Design-document adherence |
| Status | Open |
| Location | `docs/requirements/Component-CLI.md:75`, `docs/requirements/Component-CLI.md:125-126` (vs. `src/ScadaLink.CLI/Commands/TemplateCommands.cs:404-413`, `src/ScadaLink.CLI/Commands/DataConnectionCommands.cs:41`, `:86`) |
| Status | Resolved |
| Location | `docs/requirements/Component-CLI.md:75`, `docs/requirements/Component-CLI.md:125-126`, `src/ScadaLink.CLI/README.md` (vs. `src/ScadaLink.CLI/Commands/TemplateCommands.cs:404-413`, `src/ScadaLink.CLI/Commands/DataConnectionCommands.cs:41`, `:86`) |
**Re-triage 2026-05-17:** verification found the same two drifts also present in the
in-repo `src/ScadaLink.CLI/README.md` (the authoritative reference): its
`template composition delete` section used the non-existent `--template-id` /
`--instance-name` form, and `data-connection create`/`update` documented only
`--configuration` without the canonical `--primary-config` flag (`--configuration` is in
fact an alias of `--primary-config`). The README is in this module's editable surface;
`docs/requirements/Component-CLI.md` is not.
**Description**
@@ -655,7 +692,15 @@ documented elsewhere.
**Resolution**
_Unresolved._
Resolved 2026-05-17 (commit pending). Both drifts were present in the in-repo
`src/ScadaLink.CLI/README.md` and were corrected there (the README is this module's
authoritative reference): `template composition delete` now documents the real single
`--id <int>` form, and `data-connection create`/`update` now document `--primary-config`
(with the `--configuration` alias noted) alongside `--backup-config` and
`--failover-retry-count`. Added `CommandTreeTests.TemplateCompositionDelete_IsKeyedByIdOnly`
pinning the composition-delete surface (`--id` present; `--template-id`/`--instance-name`
absent). The corresponding edit to `docs/requirements/Component-CLI.md:75,125-126` is
outside this module's editable surface and remains for the docs surface to apply.
### CLI-016 — `WriteAsTable` derives columns from the first array element only
@@ -663,7 +708,7 @@ _Unresolved._
|--|--|
| Severity | Low |
| Category | Correctness & logic bugs |
| Status | Open |
| Status | Resolved |
| Location | `src/ScadaLink.CLI/Commands/CommandHelpers.cs:184-200` |
**Description**
@@ -687,4 +732,12 @@ rows, so no element's data is silently dropped.
**Resolution**
_Unresolved._
Resolved 2026-05-17 (commit pending). Root cause confirmed — `WriteAsTable` derived the
header set from `items[0].EnumerateObject()` only, dropping any property unique to a
later element. The header set is now computed as the union of property names across
*every* object element of the array, in first-seen order (a `HashSet` guards duplicates
while an ordered list preserves column order). Rows already project against the header
list and `OutputFormatter.WriteTable` pads missing cells, so heterogeneous arrays now
render every column. Regression tests added in `TableHeaderUnionTests` (3 tests:
later-element-only column included, first-seen column order preserved,
first-element-extra column still rendered).