fix(site-runtime): seed republished overrides into Script Actors without firing scripts; alarms still evaluate (review 2026-10-21 01/SR-U1, decision M1)
HandleOverridesLoaded now sends children a site-local AttributeValueSeed (stream still gets the AttributeValueChanged). ScriptActor updates its snapshot, Conditional truth value and Expression last result (seed-only eval, never fires); AlarmActor evaluates it as a normal change.
This commit is contained in:
+81
@@ -172,4 +172,85 @@ public class InstanceActorOverrideRepublishTests : TestKit, IDisposable
|
||||
Assert.False(response.TimedOut);
|
||||
Assert.Contains("instance stopped", response.ErrorMessage);
|
||||
}
|
||||
|
||||
private static ResolvedScript FiringScript(string name, string triggerType, object triggerConfig, string flag) => new()
|
||||
{
|
||||
CanonicalName = name,
|
||||
TriggerType = triggerType,
|
||||
TriggerConfiguration = JsonSerializer.Serialize(triggerConfig),
|
||||
Code = $"await Instance.SetAttribute(\"{flag}\", \"1\"); return null;"
|
||||
};
|
||||
|
||||
private string AttributeOf(IActorRef instance, string instanceName, string attribute)
|
||||
{
|
||||
instance.Tell(new GetAttributeRequest("g", instanceName, attribute, DateTimeOffset.UtcNow));
|
||||
return ExpectMsg<GetAttributeResponse>(TimeSpan.FromSeconds(15)).Value?.ToString() ?? "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// User decision M1: an override seeded at actor start updates trigger state but does NOT run
|
||||
/// ValueChange / Conditional / Expression scripts; an Expression ALARM still raises; and a
|
||||
/// later real change fires all three scripts.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task SeededOverride_RaisesAlarm_ButFiresNoScript_UntilARealChange()
|
||||
{
|
||||
const string name = "PumpSeed";
|
||||
await _storage.SetStaticOverrideAsync(name, "Level", "50");
|
||||
const string over20 = "int.Parse(Attributes[\"Level\"].ToString()) > 20";
|
||||
|
||||
var config = new FlattenedConfiguration
|
||||
{
|
||||
InstanceUniqueName = name,
|
||||
Attributes =
|
||||
[
|
||||
new ResolvedAttribute { CanonicalName = "Level", Value = "10", DataType = "Int32" },
|
||||
new ResolvedAttribute { CanonicalName = "FiredVC", Value = "0", DataType = "String" },
|
||||
new ResolvedAttribute { CanonicalName = "FiredCond", Value = "0", DataType = "String" },
|
||||
new ResolvedAttribute { CanonicalName = "FiredExpr", Value = "0", DataType = "String" }
|
||||
],
|
||||
Scripts =
|
||||
[
|
||||
FiringScript("VC", "ValueChange", new { attributeName = "Level" }, "FiredVC"),
|
||||
FiringScript("Cond", "Conditional",
|
||||
new { attributeName = "Level", @operator = ">", threshold = 20 }, "FiredCond"),
|
||||
FiringScript("Expr", "Expression", new { expression = over20 }, "FiredExpr")
|
||||
],
|
||||
Alarms =
|
||||
[
|
||||
new ResolvedAlarm
|
||||
{
|
||||
CanonicalName = "LevelHigh", TriggerType = "Expression", PriorityLevel = 500,
|
||||
TriggerConfiguration = JsonSerializer.Serialize(new { expression = over20 })
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var instance = StartInstance(name, config);
|
||||
|
||||
// The alarm raises from the seeded value alone — no other write.
|
||||
AwaitAssert(() => Assert.Equal(AlarmState.Active, AlarmStateOf(instance, name, "LevelHigh")),
|
||||
TimeSpan.FromSeconds(30), TimeSpan.FromMilliseconds(100));
|
||||
Assert.Equal("50", AttributeOf(instance, name, "Level"));
|
||||
|
||||
await Task.Delay(TimeSpan.FromSeconds(1.5));
|
||||
Assert.Equal("0", AttributeOf(instance, name, "FiredVC"));
|
||||
Assert.Equal("0", AttributeOf(instance, name, "FiredCond"));
|
||||
Assert.Equal("0", AttributeOf(instance, name, "FiredExpr"));
|
||||
|
||||
// Real changes fire them: down (Expression edge resets) then up again.
|
||||
instance.Tell(new SetStaticAttributeCommand("w1", name, "Level", "5", DateTimeOffset.UtcNow));
|
||||
ExpectMsg<SetStaticAttributeResponse>(TimeSpan.FromSeconds(15));
|
||||
await Task.Delay(500);
|
||||
instance.Tell(new SetStaticAttributeCommand("w2", name, "Level", "60", DateTimeOffset.UtcNow));
|
||||
ExpectMsg<SetStaticAttributeResponse>(TimeSpan.FromSeconds(15));
|
||||
|
||||
AwaitAssert(() =>
|
||||
{
|
||||
Assert.Equal("1", AttributeOf(instance, name, "FiredVC"));
|
||||
Assert.Equal("1", AttributeOf(instance, name, "FiredCond"));
|
||||
Assert.Equal("1", AttributeOf(instance, name, "FiredExpr"));
|
||||
}, TimeSpan.FromSeconds(20), TimeSpan.FromMilliseconds(200));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user