Repository navigation
fix(testing): validate capabilities against the echoed release under an external pin root - #3322
Conversation
…an external pin root Hosted compliance always runs with an external schema root for the pinned release, and 15.9.2's served-release rule let that root win over the echo. A 3.0 target therefore graded a conformant 3.2 get_adcp_capabilities response against 3.0: every storyboard response_schema check on that task failed (52 steps in the 3.0.27 suite), and discovery raised spurious capabilities_response_schema_invalid notices. For get_adcp_capabilities only, the storyboard response_schema check, discoverAgentProfile and TaskExecutor now validate against the echoed adcp_version when it is the pin's major, is listed in the response's own adcp.supported_versions, and has a schema root (one supplied for that release, an external root registered for it, or the SDK's bundled schemas), whether or not an external root is active for the pin. Otherwise they validate strictly against the pin. Other tasks, storyboard selection, the run's adcp_version and grading stay on the pin. New servedReleaseSchemaRoots option on comply(), runStoryboard() and runStoryboardStep(), and a withServedReleaseSchemaRoots() scope helper exported from @adcp/sdk/testing, let a hosted grader supply its own roots for echoed releases. They are activated only for that capabilities validation. Fixes #3320 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — clean, well-tested bugfix for #3320.
This PR fixes hosted compliance grading get_adcp_capabilities against the wrong release: a conformant 3.2 echo was being graded against the 3.0 pin when an external schema root was active. The fix gates the served-validation version on same-major + release-precision + supported_versions listing + a resolvable root, and fails closed to the pin otherwise.
Checked:
selectServedValidationVersion(schema-loader.ts:586-615) — correct gating, fails closed; removing the oldisExternalSchemaRootActiveguard is the intended reversal.withServedValidationVersion(schema-loader.ts:707-727) — served root scoped tofn, applied only forget_adcp_capabilitiesat all three call sites (TaskExecutor.ts:140, testing/client.ts:842, validations.ts:548); leaks nothing.validateResponseSchemaAgainst(TaskExecutor.ts:161-214) — faithful extraction, normalization/identity-3.3/warn-strict/v2.5 guard all preserved. No witness-not-translator violation.- New 410-line test covers fallback-to-pin-strict, served-root precedence, and other-tasks-ignore-served-roots.
Medium findings: none.
Non-blocking (low): changeset marked patch for an additive public-API addition — conventionally minor, but moot under SDK 16 beta pre-mode.
No blocking findings, no medium findings, no team gate, gated_paths false, high_risk false. Falls through to row 9.
…eject conflicting served-root keys Address review: the runner's Zod-rejection recovery treats a capabilities response validated against an echoed release with an external or supplied root as authoritative; withServedReleaseSchemaRoots rejects two keys for the same release with different roots; document the pin requirement and root lifetime. Tests now cover an unbundled echoed release made usable by a supplied root, served roots never activating for other tasks, and the hosted shape end to end through comply(). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — clean subsequent review, prior blockers none, delta is additive.
This is a follow-up to a prior clean approve (head a1f308f). The delta since then is 4 files / ~240 additive lines, all reviewed:
validations.ts(L719-736):isExternalResponseSchemaAuthoritativenow threads optionaltaskName/taskResultand treats a capabilities echo with an active external/served release root as authoritative. Logic is consistent — bundled echo with no external/served root correctly returns false, served root is scoped only for the boolean check with no leak.schema-loader.ts(L1350-1356): new duplicate-release guard inwithServedReleaseSchemaRootsfails-closed when two map keys name the same release with different roots.types.ts: doc-comment clarification only.- New test file: covers unbundled-3.9-root, cross-task isolation, duplicate-key, and end-to-end
comply()paths.
No Critical/High/Medium findings. Changeset (patch) and new public export were in scope at the prior clean approve and remain sound.
Gate checks:
- No no-auto-approve team match.
- gated_paths: false (review_decision REVIEW_REQUIRED does not trigger row 2 when gated_paths is false).
- high_risk: false.
- No medium findings → rows 4, 5, 8 do not fire; prior decision was approve so row 6 does not apply.
Falls through to row 9 → approve.
…external pin root for SDK 15.9.x (#3323) Backport of #3322. Hosted compliance always runs with an external schema root for the pinned release, and the served-release rule from 15.9.2 let that root win over the echo. A 3.0 target therefore graded a conformant 3.2 get_adcp_capabilities response against 3.0: every storyboard response_schema check on that task failed (52 steps in the 3.0.27 suite), and discovery raised spurious capabilities_response_schema_invalid notices. For get_adcp_capabilities only, the storyboard response_schema check, discoverAgentProfile and TaskExecutor now validate against the echoed adcp_version when it is the pin's major, is listed in the response's own adcp.supported_versions, and has a schema root (one supplied for that release, an external root registered for it, or the SDK's bundled schemas), whether or not an external root is active for the pin. Otherwise they validate strictly against the pin. Other tasks, storyboard selection, the run's adcp_version and grading stay on the pin. New servedReleaseSchemaRoots option on comply(), runStoryboard() and runStoryboardStep(), and a withServedReleaseSchemaRoots() scope helper exported from @adcp/sdk/testing, let a hosted grader supply its own roots for echoed releases. They are activated only for that capabilities validation. Fixes #3320 Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Summary
Fixes #3320. Hosted compliance always runs
comply()with an external schema root for the pinned release. Under 15.9.2's served-release rule (#3278 / #3280), that root won over the agent's echoedadcp_version. So a 3.0 target graded a conformant 3.2get_adcp_capabilitiesresponse against 3.0, with two effects:response_schemacheck on that task failed (52 steps across 43 storyboards in the 3.0.27 suite);capabilities_response_schema_invalidnotices, which fix(compliance): validate capability notices against the release the agent echoed adcp#8264 currently re-validates after the run.Rule
For
get_adcp_capabilitiesonly, three checks now validate against the echoed release:response_schemacheck;discoverAgentProfile, which feeds the notices;TaskExecutor, which backsgetAdcpCapabilities().They do so when all of these hold, whether or not an external root is active for the pin:
adcp.supported_versions. This condition is new; fix(testing): validate capabilities against the served release; key compliance_testing in the cross-check #3280 deliberately didn't consult it;servedReleaseSchemaRoots, an external root registered or scoped for it, or the SDK's bundled schemas.Otherwise they validate strictly against the pin. That covers a different major, an unlisted echo, an echo with no schema, and no echo. An echo of the pinned release keeps the exact pin and its root. Other tasks' responses, storyboard selection, the run's
adcp_versionand grading are untouched.New API:
servedReleaseSchemaRootsTestOptions.servedReleaseSchemaRoots?: Readonly<Record<string, string>>, so it is available oncomply(),runStoryboard()andrunStoryboardStep().'3.2','3.2.5','3.3.0-beta.1'). Values are schema-data roots in the same shape asschemaRoot.withServedReleaseSchemaRoots(roots, fn)is exported from@adcp/sdk/testingandvalidation, for callers outsidecomply()and the runner. It is scoped with AsyncLocalStorage, likewithExternalSchemaRoot.withExternalSchemaRootuses. That check takes about 1s per full root, so it is memoized per(version, root)for the life of the process. A malformed or mismatched map throws before the run, and so do two keys that name the same release with different roots.adcpVersion, or the storyboard or compliance bundle'sadcp_version).comply()always does.withServedValidationVersion), so it never reaches any other response. It isn't cleared from the validator caches afterwards. Those caches (states,schemaRefValidators) are keyed by bundle key plus root, so switching roots is safe.I chose a map over a resolver callback because it matches
schemaRoot, is serializable, and is all hosted needs. Hosted can then drop its post-run notice re-validation.Changes
src/lib/validation/schema-loader.ts: the rule,withServedReleaseSchemaRoots, and internalwithServedValidationVersion.resolveServedValidationVersionkeeps its signature.src/lib/testing/storyboard/validations.ts: theresponse_schemacheck onget_adcp_capabilitiesvalidates under the selected release. When it picks the echo, it uses the echo for bothadcpVersionandresponseAdcpVersion, so the reportedschema_id/schema_urlname the release that was actually validated.src/lib/testing/client.ts(discoverAgentProfile) andsrc/lib/core/TaskExecutor.ts. The TaskExecutor body moved intovalidateResponseSchemaAgainst, with the same behavior and error handling.src/lib/testing/compliance/comply.tsandstoryboard/runner.ts: scope the option around the run. Incomply()that includes discovery.isExternalResponseSchemaAuthoritative, used by the runner's Zod-rejection recovery path, also treats a capabilities response as authoritative when it is validated against an echoed release whose root is external or supplied.docs/guides/VALIDATE-YOUR-AGENT.md.The changeset is
patch, on purpose. This is a conformance bug fix that has to ship as a 15.9.x patch forlatest.servedReleaseSchemaRootsis an additive, optional input that exists only to complete the fix for hosted callers. Aminorchangeset would push the 15.9.x backport to 15.10.0.Test plan
test/lib/capabilities-echoed-release-hosted-validation.test.js(24 tests). Against unpatchedmain, 17 fail; the 7 that pass are the pin-fallback guards. Coverage:response_schemacheck on a same-major echoed 3.2 response passes, and a real 3.2 schema error still fails against 3.2.get_productsechoing 3.2 keeps its pin verdict. With the pin set to 3.2 and a supplied 3.2.5 root in scope,get_productsis still validated against the bundled 3.2, andisExternalSchemaRootActive('3.2')stays false.runStoryboard({ servedReleaseSchemaRoots }).comply()end to end in the hosted shape:version: '3.0.25'plus an external 3.0schemaRoot, against a live seller echoing 3.2. Onmainthis reproduces the bug: falseresponse_schemafailures andcapabilities_response_schema_invalidnotices. With the fix there are none. WithservedReleaseSchemaRoots, the supplied root grades both. A bad map rejects before the run.TaskExecutorstrict mode, andrunStoryboardwithschemaRoot. Also input validation, including conflicting keys, and the@adcp/sdk/testingexport.test/lib/capabilities-served-release-validation.test.jsfor the newsupported_versionscondition and the reversed pin-root precedence.npm run test:nodelocally. Every suite passes except the Postgres-backed ones (ECONNREFUSED, no local database), which CI covers. A few other files failed in the full run because the pre-push hook rebuiltdist/mid-run; all of them pass when re-run in isolation.npm run typecheck,npm run lint(0 errors),lint:boundaries,prettier --check,ci:docs-check,build:libA 15.9.x backport for
latestfollows in a separate PR.🤖 Generated with Claude Code