docs: grouped connection bindings — Central UI Configure page, ResolvedAttribute.DefaultDataSourceReference, CLI instance bind-module; correct the stale 'set-bindings REPLACES all' claim (server upserts) in README + help text

This commit is contained in:
Joseph Doherty
2026-08-20 16:01:33 -04:00
parent 77f4af0d64
commit 5e04e81ba0
6 changed files with 44 additions and 9 deletions
@@ -1,6 +1,6 @@
# Grouped Connection Bindings — design
**Date:** 2026-08-20 · **Status:** approved, implementation pending · **Components:** Central UI (#15), Template Engine (flattening), CLI
**Date:** 2026-08-20 · **Status:** implemented 2026-08-20 on `feat/grouped-connection-bindings` (plan `2026-08-20-grouped-connection-bindings.md`, 9 tasks) · **Components:** Central UI (#15), Template Engine (flattening), CLI
## Problem
+1
View File
@@ -84,6 +84,7 @@ scadabridge instance list [--site-id <id>] [--template-id <id>] [--search <term>
scadabridge instance get --id <id>
scadabridge instance create --name <name> --template-id <id> --site-id <id> [--area-id <id>]
scadabridge instance set-bindings --id <id> --bindings <json>
scadabridge instance bind-module --id <id> --module <name> --connection <id> --object <path> [--dry-run]
scadabridge instance set-overrides --id <id> --overrides <json>
scadabridge instance import-overrides --id <id> --file <path.csv>
scadabridge instance alarm-override set --instance-id <id> --alarm <name> [--trigger-config <json>] [--priority <n>]
+5 -1
View File
@@ -119,7 +119,11 @@ Central cluster only. Sites have no user interface.
### Instance Management (Deployment Role)
- Create instances from templates at a specific site.
- Assign instances to areas.
- Bind data connections — **per-attribute binding** where each attribute with a data source reference individually selects its data connection from the site's available connections. **Bulk assignment** supported: select multiple attributes and assign a data connection to all of them at once. Each row also exposes:
- Bind data connections — **per-attribute binding** where each attribute with a data source reference individually selects its data connection from the site's available connections. **The binding rows come from the instance's flattened configuration** (`IFlatteningPipeline.FlattenAndValidateAsync(id, validateScripts:false)`), so every data-sourced attribute the deploy gate will check appears — composed feature-module members (`LeftMESReceiver.MoveInFlag`, `LeftReactorSide.LeakTest.DeltaVac`, …) included. (Until 2026-08-20 the panel was built from the template's *direct* attributes only, which left 264 of a composed QA instance's 280 bindings invisible and made the instance undeployable from the UI; design `docs/plans/2026-08-20-grouped-connection-bindings-design.md`.) Only a genuine flatten *failure* falls back to the direct-attribute list, with a visible warning banner; a validation failure (e.g. unbound attributes — the very thing the panel exists to fix) is not a reason to fall back.
- **Grouped by module.** Rows are grouped by the first segment of the canonical name — `(instance)` for direct attributes first, then one collapsible group per composed module in name order; nested modules fold into their first segment (`LeftReactorSide` shows *incl. LeakTest*) and members keep their nested tail (`LeakTest.DeltaVac`). The pure grouping/inference/apply logic is `BindingGroupBuilder` (CentralUI `Services/`), unit-tested without rendering the page.
- **Group header = connection + Object + Apply.** Each group header carries a connection select and an **Object** input; **Apply to n** rewrites every member of the group to `<object>.<memberTail>` on that connection (a blank Object clears the overrides so the template default applies; surrounding whitespace and a trailing `.` are tolerated). The Object input is pre-filled with the **inferred** shared prefix when every overridden member agrees (`MESReceiver_022.MoveInFlag` + `MESReceiver_022.ScanStateCmd` ⇒ `MESReceiver_022`; nested object paths such as `Left_002.DelmiaReceiver` infer too); when members disagree, or an override does not end in its member tail, the group shows a **mixed** pill and the input's placeholder invites an object to unify them. Apply edits the in-memory maps only — **Save Bindings** persists every row (hidden rows included) through the existing `SetConnectionBindings` upsert, and the header shows an `n changed · unsaved` pill until then.
- **Pills and filters.** The card header totals unbound rows and mixed groups; each group shows `n unbound` (red) or `bound`, plus `mixed`; unbound rows render highlighted. A **Show only unbound** filter hides bound rows and fully-bound groups; **Expand all** opens every group. The legacy all-groups connection select (**Assign all to…**) is retained. A composed member whose template default is the bare member name (`MoveInFlag`) carries a ⚠ marker — such a default cannot be a real address and the deploy gate will reject the instance until an override (or a group Object) is set.
- **Template default** column shows `ResolvedAttribute.DefaultDataSourceReference` — the reference as resolved from the template chain *before* the per-instance override — so the operator always sees both the default and the effective override. **Test Bindings** (button label carries the testable-row count) reads every bound row with an effective path, composed rows included.
- **Override** — optional per-attribute OPC UA node id (or other protocol address). When set, replaces the template's `DataSourceReference` at flattening time; when blank, the template default is used. The greyed placeholder shows the template default for context.
- **Browse…** — opens the OPC UA Tag Browser dialog, populated live from the site's OPC UA server via `BrowseOpcUaNodeCommand`. Visible only when the row's connection uses the OPC UA protocol; disabled until a connection is picked on that row. The dialog lazy-loads the address space, supports manual node-id entry as a fallback, and remains usable when the site or its OPC UA session is offline (the manual-paste field stays active even on error). The dialog adds:
- **Load more** — when a browse level is truncated, a "Load more" affordance fetches the next page using the server's continuation point (`BrowseNext`); an expired continuation point falls back to a fresh browse.
@@ -292,7 +292,7 @@ Each flattened configuration output includes a **revision hash** (computed from
- Staleness detection: comparing the deployed revision to the current template-derived revision without a full diff.
- Diff correlation: ensuring diffs are computed against a consistent baseline.
The override flows into the flattened attribute's `DataSourceReference` and therefore participates in the revision hash — changes to an instance's binding overrides re-deploy as expected.
The override flows into the flattened attribute's `DataSourceReference` and therefore participates in the revision hash — changes to an instance's binding overrides re-deploy as expected. The pre-override value is preserved alongside it as **`ResolvedAttribute.DefaultDataSourceReference`** (2026-08-20, grouped connection bindings): populated by `FlatteningService.ApplyConnectionBindings` for *every* data-sourced attribute, bound or not (equal to `DataSourceReference` when no override is set; null for non-data-sourced attributes). It is central-side presentation data for the Configure page's *Template default* column and is **deliberately excluded from the revision hash** — `RevisionHashService` projects through `HashableAttribute`, which does not carry it — so adding it changed no deployed revision (pinned by `DefaultDataSourceReferenceTests`).
The revision hash covers the resolved attributes, alarms, scripts, connections, **and native alarm source bindings** (`ResolvedNativeAlarmSource`: canonical name, connection name, source reference, condition filter, and locked state). Native alarm sources were folded into the hash in migration TemplateEngine-011; the hashable projection is populated null-when-empty and the canonical serializer omits null properties, so every native-source-free configuration keeps its byte-identical pre-migration hash. **Migration note:** native-source-bearing instances will report stale once after upgrade; redeploy clears it — deliberate.