feat(testrun): route WaitForAttribute in the Test-Run sandbox (deferred #14)

Closes deferred-work register item #14 with full fidelity. The Central UI Test-Run
sandbox previously threw ScriptSandboxException for Attributes.WaitAsync/WaitForAsync;
now the value-equality forms route to the bound deployed instance over the same
cross-site path inbound Route.To().WaitForAttribute() uses.

- Commons: additive trailing RouteToWaitForAttributeRequest.RequireGoodQuality
  (default false; message-evolution safe) so quality-gated waits route too.
- SiteRuntime: DeploymentManagerActor's routed-wait handler now threads
  request.RequireGoodQuality into the InstanceActor WaitForAttributeRequest
  (previously hard-coded to default).
- CentralUI sandbox: ISandboxInstanceGateway.WaitForAttributeAsync +
  SandboxInstanceGateway impl (via CommunicationService.RouteToWaitForAttributeAsync);
  SandboxAttributeAccessor value-equality WaitAsync/WaitForAsync route through it
  (codec-encoded target, scope-resolved name). Predicate overloads stay unsupported
  (an in-process lambda can't be routed) and throw a clearer, dedicated message.
- Tests: sandbox accessor routing + predicate/unbound rejection (CentralUI);
  site-handler RequireGoodQuality threading (SiteRuntime). Register + the historical
  waitfor-deferred plan doc updated.

Full slnx build 0/0; CentralUI 59, SiteRuntime routed-wait 4, InboundAPI wait 32 green.

Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
This commit is contained in:
Joseph Doherty
2026-07-10 09:29:38 -04:00
parent 4cd21b342b
commit 22136d36d9
8 changed files with 216 additions and 31 deletions
@@ -1756,10 +1756,11 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
/// Spec §6 (WD-2b): unpacks a routed <see cref="RouteToWaitForAttributeRequest"/>
/// (inbound-API <c>Route.To().WaitForAttribute()</c>) into the deployed
/// Instance Actor's site-local <see cref="WaitForAttributeRequest"/> and relays
/// the result back. Value-equality only across the wire — the predicate is null
/// and <c>RequireGoodQuality</c> is left at its default. The Ask is bounded by the
/// wait timeout plus slack (NOT a fixed 30s), since the wait legitimately blocks
/// for up to <see cref="RouteToWaitForAttributeRequest.Timeout"/>.
/// the result back. Value-equality across the wire — the predicate is null (a
/// cross-process lambda cannot be routed), but <c>RequireGoodQuality</c> is honored
/// from <see cref="RouteToWaitForAttributeRequest.RequireGoodQuality"/>. The Ask is
/// bounded by the wait timeout plus slack (NOT a fixed 30s), since the wait
/// legitimately blocks for up to <see cref="RouteToWaitForAttributeRequest.Timeout"/>.
/// </summary>
private void RouteInboundApiWaitForAttribute(RouteToWaitForAttributeRequest request)
{
@@ -1773,10 +1774,12 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
}
var sender = Sender;
// Routed waits are value-equality only (predicate null); RequireGoodQuality left at default.
// Routed waits are value-equality (predicate null — a cross-process lambda
// cannot be routed); RequireGoodQuality is honored from the request.
var inner = new WaitForAttributeRequest(
request.CorrelationId, request.InstanceUniqueName, request.AttributeName,
request.TargetValueEncoded, null, request.Timeout, DateTimeOffset.UtcNow);
request.TargetValueEncoded, null, request.Timeout, DateTimeOffset.UtcNow,
request.RequireGoodQuality);
// Ask bounded by the WAIT timeout + slack — NOT a fixed 30s (the wait legitimately blocks up to request.Timeout).
instanceActor.Ask<WaitForAttributeResponse>(inner, request.Timeout + TimeSpan.FromSeconds(5))