# MES-Reportable Alarm Flag Implementation Plan > **For Claude:** REQUIRED SUB-SKILL: Use superpowers-extended-cc:executing-plans to implement this plan task-by-task. **Goal:** Replace the legacy BTDB `MachineAlarm.FlaggedForMES` lookup with an `IsMesReportable` checkbox on template alarms (instance-overridable), expose it to site scripts through `Alarms.CurrentAsync()` / `Alarms.MesReportableAsync()`, and prove it end-to-end on the docker rig through an `AlarmStatus` inbound API method. **Architecture:** One additive `bool` on `TemplateAlarm` + one `bool?` on `InstanceAlarmOverride` (null = inherit), resolved by the flattener into `ResolvedAlarm.IsMesReportable`, which already ships to the site inside `deployed_configurations.config_json`. The site `InstanceActor` stamps the flag on its alarm snapshot in ONE place (`BuildAlarmStatesSnapshot`), so `ScriptAlarm.IsMesReportable` is correct for fired and unfired alarms. No proto/wire change; native alarms (source bindings, no per-alarm row) are out of scope and stay on the 900–999 severity band. The inbound method and site script are deployed config seeded by `docker/seed-mes-alarm-demo.sh`. **Tech Stack:** .NET 10, EF Core (SQL Server migrations), Akka.NET, Roslyn scripting, Blazor Server + Bootstrap, System.CommandLine CLI, bash + `scadabridge` CLI on the docker rig. **Design:** `docs/plans/2026-09-11-mes-reportable-alarm-flag-design.md`. Mockup: artifact "MES Reportable Alarms". **Conventions for every task** (from `CLAUDE.md` + memory): - Work in a dedicated worktree (Task 0). `EnterWorktree` is broken here — use `git worktree add`. - Run **targeted** tests per task (`dotnet test --filter …`), the affected-project build, and the full solution build only in Task 15. - Never `git stash` (shared across worktrees). Commit with pathspecs. End every commit message with `Claude-Session: https://claude.ai/code/session_01Y5ef34iUUMjmwnMpL9xgmB`. - Every public member needs an XML doc comment (`checkdocs` enforces it). - Message contracts and Transport DTOs evolve **additively with trailing defaulted parameters** — never reorder. --- ### Task 0: Worktree **Classification:** trivial **Estimated implement time:** ~2 min **Parallelizable with:** none **Files:** none (git only) **Step 1:** Create the worktree and confirm a clean build baseline. ```bash cd /Users/dohertj2/Desktop/ScadaBridge git worktree add -b feat/mes-reportable-alarm-flag ../ScadaBridge-mes-reportable main cd ../ScadaBridge-mes-reportable dotnet build ZB.MOM.WW.ScadaBridge.slnx 2>&1 | tail -3 ``` Expected: `0 Error(s)`. All later tasks run from `/Users/dohertj2/Desktop/ScadaBridge-mes-reportable` (absolute paths in every command — Bash cwd drifts). --- ### Task 1: Commons — entity, override and resolved-alarm fields **Classification:** small **Estimated implement time:** ~3 min **Parallelizable with:** none (everything depends on it) **Files:** - Modify: `src/ZB.MOM.WW.ScadaBridge.Commons/Entities/Templates/TemplateAlarm.cs:18` (after `IsLocked`) - Modify: `src/ZB.MOM.WW.ScadaBridge.Commons/Entities/Instances/InstanceAlarmOverride.cs:44` (after `PriorityLevelOverride`) - Modify: `src/ZB.MOM.WW.ScadaBridge.Commons/Types/Flattening/FlattenedConfiguration.cs:125` (after `IsLocked` in `ResolvedAlarm`) - Test: `tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/Entities/EntityConventionTests.cs` (existing sweep, must stay green) **Step 1: Add the three properties** `TemplateAlarm.cs`: ```csharp /// /// When true, the alarm is reported to MES by the alarm-status API /// (IsFlaggedForMES on the legacy contract). Default false. Overridable per /// instance via /// unless the alarm is locked. /// public bool IsMesReportable { get; set; } ``` `InstanceAlarmOverride.cs`: ```csharp /// /// Replaces the alarm's IsMesReportable flag when set. null = keep inherited. /// public bool? IsMesReportableOverride { get; set; } ``` `FlattenedConfiguration.cs` (`ResolvedAlarm`): ```csharp /// Gets whether the alarm is reported to MES (template value after any instance override). public bool IsMesReportable { get; init; } ``` **Step 2: Build + run the convention sweeps** ```bash dotnet test tests/ZB.MOM.WW.ScadaBridge.Commons.Tests --filter "FullyQualifiedName~EntityConventionTests|FullyQualifiedName~MessageConventionTests" 2>&1 | tail -3 ``` Expected: all pass. **Step 3: Commit** ```bash git add src/ZB.MOM.WW.ScadaBridge.Commons git commit -m "feat(commons): IsMesReportable on TemplateAlarm, ResolvedAlarm; IsMesReportableOverride on InstanceAlarmOverride" ``` --- ### Task 2: Configuration Database — migration **Classification:** high-risk (migration) **Estimated implement time:** ~4 min **Parallelizable with:** Task 3, Task 6, Task 8 (after Task 1) **Files:** - Create: `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Migrations/_AddAlarmMesReportable.cs` (+ `.Designer.cs`) - Modify: `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Migrations/ScadaBridgeDbContextModelSnapshot.cs` (generated) - Test: `tests/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Tests/SchemaConfigurationTests.cs:144-157` **Step 1: Write the failing test** — extend the existing TemplateAlarm persist test (or add beside it): ```csharp [Fact] public async Task TemplateAlarm_And_Override_PersistMesReportableFlags() { // arrange as the sibling test does (in-memory/SQLite context + a template + an instance) var alarm = new TemplateAlarm("HighTemp") { TemplateId = template.Id, TriggerType = AlarmTriggerType.HiLo, PriorityLevel = 500, IsMesReportable = true }; var ovr = new InstanceAlarmOverride("HighTemp") { InstanceId = instance.Id, IsMesReportableOverride = false }; ctx.TemplateAlarms.Add(alarm); ctx.InstanceAlarmOverrides.Add(ovr); await ctx.SaveChangesAsync(); ctx.ChangeTracker.Clear(); Assert.True((await ctx.TemplateAlarms.SingleAsync(a => a.Id == alarm.Id)).IsMesReportable); Assert.False((await ctx.InstanceAlarmOverrides.SingleAsync(o => o.Id == ovr.Id)).IsMesReportableOverride); } ``` **Step 2: Scaffold the migration — BUILD FIRST, no `--no-build`** (memory: `--no-build` scaffolds an empty migration): ```bash dotnet build src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase 2>&1 | tail -1 dotnet ef migrations add AddAlarmMesReportable \ --project src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase \ --startup-project src/ZB.MOM.WW.ScadaBridge.Host ``` Verify the `Up()` matches the `20260517000628_AddDerivedAlarmFields.cs` shape: ```csharp migrationBuilder.AddColumn(name: "IsMesReportable", table: "TemplateAlarms", type: "bit", nullable: false, defaultValue: false); migrationBuilder.AddColumn(name: "IsMesReportableOverride", table: "InstanceAlarmOverrides", type: "bit", nullable: true); ``` If the file is empty, `rm` the two generated files and re-run the build + add. **Step 3: Run the tests** ```bash dotnet test tests/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Tests --filter "FullyQualifiedName~SchemaConfigurationTests" 2>&1 | tail -3 ``` Expected: PASS. No explicit EF mapping change is needed (`bool`/`bool?` map by convention). **Step 4: Commit** ```bash git add src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase tests/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Tests git commit -m "feat(config-db): AddAlarmMesReportable migration (TemplateAlarms.IsMesReportable, InstanceAlarmOverrides.IsMesReportableOverride)" ``` --- ### Task 3: Template Engine — flattening **Classification:** standard **Estimated implement time:** ~4 min **Parallelizable with:** Task 2, Task 6, Task 8 **Files:** - Modify: `src/ZB.MOM.WW.ScadaBridge.TemplateEngine/Flattening/FlatteningService.cs:450-461` (projection) and `:358-363` (override merge) - Test: `tests/ZB.MOM.WW.ScadaBridge.TemplateEngine.Tests/Flattening/FlatteningServiceTests.cs` (model on `Flatten_Alarms_ResolvedFromChain` at :221) **Step 1: Write three failing tests** (copy the arrange block of `Flatten_Alarms_ResolvedFromChain`): - `Flatten_MesReportable_TemplateValueCarried` — template alarm `IsMesReportable = true` → resolved `IsMesReportable == true`; a second alarm left default → `false`. - `Flatten_MesReportable_OverrideFalseWins` — template `true`, override `IsMesReportableOverride = false` → resolved `false`, `Source == "Override"`. - `Flatten_MesReportable_OverrideNullInherits` — template `true`, override with only `PriorityLevelOverride = 7` → resolved `IsMesReportable == true`. **Step 2: Run** `dotnet test tests/ZB.MOM.WW.ScadaBridge.TemplateEngine.Tests --filter "FullyQualifiedName~Flatten_MesReportable"` → 3 FAIL. **Step 3: Implement** In the projection (`:450`): ```csharp IsLocked = alarm.IsLocked, IsMesReportable = alarm.IsMesReportable, ``` In `ApplyInstanceAlarmOverrides` (`:358`): ```csharp PriorityLevel = ovr.PriorityLevelOverride ?? existing.PriorityLevel, IsMesReportable = ovr.IsMesReportableOverride ?? existing.IsMesReportable, ``` **Step 4: Run** the same filter → 3 PASS; then the whole `Flattening` folder filter to confirm no regression. **Step 5: Commit** ```bash git add src/ZB.MOM.WW.ScadaBridge.TemplateEngine/Flattening tests/ZB.MOM.WW.ScadaBridge.TemplateEngine.Tests/Flattening git commit -m "feat(template-engine): flatten IsMesReportable with null-inherits override semantics" ``` --- ### Task 4: Template Engine — service write paths **Classification:** standard **Estimated implement time:** ~4 min **Parallelizable with:** none (Task 5, 10, 11 depend on the new signature) **Files:** - Modify: `src/ZB.MOM.WW.ScadaBridge.TemplateEngine/TemplateService.cs:~619-627` (`UpdateAlarmAsync` field copy block: `existing.PriorityLevel = proposed.PriorityLevel; …`) - Modify: `src/ZB.MOM.WW.ScadaBridge.TemplateEngine/Services/InstanceService.cs:218-280` (`SetAlarmOverrideAsync`) - Test: `tests/ZB.MOM.WW.ScadaBridge.TemplateEngine.Tests/` (the existing `TemplateService`/`InstanceService` alarm tests — find with `grep -rn "UpdateAlarmAsync\|SetAlarmOverrideAsync" tests/ZB.MOM.WW.ScadaBridge.TemplateEngine.Tests`) **Step 1: Failing tests** - `UpdateAlarmAsync_CopiesIsMesReportable`: existing alarm `false`, proposed `true` → stored `true`. - `SetAlarmOverrideAsync_StoresIsMesReportableOverride`: call with `isMesReportableOverride: true` → repo receives an override with `IsMesReportableOverride == true`; a second call with `null` clears it. **Step 2: Implement** `TemplateService.UpdateAlarmAsync` — beside `existing.IsLocked = proposed.IsLocked;`: ```csharp existing.IsMesReportable = proposed.IsMesReportable; ``` `InstanceService.SetAlarmOverrideAsync` — new trailing optional parameter **after `user`** (all current callers pass five positional args; grep `SetAlarmOverrideAsync(` across `src/` and `tests/` to confirm): ```csharp string user, bool? isMesReportableOverride = null, CancellationToken cancellationToken = default) ``` and in both branches: ```csharp existingOverride.IsMesReportableOverride = isMesReportableOverride; … PriorityLevelOverride = priorityLevelOverride, IsMesReportableOverride = isMesReportableOverride ``` Update the XML `` docs. `AddAlarmAsync` needs nothing — it persists the entity as given. **Step 3: Run** the TemplateEngine test project filtered to `Alarm` → PASS. **Step 4: Commit** `feat(template-engine): persist IsMesReportable on alarm update; IsMesReportableOverride on SetAlarmOverrideAsync`. --- ### Task 5: Management commands + ManagementActor **Classification:** standard **Estimated implement time:** ~4 min **Parallelizable with:** none **Files:** - Modify: `src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Management/TemplateCommands.cs:45-46` - Modify: `src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Management/InstanceCommands.cs:39-43` - Modify: `src/ZB.MOM.WW.ScadaBridge.ManagementService/ManagementActor.cs:3173-3201`, `:1171-1182` - Test: `tests/ZB.MOM.WW.ScadaBridge.ManagementService.Tests/ManagementActorTests.cs` **Step 1: Failing tests** (model on the existing add-alarm / set-override tests in `ManagementActorTests`): - `AddTemplateAlarm_CarriesIsMesReportable` — send `new AddTemplateAlarmCommand(1, "HighTemp", "HiLo", 500, null, null, false, IsMesReportable: true)`; assert the `TemplateAlarm` handed to `TemplateService.AddAlarmAsync` has `IsMesReportable == true`. - `SetInstanceAlarmOverride_PassesIsMesReportableOverride` — `new SetInstanceAlarmOverrideCommand(10, "HighTemp", null, null, IsMesReportableOverride: false)` → `InstanceService.SetAlarmOverrideAsync` receives `false`. **Step 2: Implement** — trailing optionals, never reorder: ```csharp public record AddTemplateAlarmCommand(int TemplateId, string Name, string TriggerType, int PriorityLevel, string? Description, string? TriggerConfiguration, bool IsLocked, bool IsMesReportable = false); public record UpdateTemplateAlarmCommand(int AlarmId, string Name, string TriggerType, int PriorityLevel, string? Description, string? TriggerConfiguration, bool IsLocked, bool IsMesReportable = false); ``` ```csharp public record SetInstanceAlarmOverrideCommand( int InstanceId, string AlarmCanonicalName, string? TriggerConfigurationOverride, int? PriorityLevelOverride, bool? IsMesReportableOverride = null); ``` `HandleAddAlarm` / `HandleUpdateAlarm`: add `IsMesReportable = cmd.IsMesReportable` to both initializers. `HandleSetInstanceAlarmOverride`: pass `cmd.IsMesReportableOverride` after `user.Username`. Note the PLAN-08 T9 finding: Newtonsoft ignores C# ctor optional-parameter defaults on the wire → an old CLI that omits the field yields `default(T)` = `false` / `null`, which is exactly the intended inherit/off default. **Step 3: Run** ```bash dotnet test tests/ZB.MOM.WW.ScadaBridge.ManagementService.Tests --filter "FullyQualifiedName~ManagementActorTests|FullyQualifiedName~RequiredRoleMatrixTests" 2>&1 | tail -3 ``` Expected: PASS. `RequiredRoleMatrixTests` is untouched — no new command names. **Step 4: Commit** `feat(management): IsMesReportable on alarm commands, IsMesReportableOverride on SetInstanceAlarmOverride`. --- ### Task 6: Site Runtime — stamp the flag on the alarm snapshot **Classification:** high-risk (actor) **Estimated implement time:** ~4 min **Parallelizable with:** Task 2, Task 3, Task 8 **Files:** - Modify: `src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Streaming/AlarmStateChanged.cs:109` (append after `IsConfiguredPlaceholder`) - Modify: `src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Actors/InstanceActor.cs:53` (field), `:1982` (spawn), `:1569-1613` (`BuildAlarmStatesSnapshot`) - Test: `tests/ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests/Actors/InstanceActorNativeAlarmTests.cs` (has the `GetAlarmSnapshotRequest` harness — add beside it) **Step 1: Failing test** `AlarmSnapshot_StampsIsMesReportable_OnFiredAndUnfiredAlarms`: deploy a `FlattenedConfiguration` with two computed alarms (`HighTemp` `IsMesReportable=true`, `TempSpike` `false`); drive `HighTemp` active (write the attribute past the setpoint, as the sibling tests do); Ask `GetAlarmSnapshotRequest`; assert the `HighTemp` row (retained event) has `IsMesReportable == true` and the `TempSpike` row (fallback) `false`. **Step 2: Implement** `AlarmStateChanged.cs`: ```csharp /// /// True when the alarm's deployed configuration flags it as MES-reportable /// (ResolvedAlarm.IsMesReportable). Stamped by the Instance Actor on its alarm /// snapshot; always false for native conditions, which have no per-alarm config row. /// NOT carried on the gRPC stream in this slice (no proto field) — central sees the /// default. Additive; default false. /// public bool IsMesReportable { get; init; } ``` `InstanceActor.cs`: ```csharp private readonly Dictionary _alarmMesReportable = new(); … _alarmPriorities[alarm.CanonicalName] = alarm.PriorityLevel; _alarmMesReportable[alarm.CanonicalName] = alarm.IsMesReportable; ``` `BuildAlarmStatesSnapshot()` — stamp every row in one place (records support `with`): ```csharp var states = _latestAlarmEvents.Values .Select(e => e with { IsMesReportable = _alarmMesReportable.GetValueOrDefault(e.AlarmName) }) .ToList(); foreach (var name in _alarmActors.Keys) { if (_latestAlarmEvents.ContainsKey(name)) continue; states.Add(new AlarmStateChanged(/* unchanged five args */) { IsMesReportable = _alarmMesReportable.GetValueOrDefault(name) }); } ``` Native placeholder rows are untouched (`false`). Also clear `_alarmMesReportable` wherever `_alarmPriorities` is cleared on undeploy/redeploy (grep `_alarmPriorities.Clear`). **Step 3: Run** ```bash dotnet test tests/ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests --filter "FullyQualifiedName~InstanceActorNativeAlarmTests|FullyQualifiedName~InstanceActorTests" 2>&1 | tail -3 dotnet test tests/ZB.MOM.WW.ScadaBridge.Commons.Tests --filter "FullyQualifiedName~MessageConventionTests|FullyQualifiedName~AlarmStateChanged" 2>&1 | tail -3 ``` Expected: PASS (the Commons additive/back-compat tests for `AlarmStateChanged` still deserialize a pre-flag JSON to `false`). **Step 4: Commit** `feat(site-runtime): stamp IsMesReportable on the Instance Actor alarm snapshot`. --- ### Task 7: Script surface — `ScriptAlarm.IsMesReportable` + `Alarms.MesReportableAsync()` **Classification:** standard **Estimated implement time:** ~5 min **Parallelizable with:** Task 8, Task 9 (after Task 6) **Files:** - Modify: `src/ZB.MOM.WW.ScadaBridge.Commons/Types/Scripts/ScriptAlarm.cs:58-79` (trailing positional `bool IsMesReportable = false` + `` doc) - Modify: `src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/ScriptRuntimeContext.cs:1263-1304` (`GetAlarmsAsync`, `ToScriptAlarm`) - Modify: `src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/ScopeAccessors.cs:372-401` (`AlarmsAccessor`) - Modify: `src/ZB.MOM.WW.ScadaBridge.ScriptAnalysis/ScriptCompileSurface.cs:150-157` (`CompileAlarmsAccessor`) - Modify: `src/ZB.MOM.WW.ScadaBridge.CentralUI/ScriptAnalysis/SandboxScriptHost.cs:151-164` (`SandboxAlarmsAccessor`) - Test: `tests/ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests/Scripts/AlarmsAccessorTests.cs`, `tests/ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests/Scripts/CompileSurfaceParityTests.cs`, the ScriptAnalysis compile-surface test that compiles an `Alarms.CurrentAsync()` script (grep `CurrentAsync` under `tests/ZB.MOM.WW.ScadaBridge.ScriptAnalysis.Tests`), and the CentralUI `SandboxScriptHost` diagnose-clean test (grep `Alarms.CurrentAsync` under `tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests`). **Step 1: Failing tests** - `AlarmsAccessorTests.CurrentAsync_ProjectsIsMesReportable` — respond with a row `{ IsMesReportable = true }` → `ScriptAlarm.IsMesReportable == true`. - `AlarmsAccessorTests.MesReportableAsync_FiltersToFlaggedRows` — respond with one flagged + one unflagged row → exactly the flagged one. - Compile-surface + sandbox tests: extend the existing script literal to `var r = await Alarms.MesReportableAsync(); var f = r.Where(a => a.IsMesReportable);` → compiles with zero diagnostics. - `CompileSurfaceParityTests` already pairs `AlarmsAccessor` ↔ `CompileAlarmsAccessor` (`:112`) — it fails automatically until the mirror gains the member. **Step 2: Implement** `ScriptAlarm`: append `bool IsMesReportable = false` as the last positional parameter (existing 21-arg call sites keep compiling). `ScriptRuntimeContext`: ```csharp internal async Task> GetMesReportableAlarmsAsync(CancellationToken cancellationToken = default) => (await GetAlarmsAsync(cancellationToken)).Where(a => a.IsMesReportable).ToList(); … IsConfiguredPlaceholder: a.IsConfiguredPlaceholder, IsMesReportable: a.IsMesReportable); ``` `AlarmsAccessor`: ```csharp /// /// restricted to alarms whose deployed configuration flags /// them MES-reportable (template checkbox, or an instance override). Native conditions /// are never included — they carry no per-alarm flag. Placeholder rows are excluded. /// public Task> MesReportableAsync(CancellationToken cancellationToken = default) => _ctx.GetMesReportableAlarmsAsync(cancellationToken); ``` `CompileAlarmsAccessor`: same signature → `throw new NotSupportedException(CompileOnly)`. `SandboxAlarmsAccessor`: same signature → `throw new ScriptSandboxException("Alarms.MesReportableAsync() reads the site Instance Actor's live alarm conditions, which aren't available in the central Test Run sandbox — deploy to a site to exercise alarm-reading scripts.")`. **Step 3: Run** the four test projects filtered to `Alarms|CompileSurfaceParity|SandboxScriptHost` → PASS. **Step 4: Commit** `feat(scripts): ScriptAlarm.IsMesReportable + Alarms.MesReportableAsync() mirrored on both compile surfaces`. --- ### Task 8: Transport bundles **Classification:** standard **Estimated implement time:** ~5 min **Parallelizable with:** Task 2, Task 3, Task 6, Task 7 **Files:** - Modify: `src/ZB.MOM.WW.ScadaBridge.Transport/Serialization/EntityDtos.cs:212-221` (`TemplateAlarmDto` — append `bool IsMesReportable = false`), `:395-398` (`InstanceAlarmOverrideDto` — append `bool? IsMesReportableOverride = null`) - Modify: `src/ZB.MOM.WW.ScadaBridge.Transport/Serialization/EntitySerializer.cs:66-74`, `:285-288`, `:346-359`, `:603-611` - Modify: `src/ZB.MOM.WW.ScadaBridge.Transport/Import/TemplateChildEquality.cs:57-65` - Modify: `src/ZB.MOM.WW.ScadaBridge.Transport/Import/ArtifactDiff.cs:488-496` - Modify: `src/ZB.MOM.WW.ScadaBridge.Transport/Import/BundleImporter.cs:2168-2178`, `:2392-2416`, `:4357-4364`, `:4904-4916` - Test: `tests/ZB.MOM.WW.ScadaBridge.Transport.IntegrationTests/RoundTripEquivalenceTests.cs:233-241`, `:808-812`; `tests/ZB.MOM.WW.ScadaBridge.Transport.Tests/Import/ArtifactDiffTests.cs`; `tests/ZB.MOM.WW.ScadaBridge.Transport.Tests/Serialization/EntitySerializerTests.cs` **Step 1: Failing tests** - Round-trip guard: set `IsMesReportable = true` on the seeded `High` alarm (`:233`) and `IsMesReportableOverride = false` on the seeded `HiAlarm` override (`:808`). The reflection sweep now fails until the DTO carries both. - `ArtifactDiffTests`: an alarm differing only in `IsMesReportable` classifies as Changed (not Identical); same for an override differing only in `IsMesReportableOverride`. - `EntitySerializerTests`: a bundle JSON lacking the two new keys deserializes to `false` / `null`. **Step 2: Run** `dotnet test tests/ZB.MOM.WW.ScadaBridge.Transport.IntegrationTests --filter RoundTripEquivalence` → FAIL naming the two properties. **Step 3: Implement** — carry the field at every listed site: - serializer export: `IsMesReportable: a.IsMesReportable` / `IsMesReportableOverride: o.IsMesReportableOverride` - serializer import + importer entity builds (`:346`, `:603`, `:2168`, `:4357`): `IsMesReportable = al.IsMesReportable` / `IsMesReportableOverride = o.IsMesReportableOverride` - `SyncTemplateAlarmsAsync` (`:2392`): `current.IsMesReportable = alarmDto.IsMesReportable;` and add `current.IsMesReportable` to the audit payload object - `TemplateChildEquality.AlarmsEqual`: `&& e.IsMesReportable == i.IsMesReportable` - `ArtifactDiff` override lambda: `&& e.IsMesReportableOverride == i.IsMesReportableOverride` - `BundleImporter:4904` (`ResolvedAlarm` for validation): `IsMesReportable = al.IsMesReportable` - `TemplateChildEquality` also has an override-equality helper if one exists — grep `PriorityLevelOverride` in `Transport/Import` and extend every comparison you find. **Step 4: Run** ```bash dotnet test tests/ZB.MOM.WW.ScadaBridge.Transport.Tests 2>&1 | tail -3 dotnet test tests/ZB.MOM.WW.ScadaBridge.Transport.IntegrationTests --filter "RoundTripEquivalence|BundleImporterApply|ArtifactDiff" 2>&1 | tail -3 ``` Expected: PASS. **Step 5: Commit** `feat(transport): carry IsMesReportable / IsMesReportableOverride through bundles (round-trip guard extended)`. --- ### Task 9: CLI flags + README **Classification:** small **Estimated implement time:** ~4 min **Parallelizable with:** Task 7, Task 8, Task 10, Task 11 **Files:** - Modify: `src/ZB.MOM.WW.ScadaBridge.CLI/Commands/TemplateCommands.cs:683-684` (add option), `:709-730` (register on add), `:747-754` (pass), `:758-770` (update option), `:790-797` (pass) - Modify: `src/ZB.MOM.WW.ScadaBridge.CLI/Commands/InstanceCommands.cs:551-568` - Modify: `src/ZB.MOM.WW.ScadaBridge.CLI/README.md:263-313` (alarm add/update option tables), `:808-821` (alarm-override set) - Test: `tests/ZB.MOM.WW.ScadaBridge.CLI.Tests/EditingParityTests.cs:31-36` **Step 1: Failing tests** — add `InlineData` rows (exact envelope, camelCase, all fields): ```csharp [InlineData("template alarm add --template-id 12 --name HighTemp --trigger-type HiLo --priority 500 --mes-reportable", "AddTemplateAlarm", "{\"templateId\":12,\"name\":\"HighTemp\",\"triggerType\":\"HiLo\",\"priorityLevel\":500,\"description\":null,\"triggerConfiguration\":null,\"isLocked\":false,\"isMesReportable\":true}")] [InlineData("instance alarm-override set --instance-id 6 --alarm HighTemp --mes-reportable false", "SetInstanceAlarmOverride", "{\"instanceId\":6,\"alarmCanonicalName\":\"HighTemp\",\"triggerConfigurationOverride\":null,\"priorityLevelOverride\":null,\"isMesReportableOverride\":false}")] ``` (If the existing serializer emits `triggerConfiguration` differently for a no-config add, copy the exact string the failing assertion prints.) **Step 2: Implement** Template add + update (mirror `--locked`): ```csharp var mesReportableOption = new Option("--mes-reportable") { Description = "Report this alarm to MES (IsFlaggedForMES on the alarm-status API)" }; mesReportableOption.DefaultValueFactory = _ => false; ``` register on the command, pass `result.GetValue(mesReportableOption)` as the new last ctor arg (a separate `updateMesReportableOption` for `update`, like `updateLockedOption`). Instance override — nullable means inherit (the `api-method update --trace-all-calls` `Option` idiom, `ApiMethodCommands.cs:98-102`): ```csharp var setMesReportableOption = new Option("--mes-reportable") { Description = "MES-reportable override: true|false; omit to inherit the template value" }; ``` README: add the `--mes-reportable` row to the three option tables and the synopsis lines; note "update replaces the whole entity — omit `--mes-reportable` and it resets to false" on `template alarm update` (that is the existing `--locked` behaviour too). **Step 3: Run** `dotnet test tests/ZB.MOM.WW.ScadaBridge.CLI.Tests --filter "EditingParity|UpdateCommandContract"` → PASS. **Step 4: Commit** `feat(cli): --mes-reportable on template alarm add/update and instance alarm-override set`. --- ### Task 10: Central UI — template alarm dialog + Alarms table **Classification:** standard **Estimated implement time:** ~5 min **Parallelizable with:** Task 9, Task 11 **Files:** - Modify: `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/TemplateAlarmDialog.razor:38-43` (markup), `:68`, `:84`, `:94`, `:106`, `:128-133` - Modify: `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Design/TemplateEdit.razor:906-934` (table), `:1436-1469` (open/show), `:1518-1540` (save) - Test: create `tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Design/TemplateAlarmDialogMesReportableTests.cs` (structural, copy the shape of `TemplateNativeAlarmSourceEditorTests.cs:16-67`) **Step 1: Failing test** — reads `TemplateAlarmDialog.razor` from disk and asserts it contains `@bind="_isMesReportable"`, `id="alarmMesReportable"`, and the label text `MES reportable`; reads `TemplateEdit.razor` and asserts the Alarms table has a `MES` header and a `data-test="alarm-mes-badge"` cell. **Step 2: Implement** Dialog markup, directly after the Locked block: ```razor
Returned by the MES alarm-status API as IsFlaggedForMES. Instances can override unless the alarm is locked.
``` Code: `[Parameter] public bool InitialIsMesReportable { get; set; }`, `private bool _isMesReportable;`, seed in `OnInitialized`, pass in `Submit`, and extend the record: ```csharp public sealed record AlarmDraft(string Name, AlarmTriggerType TriggerType, int Priority, string? TriggerConfig, bool IsLocked, bool IsMesReportable); ``` `TemplateEdit.razor`: default draft gets `false`; edit draft passes `alarm.IsMesReportable`; `ShowAlarmDialogAsync` passes `InitialIsMesReportable="initial.IsMesReportable"`; both `proposed`/`alarm` initializers in `SaveAlarmDraftAsync` set `IsMesReportable = draft.IsMesReportable`. Table: header `MES` between Config and Lock; cell ```razor @if (alarm.IsMesReportable) { MES } else { — } ``` **Step 3: Run** `dotnet test tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests --filter "TemplateAlarmDialogMesReportable|TemplateEdit"` → PASS. `dotnet build src/ZB.MOM.WW.ScadaBridge.CentralUI` → 0 errors. **Step 4: Commit** `feat(ui): MES reportable checkbox on the template alarm dialog + MES column on the Alarms tab`. --- ### Task 11: Central UI — instance override modal (tri-state) **Classification:** standard **Estimated implement time:** ~5 min **Parallelizable with:** Task 9, Task 10 **Files:** - Modify: `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Deployment/InstanceConfigure.razor:441-499` (table), `:531-541` (modal field), `:1532` (`OverrideSummary`), `:1564-1583` (`BeginEditOverride`), `:1628-1660` (delta check + save) - Test: create `tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Deployment/InstanceConfigureMesReportableTests.cs` (structural on the razor source, plus a unit test of the delta rule if you extract it into a static helper — recommended: `static bool HasOverrideDelta(string? json, int? priority, bool? mes)`) - Test: `tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Deployment/InstanceConfigureTests.cs` — add `AlarmOverride_MesReportable_RoundTrips` beside `AlarmOverride_SetPriority_ThenClear_RoundTrips` (`:195-249`); runs against the rig in Task 14. **Step 1: Failing tests** — structural: `data-test="alarm-mes-select"` exists with three `