fix(mesh): serve the artifact regardless of deployment status (Phase 3 live-gate deadlock)
The Phase 3 live gate deadlocked every FetchAndCache deploy: DeploymentArtifactGrpcService gated on Status==Sealed, but central seals a deployment only AFTER every node acks Applied, and a FetchAndCache node cannot ack until it has fetched the artifact — seal-needs-ack -> ack-needs-fetch -> fetch-needs-sealed. The node's fetch reached central and passed the shared-key interceptor, then got a clean NotFound; the #485 apply-failure path correctly kept last-known-good, but the deploy could never seal. Direct mode reads the same ArtifactBlob from SQL while the row is still AwaitingApplyAcks, so the serve path must too. Drop the Sealed gate: serve any deployment whose blob is non-empty (unknown id / empty blob still collapse to NotFound — existence-hiding + #485). Access is already gated by the interceptor, so there is no reason to hide a non-sealed deployment a node is legitimately applying. The Task 2 'non-sealed -> NotFound' test flips to 'non-sealed with a blob is served'. Claude-Session: https://claude.ai/code/session_01GASWkNEi68FSCtvr6rLoEW
This commit is contained in:
+11
-6
@@ -73,16 +73,21 @@ public class DeploymentArtifactServiceTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task A_non_sealed_deployment_throws_NotFound()
|
||||
public async Task A_non_sealed_deployment_with_a_blob_is_served()
|
||||
{
|
||||
// A node fetches DURING apply, before the deployment is sealed (seal-needs-ack →
|
||||
// ack-needs-fetch). Gating on Sealed would deadlock every FetchAndCache deploy. So a
|
||||
// non-sealed deployment that has a blob must stream — matching Direct mode reading the same
|
||||
// AwaitingApplyAcks row from SQL. (Regression for the Phase 3 live-gate deadlock.)
|
||||
var dbName = $"artsvc-{Guid.NewGuid():N}";
|
||||
var id = Seed(dbName, DeploymentStatus.AwaitingApplyAcks, [1, 2, 3]);
|
||||
var blob = new byte[] { 1, 2, 3 };
|
||||
var id = Seed(dbName, DeploymentStatus.AwaitingApplyAcks, blob);
|
||||
|
||||
var ex = await Should.ThrowAsync<RpcException>(() => Service(dbName).Fetch(
|
||||
new FetchRequest { DeploymentId = id.ToString() },
|
||||
new CollectingStreamWriter(), new FakeServerCallContext()));
|
||||
var writer = new CollectingStreamWriter();
|
||||
await Service(dbName).Fetch(
|
||||
new FetchRequest { DeploymentId = id.ToString() }, writer, new FakeServerCallContext());
|
||||
|
||||
ex.StatusCode.ShouldBe(StatusCode.NotFound);
|
||||
writer.Reassembled().ShouldBe(blob);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user