fix(site-runtime): reject invalid runtime actor names at the site gate and never let ActorOf escalate to the Deployment Manager (review 2026-10-21 01/SR-C1)

An instance unique name Akka refuses as a path element (space, '#', '/',
non-ASCII) made Context.ActorOf throw on the singleton's own thread; the
default Restart stopped every Instance Actor on the site. DeployCompileValidator
now checks the instance + script/alarm/native-source actor names with
ActorPath.IsValidPathElement (the Commons ActorNameRules mirror is stricter
than Akka for " ( ), pinned by a parity sweep), CreateInstanceActor catches
InvalidActorNameException and every caller reports it, and the Instance Actor
skips a refused child name like a compile failure.
This commit is contained in:
Joseph Doherty
2026-10-22 06:24:28 -04:00
parent b5cebf9fa6
commit f5090fcd9e
8 changed files with 606 additions and 23 deletions
@@ -1943,7 +1943,8 @@ public class InstanceActor : ReceiveActor
_healthCollector,
_serviceProvider));
var actorRef = Context.ActorOf(props, $"script-{script.CanonicalName}");
var actorRef = TryCreateChild(props, $"script-{script.CanonicalName}", "Script", script.CanonicalName);
if (actorRef == null) continue;
_scriptActors[script.CanonicalName] = actorRef;
RouteChild(actorRef, TriggerRouting.ForScript(script.TriggerType, script.TriggerConfiguration));
}
@@ -2001,7 +2002,8 @@ public class InstanceActor : ReceiveActor
// Per-script timeout for the alarm on-trigger script.
onTriggerTimeoutSeconds));
var actorRef = Context.ActorOf(props, $"alarm-{alarm.CanonicalName}");
var actorRef = TryCreateChild(props, $"alarm-{alarm.CanonicalName}", "Alarm", alarm.CanonicalName);
if (actorRef == null) continue;
_alarmActors[alarm.CanonicalName] = actorRef;
_alarmPriorities[alarm.CanonicalName] = alarm.PriorityLevel;
_alarmMesReportable[alarm.CanonicalName] = alarm.IsMesReportable;
@@ -2034,7 +2036,9 @@ public class InstanceActor : ReceiveActor
nativeKind,
_serviceProvider));
var actorRef = Context.ActorOf(props, $"native-alarm-{nativeSource.CanonicalName}");
var actorRef = TryCreateChild(
props, $"native-alarm-{nativeSource.CanonicalName}", "Native alarm source", nativeSource.CanonicalName);
if (actorRef == null) continue;
_nativeAlarmActors[nativeSource.CanonicalName] = actorRef;
// Same kind handed to the NativeAlarmActor — used to stamp the
// (cosmetic) kind badge on placeholder rows for quiet bindings.
@@ -2046,6 +2050,31 @@ public class InstanceActor : ReceiveActor
_instanceUniqueName, _scriptActors.Count, _alarmActors.Count, _nativeAlarmActors.Count);
}
/// <summary>
/// Creates one child actor, or returns <see langword="null"/> when Akka refuses its name
/// (review 2026-10-21 01/SR-C1). The site deploy gate rejects such names before anything is
/// applied, so this only fires for a config stored before the gate checked names (boot load,
/// enable, self-heal) — and there it is handled like a compile failure: the offending child is
/// logged and skipped, the rest of the instance runs. It used to throw out of
/// <see cref="PreStart"/> and fail the whole instance.
/// </summary>
private IActorRef? TryCreateChild(Props props, string actorName, string kind, string canonicalName)
{
try
{
return Context.ActorOf(props, actorName);
}
// Only a name Akka refuses OUTRIGHT is skipped. A duplicate canonical name (also an
// InvalidActorNameException, "not unique") keeps failing PreStart as it always has.
catch (InvalidActorNameException ex) when (!ActorPath.IsValidPathElement(actorName))
{
_logger.LogError(ex,
"{Kind} '{Name}' on instance '{Instance}' skipped — its name cannot be used as a runtime actor name",
kind, canonicalName, _instanceUniqueName);
return null;
}
}
/// <summary>
/// Maps a bound connection's protocol to the native alarm kind stamped on
/// emitted events. MxAccess Gateway connections yield