feat(scripted-alarms): DeploymentArtifact byte-parity for the alarm plan (T6)
This commit is contained in:
@@ -20,6 +20,12 @@ public static class EquipmentScriptPaths
|
||||
private static readonly Regex GetTagRefRegex =
|
||||
new(@"ctx\s*\.\s*GetTag\s*\(\s*""([^""]+)""\s*\)", RegexOptions.Compiled);
|
||||
|
||||
// {TagPath} message-template token — a single-brace {…} run. The negative look-behind/-ahead
|
||||
// reject the reserved {{equip}} double-brace form (and any other doubled brace), so "{A.X}"
|
||||
// matches but "{{equip}}" does not yield "{equip}".
|
||||
private static readonly Regex MessageTemplateTokenRegex =
|
||||
new(@"(?<!\{)\{([^{}]+)\}(?!\})", RegexOptions.Compiled);
|
||||
|
||||
// ctx.GetTag("…") OR ctx.SetVirtualTag("…", …) — first string-literal arg captured in
|
||||
// three parts (prefix, content, closing quote) so token substitution touches ONLY the
|
||||
// literal content (never a comment, Logger string, or other code).
|
||||
@@ -92,4 +98,42 @@ public static class EquipmentScriptPaths
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merge a scripted alarm's dependency graph: the predicate script's distinct
|
||||
/// <c>ctx.GetTag("…")</c> read refs (via <see cref="ExtractDependencyRefs"/>, first-seen order)
|
||||
/// UNION the distinct <c>{TagPath}</c> token paths referenced in the message template (first-seen
|
||||
/// order, appended after the predicate reads, trimmed + non-empty). The reserved
|
||||
/// <c>{{equip}}</c> double-brace form is excluded by the token regex. Deterministic so the live
|
||||
/// composer (<c>Phase7Composer</c>) and the artifact-decode mirror (<c>DeploymentArtifact</c>)
|
||||
/// produce the exact same ordered list — the byte-parity contract <c>EquipmentScriptedAlarmPlan</c>
|
||||
/// equality depends on. Scripted alarms do NOT use <c>{{equip}}</c> substitution (only virtual
|
||||
/// tags do) — pass the predicate source as-is.
|
||||
/// </summary>
|
||||
/// <param name="predicateSource">The resolved predicate script source.</param>
|
||||
/// <param name="messageTemplate">The alarm message template carrying <c>{TagPath}</c> tokens.</param>
|
||||
/// <returns>The merged, distinct, deterministically-ordered dependency refs.</returns>
|
||||
public static IReadOnlyList<string> ExtractAlarmDependencyRefs(string? predicateSource, string? messageTemplate)
|
||||
{
|
||||
var seen = new HashSet<string>(StringComparer.Ordinal);
|
||||
var result = new List<string>();
|
||||
|
||||
// Predicate reads first — same regex extraction the VirtualTag projection uses.
|
||||
foreach (var r in ExtractDependencyRefs(predicateSource))
|
||||
{
|
||||
if (seen.Add(r)) result.Add(r);
|
||||
}
|
||||
|
||||
// Then message-template {TagPath} tokens (first-seen), trimmed, non-empty.
|
||||
if (!string.IsNullOrEmpty(messageTemplate))
|
||||
{
|
||||
foreach (Match m in MessageTemplateTokenRegex.Matches(messageTemplate))
|
||||
{
|
||||
var token = m.Groups[1].Value.Trim();
|
||||
if (token.Length > 0 && seen.Add(token)) result.Add(token);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user