feat(inbound-api): machine-readable error codes per spec (incl. SITE_UNREACHABLE) + 415 for non-JSON bodies

Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
This commit is contained in:
Joseph Doherty
2026-07-10 04:29:55 -04:00
parent 761729b5d0
commit b5e80d4c00
8 changed files with 248 additions and 30 deletions
@@ -384,7 +384,7 @@ public class InboundScriptExecutor
// Fast-fail only when the CURRENT script text is the one already known bad.
if (_knownBadMethods.TryGetValue(method.Name, out var badScript)
&& string.Equals(badScript, method.Script, StringComparison.Ordinal))
return new InboundScriptResult(false, null, "Script compilation failed for this method");
return new InboundScriptResult(false, null, "Script compilation failed for this method", "SCRIPT_COMPILE_FAILED");
// Lazy compile (handles methods created after startup, and stale/known-bad
// recompiles). Compile outside the cache so a failed compile is not stored,
@@ -396,7 +396,7 @@ public class InboundScriptExecutor
// Routed through TryRecordBadMethod so the
// cache is bounded under a flood of unique method names.
TryRecordBadMethod(method.Name, method.Script ?? string.Empty);
return new InboundScriptResult(false, null, "Script compilation failed for this method");
return new InboundScriptResult(false, null, "Script compilation failed for this method", "SCRIPT_COMPILE_FAILED");
}
_knownBadMethods.TryRemove(method.Name, out _);
@@ -448,17 +448,25 @@ public class InboundScriptExecutor
if (timeoutCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested)
{
_logger.LogWarning("Script execution timed out for method {Method}", method.Name);
return new InboundScriptResult(false, null, "Script execution timed out");
return new InboundScriptResult(false, null, "Script execution timed out", "TIMEOUT");
}
_logger.LogDebug("Inbound API request for method {Method} cancelled by client", method.Name);
return new InboundScriptResult(false, null, "Request cancelled by client");
}
catch (SiteUnreachableException)
{
// C3: a routed Route.To(...).Call(...) reached a site that could not be
// contacted. Surface the machine-readable SITE_UNREACHABLE code (spec
// Component-InboundAPI failure body) instead of the generic SCRIPT_ERROR.
_logger.LogWarning("Routed site unreachable for method {Method}", method.Name);
return new InboundScriptResult(false, null, "Site unreachable", "SITE_UNREACHABLE");
}
catch (Exception ex)
{
_logger.LogError(ex, "Script execution failed for method {Method}", method.Name);
// Safe error message, no internal details
return new InboundScriptResult(false, null, "Internal script error");
return new InboundScriptResult(false, null, "Internal script error", "SCRIPT_ERROR");
}
finally
{
@@ -537,7 +545,17 @@ public class InboundScriptContext
/// <summary>
/// Result of executing an inbound API script.
/// </summary>
/// <param name="Success">Whether the script executed successfully.</param>
/// <param name="ResultJson">The serialized script return value on success; null otherwise.</param>
/// <param name="ErrorMessage">A sanitized, caller-safe error message on failure; null on success.</param>
/// <param name="ErrorCode">
/// C3: an additive, machine-readable error code the endpoint surfaces in the failure
/// body's <c>code</c> field (e.g. <c>TIMEOUT</c>, <c>SITE_UNREACHABLE</c>,
/// <c>SCRIPT_COMPILE_FAILED</c>, <c>SCRIPT_ERROR</c>). Null when the executor does not
/// classify the failure; the endpoint then falls back to <c>SCRIPT_ERROR</c>.
/// </param>
public record InboundScriptResult(
bool Success,
string? ResultJson,
string? ErrorMessage);
string? ErrorMessage,
string? ErrorCode = null);