feat(m9/T28b): trigger analysis-kind selector (UI) + --trigger-kind (CLI)

Surfaces the T28a backend "analysisKind" discriminator in both authoring
surfaces: an Advisory|Strict <select> (id="alarm-trigger-kind" /
"script-trigger-kind") added to the Expression fragment of
AlarmTriggerEditor and ScriptTriggerEditor, and a --trigger-kind option
on template alarm/script add+update in the CLI.

Key/value contract: "analysisKind":"Strict" when strict; key omitted for
Advisory — exactly as ValidationService.IsStrictAnalysis reads it.
Selector only shown for Expression triggers; non-Expression triggers do
not emit the key even if IsStrictAnalysisKind is set on the model.

Both projects build 0 warnings; 101 CentralUI Trigger tests + 33 CLI
Template tests pass.
This commit is contained in:
Joseph Doherty
2026-06-18 10:44:57 -04:00
parent f618ac0322
commit dcc6f623e2
8 changed files with 684 additions and 2 deletions
@@ -100,6 +100,15 @@ internal static class AlarmTriggerConfigCodec
case AlarmTriggerType.Expression:
model.Expression = TryReadString(root, "expression");
// M9-T28b: read optional analysisKind discriminator ("Strict" → strict;
// absent/"Advisory"/anything else → Advisory default, preserving
// today's behavior exactly — matches ValidationService.IsStrictAnalysis).
if (root.TryGetProperty("analysisKind", out var ak)
&& ak.ValueKind == JsonValueKind.String)
{
model.IsStrictAnalysisKind = string.Equals(
ak.GetString(), "Strict", StringComparison.OrdinalIgnoreCase);
}
break;
}
}
@@ -172,6 +181,11 @@ internal static class AlarmTriggerConfigCodec
case AlarmTriggerType.Expression:
w.WriteString("expression", model.Expression ?? "");
// M9-T28b: emit "analysisKind":"Strict" only when explicitly set;
// Advisory is the default so the key is omitted to keep the payload
// minimal and backward-compatible with older ValidationService versions.
if (model.IsStrictAnalysisKind)
w.WriteString("analysisKind", "Strict");
break;
}
@@ -342,4 +356,14 @@ internal sealed class AlarmTriggerModel
/// The boolean C# expression to evaluate for Expression triggers.
/// </summary>
public string? Expression { get; set; }
// M9-T28b: per-trigger analysis kind (Expression only). When true the
// codec serializes "analysisKind":"Strict"; when false (Advisory, the
// default) the key is omitted. Matches ValidationService.IsStrictAnalysis.
/// <summary>
/// When <see langword="true"/>, the trigger config carries
/// <c>"analysisKind":"Strict"</c> so ValidationService escalates the
/// blank-expression advisory to a deploy-blocking error.
/// </summary>
public bool IsStrictAnalysisKind { get; set; }
}