feat(docker): seed + verify scripts for the MES-reportable alarm demo — rig live gate PASS
Task 13/14. seed-mes-alarm-demo.sh builds a template whose three computed alarms differ in how they acquire the flag (template --mes-reportable, nothing, instance override over a template false), a site Call script projecting them through Alarms.CurrentAsync() / MesReportableAsync(), and two inbound methods + a scoped API key. Idempotent by name lookup. verify-mes-alarm-demo.sh drives the real inbound HTTP surface: PASS 14/14 on the docker rig (migration 20260911135501_AddAlarmMesReportable applied at boot). Playwright AlarmOverride_MesReportable_RoundTrips passed. Results + the facts the gate established are in design doc section 8. Claude-Session: https://claude.ai/code/session_01Y5ef34iUUMjmwnMpL9xgmB
This commit is contained in:
@@ -235,3 +235,80 @@ Central inbound method AlarmStatus ── Route.To(MachineCode).Call("AlarmStatu
|
||||
editor), `Component-ConfigurationDatabase.md` (migration), CLI `README.md`, `CLAUDE.md` native-alarm
|
||||
bullet (flag vs band), `2026-06-30-mes-alarm-status-api.md` §6.1 (note: superseded for computed
|
||||
alarms), deferred-work register (native source-level flag; central badge/proto).
|
||||
|
||||
---
|
||||
|
||||
## 8. Rig live gate results (2026-09-11)
|
||||
|
||||
Seeded by `docker/seed-mes-alarm-demo.sh`, gated by `docker/verify-mes-alarm-demo.sh` against the
|
||||
`docker/` rig (8 nodes, rebuilt from this branch). Config DB migration applied on boot:
|
||||
|
||||
```
|
||||
[14:19:43 INF] [Central/scadabridge-central-a] Applying migration '20260911135501_AddAlarmMesReportable'.
|
||||
```
|
||||
|
||||
### 8.1 Seeded objects
|
||||
|
||||
| Object | Id | Notes |
|
||||
|---|---|---|
|
||||
| Template `MesAlarmDemo` | 5255 | `Temperature` Double **static**, value 20 |
|
||||
| Alarm `HighTemp` | — | HiLo hi=80 hihi=95, priority 500, **`--mes-reportable`** |
|
||||
| Alarm `TempSpike` | — | ValueMatch `100`, priority 300, not reportable |
|
||||
| Alarm `SensorFault` | — | ValueMatch `-1`, priority 700, **not reportable on the template** |
|
||||
| Site script `AlarmStatus` (Call) | 1045 | `Alarms.CurrentAsync()` / `Alarms.MesReportableAsync()` + the filter params |
|
||||
| Instance `MesAlarmDemo01` (site-a) | 3847 | `instance alarm-override set --alarm SensorFault --mes-reportable true` |
|
||||
| Inbound method `AlarmStatus` | 2224 | nested `AlarmFilter` object param, routes via `Route.To(MachineCode)` |
|
||||
| Inbound method `SimpleAlarmStatus` | 2225 | flagged-only shorthand |
|
||||
| API key `mes-alarm-demo` | — | scoped to both methods; token printed once |
|
||||
|
||||
Second seed run reported `exists` for template/attribute/all three alarms/instance, `updated` for
|
||||
both scripts and both methods, and refused to mint a second API key — **idempotent**, same ids.
|
||||
|
||||
### 8.2 Gate: `PASS 14/14`
|
||||
|
||||
| Step | Stimulus | Result |
|
||||
|---|---|---|
|
||||
| A1 | `96` → `AlarmStatus` | `WasSuccessful=true`, exactly 1 row, `HighTemp` |
|
||||
| A2 | same | `IsFlaggedForMES=true` (template flag), `Severity=500`, `StatusCode="Triggered.Acked"`, `AckDT=null` |
|
||||
| A3 | same | `HierarchicalName="MesAlarmDemo01.HighTemp"` |
|
||||
| H | same moment | `debug snapshot --id 3847` shows `HighTemp` Active (`condition.active=true`) |
|
||||
| B1/B2 | `100` → `FlaggedOnly=false` | 2 rows: `HighTemp` **true**, `TempSpike` **false** (HiHi still active at 100, so both) |
|
||||
| B3 | `100` → `FlaggedOnly=true` | 1 row, `HighTemp`; `TempSpike` excluded by `MesReportableAsync()` |
|
||||
| C1/C2 | `-1` → `SimpleAlarmStatus` | 1 row, `SensorFault`, `IsFlaggedForMES=true`, `Severity=700` — **the instance override beat the template's `false`** |
|
||||
| C3 | `-1` → `AlarmStatus` | `SensorFault` only; `HighTemp` absent (−1 trips no high band) |
|
||||
| D1 | `-1` + `MinSeverity=600` | `SensorFault` only (700 ≥ 600; HighTemp 500 and TempSpike 300 filtered) |
|
||||
| E1 | `20` | `WasSuccessful=true`, `Alarms=[]` |
|
||||
| F1 | `MachineCode=DelmiaSim01` | `WasSuccessful=false`, `ErrorText` contains "not supported on machine" (`not found on instance` catch) |
|
||||
| G1 | `MachineCode=NoSuchMachine` | `WasSuccessful=false`, `ErrorText` contains "not found" |
|
||||
|
||||
Browser round-trip: `dotnet test … --filter AlarmOverride_MesReportable_RoundTrips` →
|
||||
**Passed: 1, Failed: 0** (12 s).
|
||||
|
||||
### 8.3 Facts the gate established (read before extending the demo)
|
||||
|
||||
- **`StatusCode` is `"Triggered.Acked"` for every computed alarm, and `AckDT` is always `null`.**
|
||||
`AlarmConditionStateFactory.ForComputed` reports `Acknowledged: true` — computed alarms have no
|
||||
ack-back, so there is nothing to be unacknowledged about. A filter such as the script's
|
||||
`IncludeAcked=false` therefore drops *every* computed alarm; it is only meaningful over mirrored
|
||||
native conditions. The plan's expected `"Triggered"` was wrong and the assertion was corrected.
|
||||
- **`Severity` is the alarm's effective priority**, and for a HiLo alarm it is the *band* priority
|
||||
(`LevelPriority`), falling back to the alarm priority when no per-band priority is configured —
|
||||
which is why `HighTemp` reads 500 at HiHi and `MinSeverity=600` cleanly isolates `SensorFault`.
|
||||
- **The `debug snapshot` / stream DTO reports `isMesReportable: false` for every alarm, including
|
||||
`HighTemp`.** That is §2.2 working as specified — the flag is deliberately not on the gRPC protos
|
||||
in this slice, so central never sees it and only the site-local script snapshot is stamped. Step H
|
||||
therefore cross-checks alarm *activity*, not the flag. (Register item: central badge/proto.)
|
||||
- **`ValueMatch` compares `value?.ToString()` as an ordinal string**, so `-1` and `100` matched the
|
||||
Double attribute's rendered form on this rig. A ValueMatch alarm over a Double is a string
|
||||
comparison, not a numeric one — `RangeViolation` is the numeric trigger.
|
||||
- `--match-value=-1` must use the `=` form; a space-separated `-1` is parsed as another option.
|
||||
|
||||
### 8.4 Deviations from the plan
|
||||
|
||||
1. Step A's `StatusCode` expectation changed from `"Triggered"` to `"Triggered.Acked"` (§8.3).
|
||||
2. Step H parses `alarmStates[].condition.active` (the snapshot has no textual state field; the
|
||||
`state` enum is `Active=0`, `Normal=1`).
|
||||
3. `SensorFault` is seeded with `--attribute Temperature` — a `ValueMatch` trigger needs the watched
|
||||
attribute, which the plan's sketch omitted.
|
||||
4. `site deploy-artifacts` is not called: the demo has no shared scripts or system-wide artifacts,
|
||||
and `instance deploy` after the override is what puts the alarms and the script on site-a.
|
||||
|
||||
Reference in New Issue
Block a user