Files
ScadaBridge/docs/plans/2026-06-30-mes-alarm-status-api.md
T
Joseph Doherty 1429ddaa0e docs(plans): add MES alarm-status API draft plan (2026-06-30)
Ports the legacy WWSupport/APIServer MES alarm-status endpoints onto the
Inbound API, mirroring the MesMoveIn/MesMoveOut pattern; draft for review,
not yet executed. Committed ahead of PLAN-R2-08 T13's hygiene pass.
2026-07-13 01:45:50 -04:00

25 KiB
Raw Blame History

MES Alarm-Status API — Implementation Plan

Date: 2026-06-30 Status: Draft plan — NOT yet executed. Captures design + task breakdown for review. Component touchpoints: Inbound API (#14), Script Analysis (#25), Site Runtime (#3), Template Engine (#1) — plus deployed config (inbound methods + CvdReactor/MESReceiver 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 MES-receiver site scripts (SAPID → BTDB machine lookup → Route.To(code).Call(...)), exactly like MesMoveIn.
  4. The generic version on the MESReceiver template resolves the machine's alarms by querying the BTDB MachineAlarm table (the legacy approach).
  5. The CvdReactor template provides an override version that reads the alarms actually defined on the CvdReactor object (its 7 native alarm-source bindings) directly — no MachineAlarm lookup.

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   ┌────► MESReceiver.SimpleAlarmStatus  (BTDB MachineAlarm)
                           ├─ Route.To(code).Call(...) ──┤        MESReceiver.AlarmStatus        (BTDB MachineAlarm)
AlarmStatus (new)         ─┘      (ScriptRuntimeContext +  └────► CvdReactor.SimpleAlarmStatus    (native sources — OVERRIDE)
                                   ScriptCompileSurface)          CvdReactor.AlarmStatus          (native sources — OVERRIDE)

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.

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,
    string CurrentValue, string LimitValue, bool IsConfiguredPlaceholder);

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 (generic / "as before" — queries BTDB MachineAlarm): SimpleAlarmStatus and AlarmStatus.

  • Use await Database.Connection("BTDB") (raw ADO.NET — site Database helper has no QuerySingleAsync; that's inbound-only) to read the machine's MachineAlarm rows (Name, Severity, FlaggedForMES, …) for the resolved MachineCode.
  • Apply legacy filter semantics (Simple = flagged-only; full = FlaggedOnly/MinSeverity/MaxSeverity/NameFilter).
  • Determine live "triggered" state + ack/timestamps and project to AlarmInfo (live-state source = open question §6.2).
  • No _A/_B side parsing. This generic version is whole-machine, matching the legacy endpoint — it accepts a bare/numeric SAPID and never requires (or errors on) a missing side suffix. Side routing is CvdReactor-only (§5.3).

CvdReactor (override — reads native sources directly, no DB): SimpleAlarmStatus and AlarmStatus as root-level scripts (so Route.To(code).Call("SimpleAlarmStatus") resolves CvdReactor's version for CvdReactor instances — 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)
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: flagged-only + acked always included (see §6.1 for FlaggedForMES)
        .Select(a => new {
            Name = a.Name,
            HierarchicalName = code + "." + a.Name,
            Description = a.Message,                           // §6.3
            IsFlaggedForMES = true,                            // §6.1
            Severity = a.Severity,
            StatusCode = a.Acknowledged ? "Triggered.Acked" : "Triggered",
            TriggeredDT = (a.OriginalRaiseTime ?? a.Timestamp),
            AckDT = (DateTime?)null,                           // §6.4
            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 — but additionally applies the passed AlarmFilter (NameFilter/MinSeverity/MaxSeverity/FlaggedOnly/IncludeAcked) over the scoped native list. (When AlarmStatus was selected by Code/ZTag/MachineID with no SAPID suffix, side == null; in that case return all sources instead of erroring — see §6.6.)

Shared ReactorAlarms (Z28061.) is included on both sides — reactor-wide faults apply regardless of side. Flip this if MES wants strictly side-local alarms (drop the StartsWith("Reactor") clause).


6. Key design decisions & OPEN QUESTIONS (resolve before execution)

These are the Socratic checkpoints — they change script bodies and/or the contract.

  1. IsFlaggedForMES for native alarms. The native model has no MES flag (it lived in MachineAlarm.FlaggedForMES). Options: (a) treat all native alarms as MES-relevant (true); (b) reintroduce an MES allow-list (per native source binding, or a small config/table) the override consults; (c) for SimpleAlarmStatus (flagged-only) return all, and only honor FlaggedOnly in the full endpoint by cross-referencing MachineAlarm. Recommend (a) for v1 with (b) as a follow-up, unless MES needs a true flag.

  2. MESReceiver live-state source. MESReceiver has no native alarm sources, so after reading MachineAlarm config it has no live "InAlarm" source. Options: (a) MESReceiver version returns configured alarms only (no live triggered filter) — explicitly a degraded/reference path; (b) MESReceiver version ALSO reads Alarms.CurrentAsync() and cross-references MachineAlarm by name (works only if the instance happens to mirror native alarms); (c) declare the BTDB-only path returns config and document that live status requires the CvdReactor-style override. Recommend (c) — the real live path is CvdReactor; the MESReceiver version demonstrates the MachineAlarm-driven catalog and is the fallback for machine types catalogued in SQL.

  3. Description mapping. Legacy Description came from the MXAccess DescAttrName tag. AlarmStateChanged has no dedicated description; closest is Message (per-band operator text) / AlarmTypeName / Category. Recommend Message, fall back to AlarmTypeName. Confirm acceptable.

  4. AckDT mapping. No explicit ack-timestamp in AlarmStateChanged (Timestamp = last change, OriginalRaiseTime = raise). For acked alarms we can't precisely fill AckDT. Options: (a) leave null; (b) use Timestamp when Acknowledged (approximate); (c) enrich the native mirror to carry an ack timestamp (larger change). Recommend (a) for v1, (c) as a follow-up if MES depends on it.

  5. Script naming / override resolution. Keep script names identical across both templates (SimpleAlarmStatus, AlarmStatus) so the inbound method is template-agnostic (Call("AlarmStatus")), and CvdReactor's root-level definition is what Route.Call resolves for CvdReactor instances. (Alternative: Mes-prefixed CvdReactor scripts like MesMoveIn.) Recommend identical names; confirm Route.Call resolves a CvdReactor root script over any composed-module script of the same name (verify during impl — the MoveIn precedent says yes).

  6. Side scoping — DECIDED, CvdReactor-only. Only the CvdReactor override routes off the SAPID suffix: _A ⇒ Left, _B ⇒ Right; _LT is ignored (stripped before reading the side; leak-test sources still included). Mirrors MesMoveIn. The generic MESReceiver version and the inbound method do not parse or require the suffix — they stay whole-machine (legacy). Two residual sub-choices remain: (a) shared ReactorAlarms (Z28061.) included on both sides (current plan) vs strictly side-local — confirm; (b) missing _A/_B suffix — the CvdReactor SimpleAlarmStatus errors (matches MesMoveIn); the CvdReactor AlarmStatus selected by Code/ZTag/MachineID (no SAPID) returns all sources rather than erroring; the MESReceiver version never errors on a missing suffix — confirm acceptable.

  7. New method roles / auth. AlarmStatus is a new inbound ApiMethod (X-API-Key); creating it requires Roles.Designer. No new global roles. Confirm the API key in use is authorized.


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 [repo]

  1. Add AlarmsAccessor + ScriptAlarm to the runtime context (ScriptRuntimeContext) — local Ask to the Instance Actor; project AlarmStateChangedScriptAlarm. Add internal request/response message if not reusing DebugSnapshotRequest.
  2. Mirror the stub on ScriptCompileSurface (and confirm TriggerCompileSurface not needed — trigger expressions don't read alarms).
  3. Confirm/extend ScriptTrustPolicy allow-list so Alarms is permitted; no new forbidden APIs.
  4. Unit tests: runtime accessor projection (active/acked/severity/timestamps), compile-surface compiles a representative Alarms.CurrentAsync() script, trust-policy accepts it.
  5. Doc: update docs/requirements/Component-SiteRuntime.md (+ Script Analysis #25 surface list) to document the Alarms accessor; note it in Component-InboundAPI.md routing examples.

Phase 2 — Inbound methods [deployed] + doc [repo] 6. Update SimpleAlarmStatusRequest (id 9) body to the §5.1 router (validate via design page first to avoid the stale-handler trap — see memory inbound-noncompiling-update-keeps-old-handler). 7. Create AlarmStatus method (params/return/script per §5.1). 8. 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] 9. Add SimpleAlarmStatus + AlarmStatus to MESReceiver (BTDB MachineAlarm via Database.Connection("BTDB")), per §6.2 decision. 10. Add override SimpleAlarmStatus + AlarmStatus to CvdReactor (native sources via Alarms.CurrentAsync()), per §5.3. 11. template validate both templates (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 + filtered.


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 for native alarms (per-source MES allow-list) — §6.1 option (b).
  • Precise AckDT (enrich native mirror with an ack timestamp) — §6.4 option (c).
  • A live/aggregated central alarm store or stream (the M7 follow-up) — these endpoints stay pull-based, per-instance, like the Alarm Summary page.
  • MESReceiver live-state path if §6.2 (c) is chosen and MES later needs live status from non-native machines.

10. Summary of changes

Artifact Type Change
ScriptRuntimeContext [repo] New Alarms accessor + ScriptAlarm; 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 + Script Analysis + Inbound API docs [repo] Document Alarms accessor + endpoints
Unit tests (SiteRuntime / ScriptAnalysis / InboundAPI) [repo] New
SimpleAlarmStatusRequest (id 9) [deployed] Stub → real router
AlarmStatus (new) [deployed] New inbound method
MESReceiver.SimpleAlarmStatus / .AlarmStatus [deployed] New BTDB-driven scripts
CvdReactor.SimpleAlarmStatus / .AlarmStatus [deployed] New native-source override scripts