Merge Wave-1 integration: cross-lane scripting/runtime follow-ups (#731, #734)

Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
This commit is contained in:
Joseph Doherty
2026-10-22 15:25:43 -04:00
35 changed files with 1121 additions and 123 deletions
@@ -38,13 +38,16 @@ public static class CalculationDriverFactoryExtensions
/// <param name="registry">The driver factory registry to register with.</param>
/// <param name="loggerFactory">Optional logger factory for per-instance diagnostics loggers.</param>
/// <param name="scriptRoot">Optional root script logger (fans failure entries onto the <c>script-logs</c> topic).</param>
/// <param name="scripting">The node-wide <c>Scripting</c> options every Calculation driver inherits; a key
/// authored in a driver's own config overrides that one knob. Null ⇒ the compiled defaults.</param>
public static void Register(
DriverFactoryRegistry registry,
ILoggerFactory? loggerFactory = null,
ScriptRootLogger? scriptRoot = null)
ScriptRootLogger? scriptRoot = null,
ScriptingOptions? scripting = null)
{
ArgumentNullException.ThrowIfNull(registry);
registry.Register(DriverTypeName, (id, json) => CreateInstance(id, json, loggerFactory, scriptRoot));
registry.Register(DriverTypeName, (id, json) => CreateInstance(id, json, loggerFactory, scriptRoot, scripting));
}
/// <summary>Public overload for the Server-side bootstrapper + test consumers.</summary>
@@ -63,6 +66,18 @@ public static class CalculationDriverFactoryExtensions
public static CalculationDriver CreateInstance(
string driverInstanceId, string driverConfigJson,
ILoggerFactory? loggerFactory, ScriptRootLogger? scriptRoot)
=> CreateInstance(driverInstanceId, driverConfigJson, loggerFactory, scriptRoot, nodeScripting: null);
/// <summary>The full overload: also takes the node-wide <c>Scripting</c> options the driver inherits.</summary>
/// <param name="driverInstanceId">The unique identifier for the driver instance.</param>
/// <param name="driverConfigJson">The merged driver config JSON (carries the <c>RawTags</c> array).</param>
/// <param name="loggerFactory">Optional logger factory for per-instance diagnostics loggers.</param>
/// <param name="scriptRoot">Optional root script logger for failure fan-out.</param>
/// <param name="nodeScripting">The node-wide run-guard options; null ⇒ the compiled defaults.</param>
/// <returns>The constructed <see cref="CalculationDriver"/>.</returns>
public static CalculationDriver CreateInstance(
string driverInstanceId, string driverConfigJson,
ILoggerFactory? loggerFactory, ScriptRootLogger? scriptRoot, ScriptingOptions? nodeScripting)
{
ArgumentException.ThrowIfNullOrWhiteSpace(driverInstanceId);
ArgumentException.ThrowIfNullOrWhiteSpace(driverConfigJson);
@@ -71,15 +86,14 @@ public static class CalculationDriverFactoryExtensions
?? throw new InvalidOperationException(
$"Calculation driver config for '{driverInstanceId}' deserialised to null");
// Run-guard knobs are optional and default to the node-wide ScriptingOptions defaults; only an
// explicitly authored key overrides one (so a config carrying neither binds Scripting = null).
ScriptingOptions? scripting = null;
if (dto.MaxAllocBytesPerRun is not null || dto.QuarantineAfterConsecutiveTimeouts is not null)
{
scripting = new ScriptingOptions();
if (dto.MaxAllocBytesPerRun is { } alloc) scripting.MaxAllocBytesPerRun = alloc;
if (dto.QuarantineAfterConsecutiveTimeouts is { } n) scripting.QuarantineAfterConsecutiveTimeouts = n;
}
var scripting = ResolveScripting(
dto.MaxAllocBytesPerRun, dto.QuarantineAfterConsecutiveTimeouts, nodeScripting, out var adjustments);
var logger = loggerFactory?.CreateLogger<CalculationDriver>();
// An authored value is never changed silently: the run guard would clamp it anyway, without a word.
foreach (var adjustment in adjustments)
logger?.LogWarning(
"Calculation driver {DriverInstanceId}: its run-guard config was out of range and is clamped: {Adjustment}",
driverInstanceId, adjustment);
var options = new CalculationDriverOptions
{
@@ -89,9 +103,37 @@ public static class CalculationDriverFactoryExtensions
Scripting = scripting,
};
return new CalculationDriver(
options, driverInstanceId, scriptRoot,
logger: loggerFactory?.CreateLogger<CalculationDriver>());
return new CalculationDriver(options, driverInstanceId, scriptRoot, logger: logger);
}
/// <summary>
/// The run-guard options for one Calculation driver. The node-wide options are the base. A key authored
/// in the driver's config overrides only its own knob, on a copy, so the shared node object is never
/// written. With neither, the result is null and the guard uses its compiled defaults.
/// </summary>
/// <param name="maxAllocBytesPerRun">The driver config's <c>maxAllocBytesPerRun</c>, or null.</param>
/// <param name="quarantineAfterConsecutiveTimeouts">The driver config's <c>quarantineAfterConsecutiveTimeouts</c>, or null.</param>
/// <param name="nodeScripting">The node-wide options, or null.</param>
/// <param name="adjustments">The clamps applied to the driver's OWN keys (the node options were already
/// clamped and logged at bind), for the caller to log. Empty when nothing was out of range.</param>
/// <returns>The options to hand the driver, or null.</returns>
internal static ScriptingOptions? ResolveScripting(
long? maxAllocBytesPerRun, int? quarantineAfterConsecutiveTimeouts, ScriptingOptions? nodeScripting,
out IReadOnlyList<string> adjustments)
{
adjustments = [];
if (maxAllocBytesPerRun is null && quarantineAfterConsecutiveTimeouts is null) return nodeScripting;
var resolved = new ScriptingOptions();
if (nodeScripting is not null)
{
resolved.MaxAllocBytesPerRun = nodeScripting.MaxAllocBytesPerRun;
resolved.QuarantineAfterConsecutiveTimeouts = nodeScripting.QuarantineAfterConsecutiveTimeouts;
resolved.StuckOrphanQuarantineFactor = nodeScripting.StuckOrphanQuarantineFactor;
}
if (maxAllocBytesPerRun is { } alloc) resolved.MaxAllocBytesPerRun = alloc;
if (quarantineAfterConsecutiveTimeouts is { } n) resolved.QuarantineAfterConsecutiveTimeouts = n;
return resolved.Sanitized(out adjustments);
}
/// <summary>The merged-config DTO the calc factory binds from. The Calculation driver has no