Files
ScadaBridge/docs/plans/2026-06-30-mes-alarm-status-api.md
T
Joseph Doherty 0123b68719 docs(plans): MES alarm-status API — all 7 open design questions decided
Design review 2026-08-01: (1) MES relevance = dedicated severity band 900-999
(script constants, real IsFlaggedForMES predicate); (2) MESReceiver version
DROPPED — CvdReactor is the only implementation, router returns a clean
not-supported error elsewhere; (3) Description = Message else AlarmTypeName;
(4) enrich the native mirror NOW with an additive AckTime (proto +
native_alarm_state + DCL stamping); (5) script names match the endpoints;
(6) shared ReactorAlarms on both sides, suffix behavior as planned;
(7) existing MES API key, no new roles. Plan is now ready to execute.
2026-08-01 10:29:44 -04:00

26 KiB
Raw Blame History

MES Alarm-Status API — Implementation Plan

Date: 2026-06-30 Status: Ready to execute — all §6 open questions DECIDED 2026-08-01 (design review with user). Not yet implemented. Component touchpoints: Inbound API (#14), Script Analysis (#25), Site Runtime (#3), Template Engine (#1) — plus deployed config (inbound methods + CvdReactor template scripts).


1. Goal

Port the legacy WWSupport / APIServer MES alarm-status endpoints onto ScadaBridge's Inbound API, mirroring how MesMoveIn / MesMoveOut were ported:

  1. Update the existing SimpleAlarmStatusRequest inbound method (currently a stub) to do real work.
  2. Create a new AlarmStatus inbound method (the full, filtered endpoint).
  3. Both inbound methods forward to site scripts (SAPID → BTDB machine lookup → Route.To(code).Call(...)), exactly like MesMoveIn.
  4. The generic version on the MESReceiver template queries the BTDB MachineAlarm table. DROPPED (Q2, 2026-08-01): no MESReceiver version ships — a config-only answer with no live state was judged not worth having. Machines whose template doesn't implement the scripts get a clean WasSuccessful=false "not supported on this machine" error from the inbound router.
  5. The CvdReactor template provides the (only) implementation, reading the alarms actually defined on the CvdReactor object (its 7 native alarm-source bindings) directly — no MachineAlarm lookup anywhere.

Legacy spec being mirrored: docs/former-api-specs/mes/Alarm-API.md.


2. Legacy contract recap (target parity)

Two endpoints, one response shape (AlarmStatusResponse):

Legacy route New inbound method Request
POST /mes/simplealarmstatus SimpleAlarmStatusRequest (exists, id 9) { "SAPID": "..." }
POST /mes/alarmstatus AlarmStatus (NEW) { MachineFilter, AlarmFilter }

AlarmStatusResponse = { WasSuccessful: bool, ErrorText: string, Alarms: AlarmInfo[] }; on failure WasSuccessful=false and Alarms is cleared (all-or-nothing).

AlarmInfo = Name, HierarchicalName ({Code}.{Name}), Description, IsFlaggedForMES, Severity (0999), StatusCode ("Triggered" / "Triggered.Acked"), TriggeredDT, AckDT?, AckComment.

Behaviors to preserve:

  • Only triggered alarms returned (InAlarm == true). No "all configured" mode. IncludeTriggered is a no-op (legacy declared-but-unused).
  • /simplealarmstatus: always flagged-only, ack filtering ignored (acked alarms always included, tagged Triggered.Acked).
  • /alarmstatus: MachineFilter precedence SAPID → Code → ZTag → MachineID; AlarmFilter order FlaggedOnly → MinSeverity → MaxSeverity → NameFilter (substring, case-insensitive; severity bounds inclusive); IncludeAcked=false excludes acked.

3. Current state in ScadaBridge (verified 2026-06-30 against wonder-app-vd03)

Inbound methods (api-method list):

  • SimpleAlarmStatusRequest (id 9) — stub: return new { WasSuccessful = true, Alarms = Array.Empty<Object>() };. Param: SAPID (string). Return definition already shaped to AlarmInfo (Name/HierarchicalName/Description/IsFlaggedForMES/Severity/StatusCode/TriggeredDT/AckDT/AckComment). no return-contract change needed.
  • No AlarmStatus method yet.
  • Reference pattern (MesMoveIn, id 2): SAPID → Database.QuerySingleAsync<string>("BTDB", "SELECT TOP 1 Code FROM dbo.Machine WHERE SAPID=@s") → Route.To(code).Call("MesMoveIn", {...}).

Templates (template list / template get):

  • CvdReactor (id 1, parent=none): root scripts IpsenMoveIn, ProcessRecipeDownload, MesMoveIn, MesMoveOut; 7 native alarm sources (all over MxGateway): ReactorAlarms (Z28061.), LeftSideAlarms (Left_002.), RightSideAlarms (Right_002.), LeftLeakTestAlarms (LeakTest_003.), RightLeakTestAlarms (LeakTest_004.), LeftTableAlarms (TableAlarms_003.), RightTableAlarms (TableAlarms_004.); composes templates 5,6,8,9,13,14,26. No computed TemplateAlarms.
  • MESReceiver (id 3, parent=none): root scripts MoveIn, MoveOut (scope-bound to the composed receiver, bare Attributes); Left/RightMESReceiver (5/6) derive from it and are composed into CvdReactor. No alarms, no native sources.

Established override pattern (MoveIn): CvdReactor.MesMoveIn (root) does side-dispatch off the SAPID suffix and writes directly into Children["LeftMESReceiver"|"RightMESReceiver"].Attributes via WriteBatchAndWaitAsync — it does not CallScript the base. MESReceiver.MoveIn is the generic scope-bound implementation. So "override" here = CvdReactor supplies its own root-level script that Route.Call resolves for CvdReactor instances; the MESReceiver version is the reusable/generic one. (CvdReactor does not inherit MESReceiver — it composes it — so this is parallel-definition, not inheritance-override.)


4. The blocking gap (drives most of the code work)

Site call-type scripts currently have NO public API to read alarm condition state.

From the site-script API map:

  • The runtime context is ScriptRuntimeContext (src/.../SiteRuntime/Scripts/ScriptRuntimeContext.cs); compile-time mirror is ScriptCompileSurface (src/.../ScriptAnalysis/ScriptCompileSurface.cs). Members today: Attributes, Children, Parent, Instance (GetAttribute/SetAttribute/CallScript), Database, ExternalSystem, Notify, Scripts, Tracking, Parameters, Alarm (on-trigger only), CancellationToken.
  • Computed alarm state is only exposed via the Alarm global passed into on-trigger scripts. Native alarm state (AlarmConditionState) lives in the Instance Actor's _latestAlarmEvents; no script-facing query method exists.

⇒ The CvdReactor override ("read the defined alarms directly") cannot be written as a script today. It requires a new enabling capability: a script-facing Alarms read accessor.

Good news — the data already exists and is local. DebugViewSnapshot (Commons/Messages/DebugView/DebugViewSnapshot.cs) carries IReadOnlyList<AlarmStateChanged> AlarmStates, and the M7 Alarm Summary page already fans this out per instance. AlarmStateChanged (Commons/Messages/Streaming/AlarmStateChanged.cs) carries everything we need: AlarmName, Condition (AlarmConditionState: Active/Acknowledged/Confirmed/Shelve/Suppressed/Severity), Priority, Level, Message, Kind, SourceReference, AlarmTypeName, Category, OperatorUser, OperatorComment, OriginalRaiseTime, Timestamp, NativeSourceCanonicalName, IsConfiguredPlaceholder. The script runs inside the Instance Actor's context, so an Alarms accessor reads this via a local Ask — same mechanism as GetAttribute, no cross-cluster hop.


5. Target architecture (three layers)

INBOUND (central)                 ENABLING CODE (repo)            SITE TEMPLATE SCRIPTS (deployed config)
─────────────────                 ────────────────────           ───────────────────────────────────────
SimpleAlarmStatusRequest  ─┐      Alarms script accessor
                           ├─ Route.To(code).Call(...) ─────────► CvdReactor.SimpleAlarmStatus    (native sources)
AlarmStatus (new)         ─┘      + AckTime mirror enrichment ──► CvdReactor.AlarmStatus          (native sources)
                                  (ScriptRuntimeContext +
                                   ScriptCompileSurface)          (no MESReceiver version — Q2 DECIDED: dropped)

5.1 Layer A — Inbound API methods (deployed config; central)

Thin routers, identical shape to MesMoveIn. They own machine resolution (the MachineFilter precedence) against BTDB, then Route.To(code).Call(<siteScript>, <filters>) and return the site result verbatim.

SimpleAlarmStatusRequest (update id 9): extract the numeric SAP id for the BTDB lookup, but forward the raw SAPID (with _A/_B[_LT] suffix) to the site so the CvdReactor override can resolve the side — exactly as MesMoveIn does.

try {
    var raw = ((Parameters.ContainsKey("SAPID") ? Parameters["SAPID"]?.ToString() : null) ?? "").Trim();
    var m = System.Text.RegularExpressions.Regex.Match(raw, @"^(\d+)");
    if (!m.Success)
        return new { WasSuccessful = false, ErrorText = $"SAPID does not contain a numeric SAP id: '{raw}'", Alarms = Array.Empty<object>() };
    var sap = m.Groups[1].Value;                                  // numeric SAP for the DB lookup
    var code = await Database.QuerySingleAsync<string>("BTDB",
        "SELECT TOP 1 Code FROM dbo.Machine WHERE SAPID=@s", new { s = sap });
    if (string.IsNullOrEmpty(code))
        return new { WasSuccessful = false, ErrorText = $"Failed to find machine with SAPID '{sap}'", Alarms = Array.Empty<object>() };
    // SimpleAlarmStatus semantics: flagged-only, acked always included. Pass RAW SAPID for side routing.
    return await Route.To(code).Call("SimpleAlarmStatus", new { SAPID = raw, MachineCode = code });
} catch (Exception ex) {
    return new { WasSuccessful = false, ErrorText = "SimpleAlarmStatus failed: " + ex.Message, Alarms = Array.Empty<object>() };
}

AlarmStatus (new method): nested MachineFilter + AlarmFilter params (Inbound API extended type system supports Object). Resolve machine via precedence (all selectors come from dbo.Machine), then forward the filter to the site script.

  • Param definition (object): MachineFilter { MachineID:int?, SAPID:string, ZTag:string, Code:string }, AlarmFilter { NameFilter:string, MinSeverity:int?, MaxSeverity:int?, IncludeTriggered:bool, IncludeAcked:bool, FlaggedOnly:bool }.
  • Return definition: same AlarmInfo[] shape as id 9.
  • Body: resolve code by SAPID → Code → ZTag → MachineID (each its own SELECT TOP 1 Code FROM dbo.Machine WHERE ...), selector-specific error text on unmatched, then Route.To(code).Call("AlarmStatus", new { MachineCode = code, SAPID = <raw MachineFilter.SAPID or "">, ...AlarmFilter fields... }). The raw MachineFilter.SAPID (suffix included) is forwarded so the CvdReactor override can scope by side; when the machine was selected by Code/ZTag/MachineID (no SAPID suffix), side scoping is skipped and all sources are returned (see §6.6).

Note: Machine resolution stays on the inbound side (central) because it is environment/SQL-shaped and identical for both endpoints; site scripts receive an already-resolved MachineCode plus the alarm filter.

Unsupported machines (Q2 DECIDED): since only CvdReactor implements the site scripts, Route.Call on any other machine fails script-not-found. Both routers catch that case specifically and return { WasSuccessful=false, ErrorText = $"Alarm status is not supported on machine '{code}'" } instead of the raw exception text (verify during impl what exception/message Route.Call yields for a missing script so the catch can distinguish it).

5.2 Layer B — Enabling code change: script-facing Alarms accessor (repo)

New read-only accessor on the site script surface so scripts can enumerate the instance's current alarms.

Proposed shape (final names TBD in review):

// On ScriptRuntimeContext (runtime) + ScriptCompileSurface (compile-time stub):
public AlarmsAccessor Alarms { get; }

public sealed class AlarmsAccessor {
    // Snapshot of the instance's CURRENT alarm conditions (computed + native),
    // read locally from the Instance Actor (same Ask path as attribute reads).
    Task<IReadOnlyList<ScriptAlarm>> CurrentAsync(CancellationToken ct = default);
}

public sealed record ScriptAlarm(
    string Name, string SourceReference, string NativeSourceCanonicalName,
    bool Active, bool Acknowledged, bool? Confirmed, bool Shelved, bool Suppressed,
    int Severity, string Kind,            // "Computed" | "NativeOpcUa" | "NativeMxAccess"
    string Message, string AlarmTypeName, string Category,
    string OperatorUser, string OperatorComment,
    DateTimeOffset? OriginalRaiseTime, DateTimeOffset Timestamp,
    DateTimeOffset? AckTime,              // Q4 DECIDED: real ack timestamp from the enriched mirror
    string CurrentValue, string LimitValue, bool IsConfiguredPlaceholder);

Ack-timestamp enrichment (Q4 DECIDED 2026-08-01 — enrich the mirror now, not a follow-up):

  • Add an additive AckTime (DateTimeOffset?) to AlarmStateChanged, the vendored AlarmStateUpdate proto (additive field number, never reuse — manual toggle-build-copy-untoggle regen), and the site native_alarm_state persistence so it survives failover.
  • Semantics: when the underlying source supplies a true ack time (OPC UA A&C ack transitions do), use it; when it doesn't (MxGateway events without one), stamp the observation time of the ack transition at the DCL — accurate to when the system saw the ack, never fabricated. Null while unacked; cleared on re-raise.

Implementation:

  • Runtime: Alarms.CurrentAsync() Asks the Instance Actor for its alarm snapshot (reuse the existing internal alarm-state map that feeds DebugViewSnapshot.AlarmStates; project each AlarmStateChangedScriptAlarm). New internal request/response message (or reuse DebugSnapshotRequest and project off AlarmStates).
  • Compile surface: matching stub returning Task.FromResult(empty) so design-time compile + Test Run pass.
  • ScriptTrustPolicy (#25): no new forbidden surface; Alarms is an allow-listed context member like Attributes. Confirm the validator's allow-list includes it.
  • Additive message-contract evolution only.

5.3 Layer C — Site template scripts (deployed config)

MESReceiver version — DROPPED (Q2 DECIDED 2026-08-01). With no native alarm sources, MESReceiver has no live "triggered" signal; a configured-catalog-only answer was judged misleading rather than useful. No BTDB MachineAlarm read ships anywhere in this feature. Machines without the CvdReactor-style scripts get the router's "not supported on machine" error (§5.1).

CvdReactor (the only implementation — reads native sources directly, no DB): SimpleAlarmStatus and AlarmStatus (Q5 DECIDED: names match the endpoints, template-agnostic contract) as root-level scripts (mirrors MesMoveIn).

The override routes left vs right off the SAPID suffix_A ⇒ Left, _B ⇒ Right — and scopes the returned alarms to that side's native sources plus the shared reactor-wide source. The _LT (leak-test) suffix is ignored for scoping: it is stripped before reading the side, and both the side's run and leak-test sources are included. Source → side map (the 7 CvdReactor native sources):

Side (_A/_B) Native sources included
Left (_A) LeftSideAlarms, LeftLeakTestAlarms, LeftTableAlarms, + shared ReactorAlarms
Right (_B) RightSideAlarms, RightLeakTestAlarms, RightTableAlarms, + shared ReactorAlarms
// CvdReactor.SimpleAlarmStatus (sketch)
// Q1 DECIDED: MES relevance = dedicated severity band. Galaxy alarm priorities for
// MES-relevant alarms are configured into 900-999; nothing else may use that band.
const int MesBandMin = 900;
const int MesBandMax = 999;
try {
    var raw  = (Parameters["SAPID"] as string) ?? "";
    var code = Parameters["MachineCode"]?.ToString() ?? "";

    // Side routing from the SAPID suffix: _A => Left, _B => Right. _LT (leak test) is ignored.
    var core = raw.EndsWith("_LT") ? raw.Substring(0, raw.Length - 3) : raw;
    var side = core.EndsWith("_A") ? "Left" : core.EndsWith("_B") ? "Right" : null;
    if (side == null)
        return new { WasSuccessful = false, ErrorText = "SAPID missing _A/_B side suffix", Alarms = Array.Empty<object>() };

    // Keep this side's sources (Left*/Right*) + the shared reactor-wide source; drop the other side's.
    System.Func<string,bool> InScope = src =>
        string.IsNullOrEmpty(src)                                                  // computed/unbound — keep
        || src.StartsWith(side, System.StringComparison.OrdinalIgnoreCase)         // Left* or Right*
        || src.StartsWith("Reactor", System.StringComparison.OrdinalIgnoreCase);   // shared ReactorAlarms

    var alarms = await Alarms.CurrentAsync();                 // native + computed, all 7 sources
    var infos = alarms
        .Where(a => a.Active && !a.IsConfiguredPlaceholder)   // only triggered
        .Where(a => InScope(a.NativeSourceCanonicalName))     // _A => Left*, _B => Right*, + ReactorAlarms
        // SimpleAlarmStatus semantics: flagged-only (= MES band, Q1) + acked always included
        .Where(a => a.Severity >= MesBandMin && a.Severity <= MesBandMax)
        .Select(a => new {
            Name = a.Name,
            HierarchicalName = code + "." + a.Name,
            Description = string.IsNullOrEmpty(a.Message) ? a.AlarmTypeName : a.Message,  // Q3 DECIDED
            IsFlaggedForMES = a.Severity >= MesBandMin && a.Severity <= MesBandMax,       // Q1 DECIDED: real predicate
            Severity = a.Severity,
            StatusCode = a.Acknowledged ? "Triggered.Acked" : "Triggered",
            TriggeredDT = (a.OriginalRaiseTime ?? a.Timestamp),
            AckDT = a.AckTime,                                                            // Q4 DECIDED: enriched mirror
            AckComment = a.OperatorComment,
        }).ToList();
    return new { WasSuccessful = true, ErrorText = (string)null, Alarms = infos };
} catch (Exception ex) {
    return new { WasSuccessful = false, ErrorText = "SimpleAlarmStatus failed: " + ex.Message, Alarms = Array.Empty<object>() };
}

CvdReactor.AlarmStatus is the same — same side parsing + InScope filter, WITHOUT the always-on band filter — but applies the passed AlarmFilter (NameFilter/MinSeverity/MaxSeverity/FlaggedOnly/IncludeAcked) over the scoped native list; FlaggedOnly=true means the MES-band predicate (Q1). IsFlaggedForMES is always reported per-row from the band predicate. (When AlarmStatus was selected by Code/ZTag/MachineID with no SAPID suffix, side == null; in that case return all sources instead of erroring — Q6b DECIDED.)

Shared ReactorAlarms (Z28061.) is included on both sides — Q6a DECIDED 2026-08-01: reactor-wide faults apply regardless of which side MES asks about.


6. Key design decisions — ALL DECIDED 2026-08-01 (design review with user)

  1. IsFlaggedForMES for native alarms — DECIDED: severity band. MES relevance is encoded in the alarm severity itself: MES-relevant alarms are configured (in the Galaxy alarm priority) into a dedicated band 900999 reserved exclusively for MES-relevant alarms, so it cannot collide with ordinary criticality tuning. Scripts carry MesBandMin = 900 / MesBandMax = 999 as named constants; IsFlaggedForMES = (Severity in band) is reported honestly per row; SimpleAlarmStatus (always flagged-only) and AlarmStatus with FlaggedOnly=true filter by the band predicate. Since Severity is returned verbatim, MES's own MinSeverity/MaxSeverity filters compose naturally with the band. No new tables, code flags, or allow-lists. Operational prerequisite: the Galaxy alarm priorities for MES-relevant CvdReactor alarms must be set into 900999 before the endpoints are meaningful.

  2. MESReceiver version — DECIDED: dropped entirely. With no native alarm sources there is no live state; a config-only catalog answer was judged not worth shipping. Only CvdReactor implements the scripts; other machines get the router's clean "not supported on machine" error (§5.1). No BTDB MachineAlarm dependency remains.

  3. Description mapping — DECIDED: Message, falling back to AlarmTypeName when Message is empty.

  4. AckDT mapping — DECIDED: enrich the native mirror now. Additive AckTime on AlarmStateChanged + vendored proto + native_alarm_state (survives failover). True source ack time where available (OPC UA A&C); DCL observation time of the ack transition where the source lacks one (MxGateway); null while unacked; cleared on re-raise. See §5.2.

  5. Script naming — DECIDED: SimpleAlarmStatus / AlarmStatus (match the endpoint names; template-agnostic contract any future machine template can implement). With the MESReceiver version dropped there is no parallel definition, so no override-resolution concern remains.

  6. Side scoping — DECIDED (all parts). CvdReactor-only: _A ⇒ Left, _B ⇒ Right; _LT stripped before reading the side (leak-test sources still included). (a) shared ReactorAlarms (Z28061.) included on both sides — reactor-wide faults apply regardless of side. (b) missing suffix: SimpleAlarmStatus errors on a SAPID without _A/_B (matches MesMoveIn); AlarmStatus selected by Code/ZTag/MachineID (no SAPID) returns all sources; a SAPID selector without a suffix errors.

  7. Roles / auth — DECIDED: no new roles; create AlarmStatus as Designer via CLI; authorize the existing MES API key (the one already calling MesMoveIn/MesMoveOut) for both alarm endpoints — one key per external system.


7. Implementation task breakdown (ordered)

Two artifact classes: [repo] = source/tests/docs committed to git; [deployed] = inbound methods / template scripts pushed to the cluster via CLI/UI (not in repo). The user said "don't execute yet" — this is the ordered plan only.

Phase 1 — Enabling Alarms script API + AckTime mirror enrichment [repo]

  1. AckTime enrichment (§6.4): additive AckTime on AlarmStateChanged, the vendored AlarmStateUpdate proto (manual regen), native_alarm_state persistence, and the DCL ack-transition stamping (source ack time where supplied, else observation time).
  2. Add AlarmsAccessor + ScriptAlarm to the runtime context (ScriptRuntimeContext) — local Ask to the Instance Actor; project AlarmStateChangedScriptAlarm (incl. AckTime). Add internal request/response message if not reusing DebugSnapshotRequest.
  3. Mirror the stub on ScriptCompileSurface (and confirm TriggerCompileSurface not needed — trigger expressions don't read alarms).
  4. Confirm/extend ScriptTrustPolicy allow-list so Alarms is permitted; no new forbidden APIs.
  5. Unit tests: runtime accessor projection (active/acked/severity/timestamps/AckTime), AckTime stamping + failover persistence, compile-surface compiles a representative Alarms.CurrentAsync() script, trust-policy accepts it.
  6. Doc: update Component-SiteRuntime.md + Component-DataConnectionLayer.md (native-mirror AckTime) + Script Analysis #25 surface list; note the accessor in Component-InboundAPI.md routing examples.

Phase 2 — Inbound methods [deployed] + doc [repo] 7. Update SimpleAlarmStatusRequest (id 9) body to the §5.1 router incl. the not-supported-machine catch (validate via design page first to avoid the stale-handler trap — see memory inbound-noncompiling-update-keeps-old-handler). 8. Create AlarmStatus method (params/return/script per §5.1); authorize the existing MES API key for both methods (§6.7). 9. Doc the two endpoints in docs/requirements/Component-InboundAPI.md (or a dedicated MES-integration note) cross-referencing the legacy spec.

Phase 3 — Site template scripts [deployed] (MESReceiver task removed — §6.2 decided dropped) 10. Add SimpleAlarmStatus + AlarmStatus to CvdReactor (native sources via Alarms.CurrentAsync(), MES band constants), per §5.3. 11. template validate (script compilation gate) before redeploy; redeploy affected instances.

Phase 4 — Verify 12. Build affected projects + run targeted tests (per memory targeted-tests-not-full-suite). 13. Live smoke against a real reactor instance (see §8) — both endpoints, success + machine-not-found + not-supported-machine + filtered. Requires the Galaxy MES-band priorities (§6.1) to be set for a meaningful flagged-only result.


8. Testing & verification

  • Unit [repo]: Alarms accessor projection; compile-surface acceptance; trust-policy allow; inbound param/return validation for AlarmStatus (nested objects).
  • Design-page [deployed]: paste both site scripts into the api-method/template design pages; confirm no CS errors and Test Run executes (this is the gate that catches Database.QuerySingle-style mistakes pre-deploy).
  • Live smoke [deployed]: curl simplealarmstatus and alarmstatus against the deployed reactor; cross-check returned alarms against the CLI debug snapshot (debug snapshot --id <instance>) alarm table for the same instance; verify only Active alarms appear, ack mapping, severity filtering, and machine-not-found error text.
  • Caveat: native MxGateway alarms need a real gateway; the docker cluster can't fully exercise the CvdReactor native path (cf. memory on no-local-gateway). Validate the native-read path on the wonder-app-vd03 / real-gateway environment; unit-test the projection in isolation.

9. Out of scope / follow-ups

  • True IsFlaggedForMES allow-list — resolved by the §6.1 severity-band decision (no follow-up needed).
  • Precise AckDT follow-up — pulled INTO scope by §6.4 (mirror enriched now).
  • A live/aggregated central alarm store or stream (the M7 follow-up) — these endpoints stay pull-based, per-instance, like the Alarm Summary page.
  • Alarm-status support for non-CvdReactor machine types (any future template just implements SimpleAlarmStatus/AlarmStatus root scripts against the same contract).

10. Summary of changes

Artifact Type Change
AlarmStateChanged + vendored AlarmStateUpdate proto + native_alarm_state + DCL stamping [repo] Additive AckTime enrichment (§6.4)
ScriptRuntimeContext [repo] New Alarms accessor + ScriptAlarm (incl. AckTime); local alarm-snapshot Ask
ScriptCompileSurface [repo] Mirror Alarms stub
ScriptTrustPolicy (#25) [repo] Allow Alarms member (verify)
Internal alarm-snapshot message (or reuse DebugSnapshotRequest) [repo] Additive
Site Runtime + DCL + Script Analysis + Inbound API docs [repo] Document Alarms accessor, AckTime, endpoints
Unit tests (SiteRuntime / ScriptAnalysis / InboundAPI) [repo] New
SimpleAlarmStatusRequest (id 9) [deployed] Stub → real router (+ not-supported catch)
AlarmStatus (new) [deployed] New inbound method, existing MES key authorized
CvdReactor.SimpleAlarmStatus / .AlarmStatus [deployed] New native-source scripts (MES band 900999)
Galaxy alarm priorities [external] MES-relevant CvdReactor alarms configured into 900999 (§6.1 prerequisite)