fix(m9/T32b): resolve $ref in InboundAPI runtime validators (no deploy-passes/runtime-400); diamond test; ref-annotation message

This commit is contained in:
Joseph Doherty
2026-06-18 12:16:39 -04:00
parent 26e2cdef23
commit 71d5722692
9 changed files with 424 additions and 25 deletions
@@ -36,8 +36,21 @@ public static class ReturnValueValidator
/// </summary>
/// <param name="resultJson">The JSON-serialized script return value to validate.</param>
/// <param name="returnDefinition">JSON Schema describing the method's return value, or null/empty to skip validation. The legacy flat-array form is also accepted.</param>
/// <param name="resolveRef">
/// M9-T32b: optional JSON-Schema <c>$ref</c> resolution seam mapping a
/// <c>{"$ref":"lib:Name"}</c> reference target's name to the referenced schema JSON
/// (or <c>null</c> when the library entry does not exist). The executor pre-loads the
/// shared-schema library (backed by <c>ISharedSchemaRepository</c>) and supplies it
/// ONLY when the definition actually uses a <c>$ref</c>. <c>null</c> means no resolver:
/// schemas with no <c>$ref</c> behave exactly as before; a <c>$ref</c> with no resolver
/// (or a dangling one) is surfaced as a CLEAR invalid result naming the missing
/// reference — NOT an opaque parse-error from a thrown <see cref="JsonException"/>.
/// </param>
/// <returns>A <see cref="ReturnValidationResult"/> indicating success or describing the validation failures.</returns>
public static ReturnValidationResult Validate(string? resultJson, string? returnDefinition)
public static ReturnValidationResult Validate(
string? resultJson,
string? returnDefinition,
Func<string, string?>? resolveRef = null)
{
if (string.IsNullOrWhiteSpace(returnDefinition))
{
@@ -45,10 +58,14 @@ public static class ReturnValueValidator
return ReturnValidationResult.Valid();
}
InboundApiSchema? schema;
// M9-T32b: parse through the ref-COLLECTING path so a {"$ref":"lib:Name"} the
// resolver can satisfy is resolved inline, and a dangling/cyclic/over-depth ref is
// surfaced as a descriptive "could not be resolved" message rather than an opaque
// "Invalid return definition" from a swallowed JsonException.
SchemaParseResult parsed;
try
{
schema = InboundApiSchema.Parse(returnDefinition);
parsed = InboundApiSchema.ParseWithRefs(returnDefinition, resolveRef);
}
catch (JsonException)
{
@@ -56,6 +73,14 @@ public static class ReturnValueValidator
"Invalid return definition in method configuration");
}
if (parsed.UnresolvedReferences.Count > 0)
{
return ReturnValidationResult.Invalid(
$"Return definition references {ParameterValidator.DescribeUnresolved(parsed.UnresolvedReferences)} which could not be resolved");
}
InboundApiSchema? schema = parsed.Schema;
// A schema that declares no constraints (e.g. an object schema with no
// fields) leaves the return value unconstrained.
if (schema is null || (schema.Type == "object" && schema.Fields.Count == 0))