fix(site-runtime): site-side compile failure now rejects the deployment per spec (S3) — synchronous validation gate before actor creation

Validation runs on the actor thread (not off-thread as the plan sketched): piping
the verdict back to self reorders concurrent deploys relative to each other and to
delete/disable, breaking the redeploy-supersede FIFO ordering (SR020/SR029). A deploy
is an infrequent admin command, so a brief synchronous Roslyn compile is acceptable.
This commit is contained in:
Joseph Doherty
2026-07-08 23:33:48 -04:00
parent 2caab79c0a
commit b83d1ed19c
3 changed files with 77 additions and 1 deletions
@@ -232,6 +232,40 @@ public class DeploymentManagerActorTests : TestKit, IDisposable
Assert.Equal("NewPump", response.InstanceUniqueName);
}
// ── S3: site-side compile failure rejects the deployment (no partial state) ──
private static string MakeConfigWithBrokenScriptJson(string instanceName) =>
JsonSerializer.Serialize(new FlattenedConfiguration
{
InstanceUniqueName = instanceName,
Attributes = [new ResolvedAttribute { CanonicalName = "TestAttr", Value = "42", DataType = "Int32" }],
Scripts = [new ResolvedScript { CanonicalName = "S1", Code = "this is not C# ~~~" }]
});
[Fact]
public async Task Deploy_WithNonCompilingScript_ReportsFailed_AndCreatesNoInstanceActor()
{
var actor = CreateDeploymentManager();
await Task.Delay(500); // wait for empty startup
actor.Tell(new DeployInstanceCommand(
"dep-bad", "bad-instance", "r1",
MakeConfigWithBrokenScriptJson("bad-instance"), "admin", DateTimeOffset.UtcNow));
var resp = ExpectMsg<DeploymentStatusResponse>(TimeSpan.FromSeconds(10));
Assert.Equal(DeploymentStatus.Failed, resp.Status);
Assert.NotNull(resp.ErrorMessage);
Assert.Contains("compil", resp.ErrorMessage!, StringComparison.OrdinalIgnoreCase);
// No partial state applied: instance actor must not exist …
Sys.ActorSelection(actor.Path / "bad-instance").Tell(new Identify(1));
Assert.Null(ExpectMsg<ActorIdentity>().Subject);
// … and no config must have been persisted.
var configs = await _storage.GetAllDeployedConfigsAsync();
Assert.DoesNotContain(configs, c => c.InstanceUniqueName == "bad-instance");
}
// ── M1.6: site event log `deployment` category ─────────────────────────
[Fact]