feat(triggers): runtime expression trigger evaluation for scripts and alarms

This commit is contained in:
Joseph Doherty
2026-05-16 05:35:02 -04:00
parent f789ab4a91
commit 9e21b47080
5 changed files with 301 additions and 22 deletions

View File

@@ -515,6 +515,10 @@ public class InstanceActor : ReceiveActor
continue;
}
// Compile the trigger expression for Expression-triggered scripts.
var triggerExpression = CompileTriggerExpression(
script.TriggerType, script.TriggerConfiguration, $"script-trigger-{script.CanonicalName}");
var props = Props.Create(() => new ScriptActor(
script.CanonicalName,
_instanceUniqueName,
@@ -524,6 +528,7 @@ public class InstanceActor : ReceiveActor
_sharedScriptLibrary,
_options,
_logger,
triggerExpression,
_healthCollector,
_serviceProvider));
@@ -559,6 +564,10 @@ public class InstanceActor : ReceiveActor
}
}
// Compile the trigger expression for Expression-triggered alarms.
var triggerExpression = CompileTriggerExpression(
alarm.TriggerType, alarm.TriggerConfiguration, $"alarm-trigger-expr-{alarm.CanonicalName}");
var props = Props.Create(() => new AlarmActor(
alarm.CanonicalName,
_instanceUniqueName,
@@ -568,6 +577,7 @@ public class InstanceActor : ReceiveActor
_sharedScriptLibrary,
_options,
_logger,
triggerExpression,
_healthCollector));
var actorRef = Context.ActorOf(props, $"alarm-{alarm.CanonicalName}");
@@ -581,6 +591,47 @@ public class InstanceActor : ReceiveActor
_instanceUniqueName, _scriptActors.Count, _alarmActors.Count);
}
/// <summary>
/// Compiles the boolean trigger expression for an Expression-triggered
/// script or alarm. Returns null for non-Expression triggers, a blank
/// expression, or a compilation failure (logged) — in which case the
/// trigger is inert and the actor still starts.
/// </summary>
private Microsoft.CodeAnalysis.Scripting.Script<object?>? CompileTriggerExpression(
string? triggerType, string? triggerConfigJson, string compileName)
{
if (!string.Equals(triggerType, "Expression", StringComparison.OrdinalIgnoreCase))
return null;
if (string.IsNullOrEmpty(triggerConfigJson))
return null;
string? expression;
try
{
var doc = JsonSerializer.Deserialize<JsonElement>(triggerConfigJson);
expression = doc.TryGetProperty("expression", out var e) ? e.GetString() : null;
}
catch (Exception ex)
{
_logger.LogWarning(ex,
"Failed to read trigger expression config for {Name} on {Instance}",
compileName, _instanceUniqueName);
return null;
}
if (string.IsNullOrWhiteSpace(expression))
return null;
var result = _compilationService.CompileTriggerExpression(compileName, expression);
if (result.IsSuccess)
return result.CompiledScript;
_logger.LogError(
"Trigger expression for {Name} on {Instance} failed to compile: {Errors}",
compileName, _instanceUniqueName, string.Join("; ", result.Errors));
return null;
}
/// <summary>
/// Read-only access to current attribute count (for testing/diagnostics).
/// </summary>