@adcp/sdk 7.0.0 → 7.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/adcp-config.js +10 -1
- package/bin/adcp.js +424 -22
- package/dist/lib/auth/oauth/authorization-required.d.ts +17 -0
- package/dist/lib/auth/oauth/authorization-required.d.ts.map +1 -1
- package/dist/lib/auth/oauth/authorization-required.js +20 -0
- package/dist/lib/auth/oauth/authorization-required.js.map +1 -1
- package/dist/lib/auth/oauth/index.d.ts +1 -1
- package/dist/lib/auth/oauth/index.d.ts.map +1 -1
- package/dist/lib/auth/oauth/index.js +2 -1
- package/dist/lib/auth/oauth/index.js.map +1 -1
- package/dist/lib/core/AgentClient.d.ts.map +1 -1
- package/dist/lib/core/SingleAgentClient.d.ts.map +1 -1
- package/dist/lib/core/SingleAgentClient.js +13 -2
- package/dist/lib/core/SingleAgentClient.js.map +1 -1
- package/dist/lib/discovery/property-crawler.d.ts +13 -1
- package/dist/lib/discovery/property-crawler.d.ts.map +1 -1
- package/dist/lib/discovery/property-crawler.js +40 -14
- package/dist/lib/discovery/property-crawler.js.map +1 -1
- package/dist/lib/discovery/resolve-agent-properties.d.ts +103 -0
- package/dist/lib/discovery/resolve-agent-properties.d.ts.map +1 -0
- package/dist/lib/discovery/resolve-agent-properties.js +182 -0
- package/dist/lib/discovery/resolve-agent-properties.js.map +1 -0
- package/dist/lib/discovery/types.d.ts +41 -2
- package/dist/lib/discovery/types.d.ts.map +1 -1
- package/dist/lib/discovery/types.js +2 -1
- package/dist/lib/discovery/types.js.map +1 -1
- package/dist/lib/discovery/validate-adagents.d.ts +114 -0
- package/dist/lib/discovery/validate-adagents.d.ts.map +1 -0
- package/dist/lib/discovery/validate-adagents.js +417 -0
- package/dist/lib/discovery/validate-adagents.js.map +1 -0
- package/dist/lib/errors/index.d.ts +42 -5
- package/dist/lib/errors/index.d.ts.map +1 -1
- package/dist/lib/errors/index.js +64 -9
- package/dist/lib/errors/index.js.map +1 -1
- package/dist/lib/index.d.ts +3 -1
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +17 -10
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/protocols/a2a.d.ts.map +1 -1
- package/dist/lib/protocols/a2a.js +70 -11
- package/dist/lib/protocols/a2a.js.map +1 -1
- package/dist/lib/protocols/mcp.d.ts.map +1 -1
- package/dist/lib/protocols/mcp.js +61 -5
- package/dist/lib/protocols/mcp.js.map +1 -1
- package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
- package/dist/lib/signing/fetch-async.d.ts.map +1 -1
- package/dist/lib/signing/fetch-async.js +5 -0
- package/dist/lib/signing/fetch-async.js.map +1 -1
- package/dist/lib/signing/fetch.d.ts.map +1 -1
- package/dist/lib/signing/fetch.js +11 -0
- package/dist/lib/signing/fetch.js.map +1 -1
- package/dist/lib/testing/client.d.ts +16 -2
- package/dist/lib/testing/client.d.ts.map +1 -1
- package/dist/lib/testing/client.js +1 -0
- package/dist/lib/testing/client.js.map +1 -1
- package/dist/lib/testing/compliance/comply.d.ts +17 -2
- package/dist/lib/testing/compliance/comply.d.ts.map +1 -1
- package/dist/lib/testing/compliance/comply.js +38 -0
- package/dist/lib/testing/compliance/comply.js.map +1 -1
- package/dist/lib/testing/compliance/spec-conformance.d.ts.map +1 -1
- package/dist/lib/testing/compliance/spec-conformance.js +1 -0
- package/dist/lib/testing/compliance/spec-conformance.js.map +1 -1
- package/dist/lib/testing/compliance/types.d.ts +11 -0
- package/dist/lib/testing/compliance/types.d.ts.map +1 -1
- package/dist/lib/testing/index.d.ts +1 -1
- package/dist/lib/testing/index.d.ts.map +1 -1
- package/dist/lib/testing/index.js.map +1 -1
- package/dist/lib/testing/storyboard/default-invariants.js +21 -2
- package/dist/lib/testing/storyboard/default-invariants.js.map +1 -1
- package/dist/lib/testing/storyboard/index.d.ts +1 -1
- package/dist/lib/testing/storyboard/index.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/index.js.map +1 -1
- package/dist/lib/testing/storyboard/runner.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/runner.js +284 -16
- package/dist/lib/testing/storyboard/runner.js.map +1 -1
- package/dist/lib/testing/storyboard/types.d.ts +112 -1
- package/dist/lib/testing/storyboard/types.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/types.js +1 -0
- package/dist/lib/testing/storyboard/types.js.map +1 -1
- package/dist/lib/utils/response-unwrapper.d.ts +33 -0
- package/dist/lib/utils/response-unwrapper.d.ts.map +1 -1
- package/dist/lib/utils/response-unwrapper.js +41 -3
- package/dist/lib/utils/response-unwrapper.js.map +1 -1
- package/dist/lib/version.d.ts +10 -6
- package/dist/lib/version.d.ts.map +1 -1
- package/dist/lib/version.js +11 -4
- package/dist/lib/version.js.map +1 -1
- package/docs/llms.txt +10 -2
- package/package.json +1 -1
- package/skills/call-adcp-agent/SKILL.md +1 -1
|
@@ -32,6 +32,7 @@ const validations_1 = require("./validations");
|
|
|
32
32
|
const parallel_dispatch_1 = require("./parallel-dispatch");
|
|
33
33
|
const path_1 = require("./path");
|
|
34
34
|
const redact_secrets_1 = require("../../utils/redact-secrets");
|
|
35
|
+
const response_unwrapper_1 = require("../../utils/response-unwrapper");
|
|
35
36
|
const test_controller_1 = require("../test-controller");
|
|
36
37
|
const request_builder_1 = require("./request-builder");
|
|
37
38
|
const client_2 = require("../client");
|
|
@@ -681,6 +682,7 @@ function buildCapabilityUnsupportedResult(agentUrls, storyboard, detail) {
|
|
|
681
682
|
strict_only_failures: 0,
|
|
682
683
|
lenient_also_failed: 0,
|
|
683
684
|
},
|
|
685
|
+
notices: [],
|
|
684
686
|
};
|
|
685
687
|
}
|
|
686
688
|
/**
|
|
@@ -701,6 +703,7 @@ const REQUIREMENT_TO_SKIP_REASON = {
|
|
|
701
703
|
seeded_state: 'requirement_unmet',
|
|
702
704
|
real_wire: 'requirement_unmet',
|
|
703
705
|
webhook_receiver: 'requirement_unmet',
|
|
706
|
+
request_signer: 'not_applicable',
|
|
704
707
|
};
|
|
705
708
|
/**
|
|
706
709
|
* Build a minimal StoryboardResult for a storyboard skipped because a
|
|
@@ -756,6 +759,7 @@ function buildRequirementUnmetResult(agentUrls, storyboard, requirement, detail)
|
|
|
756
759
|
strict_only_failures: 0,
|
|
757
760
|
lenient_also_failed: 0,
|
|
758
761
|
},
|
|
762
|
+
notices: [],
|
|
759
763
|
};
|
|
760
764
|
}
|
|
761
765
|
/**
|
|
@@ -777,8 +781,21 @@ function buildRequirementUnmetResult(agentUrls, storyboard, requirement, detail)
|
|
|
777
781
|
* Autodetected from step `sample_request` token presence (see
|
|
778
782
|
* `detectImplicitRequires`); authors don't write this tag manually.
|
|
779
783
|
* Spec: adcp-client#1678.
|
|
784
|
+
* - `request_signer` — agent advertises `request_signing.supported: true`
|
|
785
|
+
* in `get_adcp_capabilities`. Autodetected for any storyboard whose
|
|
786
|
+
* id is `'signed_requests'` or that contains a `request_signing_probe`
|
|
787
|
+
* step (see `detectImplicitRequires`); authors don't write this tag
|
|
788
|
+
* manually. Absence-skip semantics are INVERTED from the general
|
|
789
|
+
* `requires_capability` rule: the signed-requests universal
|
|
790
|
+
* storyboard's own gating spec (signed-requests.yaml prerequisites)
|
|
791
|
+
* declares "agents that do not advertise support are not tested
|
|
792
|
+
* against this storyboard — absence of advertisement is not a
|
|
793
|
+
* failure". When the runner can't read capabilities (no profile
|
|
794
|
+
* threaded through), the gate is a no-op — the caller accepted
|
|
795
|
+
* responsibility for capability compatibility by reusing an external
|
|
796
|
+
* client. Spec: adcp-client#1702.
|
|
780
797
|
*/
|
|
781
|
-
function checkRequires(requires, options) {
|
|
798
|
+
function checkRequires(requires, options, profile) {
|
|
782
799
|
for (const requirement of requires) {
|
|
783
800
|
switch (requirement) {
|
|
784
801
|
case 'controller': {
|
|
@@ -820,6 +837,31 @@ function checkRequires(requires, options) {
|
|
|
820
837
|
}
|
|
821
838
|
break;
|
|
822
839
|
}
|
|
840
|
+
case 'request_signer': {
|
|
841
|
+
// Gate is a no-op when no profile is threaded through (external
|
|
842
|
+
// `_client` mode). The caller has accepted responsibility for
|
|
843
|
+
// capability compatibility; failing the gate here would surprise
|
|
844
|
+
// them with a skip they can't explain from CLI options alone.
|
|
845
|
+
if (!profile?.raw_capabilities)
|
|
846
|
+
break;
|
|
847
|
+
const supported = resolveCapabilityPath(profile.raw_capabilities, 'request_signing.supported');
|
|
848
|
+
if (supported === true)
|
|
849
|
+
break;
|
|
850
|
+
return {
|
|
851
|
+
requirement,
|
|
852
|
+
detail: 'Storyboard requires `request_signing.supported: true` in `get_adcp_capabilities`; ' +
|
|
853
|
+
`agent declared ${supported === undefined ? 'no request_signing block' : JSON.stringify(supported)}. ` +
|
|
854
|
+
'Per compliance/{version}/universal/signed-requests.yaml: "Agents that do not advertise ' +
|
|
855
|
+
'support are not tested against this storyboard — absence of advertisement is not a ' +
|
|
856
|
+
'failure, it is a declaration that the agent does not offer verified signed requests." ' +
|
|
857
|
+
'To opt in, advertise `request_signing.supported: true` and pre-register the runner ' +
|
|
858
|
+
'compliance test keypair per test-kits/signed-requests-runner.yaml. ' +
|
|
859
|
+
'Forward-readiness: optional in AdCP 3.0; the schema (request_signing block description) ' +
|
|
860
|
+
'declares request signing required for spend-committing operations in AdCP 4.0. Sellers ' +
|
|
861
|
+
'that intend to support spend-committing tools SHOULD advertise the capability and ' +
|
|
862
|
+
'register the test keypair before the 4.0 cut to avoid a hard compliance failure then.',
|
|
863
|
+
};
|
|
864
|
+
}
|
|
823
865
|
}
|
|
824
866
|
}
|
|
825
867
|
return null;
|
|
@@ -854,23 +896,45 @@ function valueContainsWebhookToken(value) {
|
|
|
854
896
|
}
|
|
855
897
|
/**
|
|
856
898
|
* Return the implicit requirements a storyboard needs based on its
|
|
857
|
-
* structure (not its declared `requires:` list). Today:
|
|
858
|
-
* `'webhook_receiver'`, autodetected from `{{runner.webhook_url:…}}`
|
|
859
|
-
* `{{runner.webhook_base}}` token presence inside any step's
|
|
860
|
-
*
|
|
861
|
-
*
|
|
862
|
-
*
|
|
863
|
-
*
|
|
899
|
+
* structure (not its declared `requires:` list). Today:
|
|
900
|
+
* - `'webhook_receiver'`, autodetected from `{{runner.webhook_url:…}}`
|
|
901
|
+
* / `{{runner.webhook_base}}` token presence inside any step's
|
|
902
|
+
* `sample_request`. Token presence is the authoring contract — a
|
|
903
|
+
* storyboard that names the runner's webhook receiver cannot run
|
|
904
|
+
* without one — so authors don't need to remember to add
|
|
905
|
+
* `requires: [webhook_receiver]` separately. Spec: adcp-client#1678.
|
|
906
|
+
* - `'request_signer'`, autodetected from `storyboard.id ===
|
|
907
|
+
* 'signed_requests'` or any step using the synthesized
|
|
908
|
+
* `request_signing_probe` task. The signed-requests universal
|
|
909
|
+
* storyboard's own prerequisites section declares the
|
|
910
|
+
* `request_signing.supported: true` capability gate; the runner
|
|
911
|
+
* enforces it here so adopters don't see false-negative vector
|
|
912
|
+
* failures on bearer-only agents that never claimed signing.
|
|
913
|
+
* Spec: adcp-client#1702.
|
|
864
914
|
*/
|
|
865
915
|
function detectImplicitRequires(storyboard) {
|
|
916
|
+
const requires = [];
|
|
917
|
+
let needsWebhook = false;
|
|
918
|
+
let needsSigner = storyboard.id === 'signed_requests';
|
|
866
919
|
for (const phase of storyboard.phases) {
|
|
867
920
|
for (const step of phase.steps) {
|
|
868
|
-
if (step.sample_request && valueContainsWebhookToken(step.sample_request)) {
|
|
869
|
-
|
|
921
|
+
if (!needsWebhook && step.sample_request && valueContainsWebhookToken(step.sample_request)) {
|
|
922
|
+
needsWebhook = true;
|
|
870
923
|
}
|
|
924
|
+
if (!needsSigner && step.task === 'request_signing_probe') {
|
|
925
|
+
needsSigner = true;
|
|
926
|
+
}
|
|
927
|
+
if (needsWebhook && needsSigner)
|
|
928
|
+
break;
|
|
871
929
|
}
|
|
930
|
+
if (needsWebhook && needsSigner)
|
|
931
|
+
break;
|
|
872
932
|
}
|
|
873
|
-
|
|
933
|
+
if (needsWebhook)
|
|
934
|
+
requires.push('webhook_receiver');
|
|
935
|
+
if (needsSigner)
|
|
936
|
+
requires.push('request_signer');
|
|
937
|
+
return requires;
|
|
874
938
|
}
|
|
875
939
|
/**
|
|
876
940
|
* Build a hard-failure StoryboardResult for when agent capability
|
|
@@ -927,6 +991,7 @@ function buildDiscoveryFailedResult(agentUrls, storyboard, discoveryStep) {
|
|
|
927
991
|
strict_only_failures: 0,
|
|
928
992
|
lenient_also_failed: 0,
|
|
929
993
|
},
|
|
994
|
+
notices: [],
|
|
930
995
|
};
|
|
931
996
|
}
|
|
932
997
|
/**
|
|
@@ -984,8 +1049,85 @@ function buildRequiredToolsMissingResult(agentUrls, storyboard, detail) {
|
|
|
984
1049
|
strict_only_failures: 0,
|
|
985
1050
|
lenient_also_failed: 0,
|
|
986
1051
|
},
|
|
1052
|
+
notices: [],
|
|
987
1053
|
};
|
|
988
1054
|
}
|
|
1055
|
+
/**
|
|
1056
|
+
* Collect protocol-compliance notices for a storyboard run based on the
|
|
1057
|
+
* agent's declared capabilities. Returns an empty array when rawCaps is
|
|
1058
|
+
* absent (standalone runner without pre-fetched profile).
|
|
1059
|
+
*
|
|
1060
|
+
* Currently emits two spec-grounded notices (adcp-client#1704):
|
|
1061
|
+
*
|
|
1062
|
+
* - `request_signing.required`: `request_signing.supported` is absent or
|
|
1063
|
+
* false on the signed_requests storyboard. Signing becomes required for
|
|
1064
|
+
* spend-committing operations in `effective_version: '4.0'`.
|
|
1065
|
+
*
|
|
1066
|
+
* - `webhook_signing.legacy_hmac_fallback.removed`: agent claims
|
|
1067
|
+
* `webhook_signing.legacy_hmac_fallback: true`, which is removed in
|
|
1068
|
+
* `effective_version: '4.0'`.
|
|
1069
|
+
*
|
|
1070
|
+
* Note: a third notice (`signed_requests_specialism_deprecated`) is deferred
|
|
1071
|
+
* pending upstream deprecation of the `'signed-requests'` specialism value in
|
|
1072
|
+
* adcontextprotocol/adcp#4418 — the value is still active in the spec.
|
|
1073
|
+
*/
|
|
1074
|
+
function collectCapabilityNotices(storyboard, rawCaps) {
|
|
1075
|
+
const notices = [];
|
|
1076
|
+
if (!rawCaps || typeof rawCaps !== 'object')
|
|
1077
|
+
return notices;
|
|
1078
|
+
const caps = rawCaps;
|
|
1079
|
+
// Notice: request_signing required in AdCP 4.0.
|
|
1080
|
+
// Fired when this is the signed_requests storyboard and the agent hasn't
|
|
1081
|
+
// declared request_signing support. The storyboard is identified by id OR
|
|
1082
|
+
// by the presence of a request_signing_probe step (mirrors the implicit-
|
|
1083
|
+
// require detection in detectImplicitRequires for PR #1703's gate).
|
|
1084
|
+
const isSignedRequestsStoryboard = storyboard.id === 'signed_requests' ||
|
|
1085
|
+
storyboard.phases.some(p => p.steps.some(s => s.task === 'request_signing_probe'));
|
|
1086
|
+
if (isSignedRequestsStoryboard) {
|
|
1087
|
+
const requestSigning = caps['request_signing'];
|
|
1088
|
+
if (requestSigning?.['supported'] !== true) {
|
|
1089
|
+
notices.push({
|
|
1090
|
+
severity: 'future_required',
|
|
1091
|
+
code: 'request_signing.required',
|
|
1092
|
+
message: 'RFC 9421 request signing (`request_signing.supported: true`) is not advertised. ' +
|
|
1093
|
+
'Required for spend-committing operations in AdCP 4.0 — declare the capability and ' +
|
|
1094
|
+
'pre-register the runner compliance test keypair before the 4.0 cut.',
|
|
1095
|
+
effective_version: '4.0',
|
|
1096
|
+
capability_path: 'request_signing.supported',
|
|
1097
|
+
docs_url: 'https://adcontextprotocol.org/docs/building/implementation/security#signed-requests-transport-layer',
|
|
1098
|
+
storyboard_ids: [storyboard.id],
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
// Notice: legacy_hmac_fallback removed in AdCP 4.0.
|
|
1103
|
+
// Scoped via the WEBHOOK_STEP_TASKS step-task set rather than an id-regex.
|
|
1104
|
+
// Step-task presence is the authoring contract — a storyboard that asserts
|
|
1105
|
+
// webhook delivery uses one of these tasks. A storyboard id alone (e.g. a
|
|
1106
|
+
// hypothetical `webhook_authoring_guide`) shouldn't trigger the notice
|
|
1107
|
+
// unless it actually exercises the delivery path.
|
|
1108
|
+
const WEBHOOK_STEP_TASKS = new Set([
|
|
1109
|
+
'expect_webhook',
|
|
1110
|
+
'expect_webhook_retry_keys_stable',
|
|
1111
|
+
'expect_webhook_signature_valid',
|
|
1112
|
+
]);
|
|
1113
|
+
const isWebhookRelatedStoryboard = storyboard.phases.some(p => p.steps.some(s => WEBHOOK_STEP_TASKS.has(s.task)));
|
|
1114
|
+
if (isWebhookRelatedStoryboard) {
|
|
1115
|
+
const webhookSigning = caps['webhook_signing'];
|
|
1116
|
+
if (webhookSigning?.['legacy_hmac_fallback'] === true) {
|
|
1117
|
+
notices.push({
|
|
1118
|
+
severity: 'deprecation',
|
|
1119
|
+
code: 'webhook_signing.legacy_hmac_fallback.removed',
|
|
1120
|
+
message: '`webhook_signing.legacy_hmac_fallback: true` is deprecated and removed in AdCP 4.0. ' +
|
|
1121
|
+
'Migrate webhook signature verification to RFC 9421 before the 4.0 cut.',
|
|
1122
|
+
effective_version: '4.0',
|
|
1123
|
+
capability_path: 'webhook_signing.legacy_hmac_fallback',
|
|
1124
|
+
docs_url: 'https://adcontextprotocol.org/docs/building/implementation/webhooks',
|
|
1125
|
+
storyboard_ids: [storyboard.id],
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
return notices;
|
|
1130
|
+
}
|
|
989
1131
|
/**
|
|
990
1132
|
* Execute a single pass of the storyboard against the supplied replica URLs
|
|
991
1133
|
* using round-robin dispatch starting at `dispatchOffset`. Called directly
|
|
@@ -1108,15 +1250,24 @@ async function executeStoryboardPass(agentUrls, storyboard, options, dispatchOff
|
|
|
1108
1250
|
// implicit gate, storyboards that name the receiver but run without
|
|
1109
1251
|
// one would silently ship literal mustache tokens on the wire and
|
|
1110
1252
|
// get rejected by 3.0-strict sellers as `INVALID_REQUEST: relative URL`.
|
|
1253
|
+
// Pre-flight notice collection. Uses options._profile (set by the comply()
|
|
1254
|
+
// pipeline before calling runStoryboard) so the notices are available on
|
|
1255
|
+
// early-return results (requirement-unmet, capability-unsupported). In the
|
|
1256
|
+
// standalone runner path options._profile may be undefined; notices will be
|
|
1257
|
+
// collected again from the fully-fetched profile at result-build time.
|
|
1258
|
+
const preflightNotices = collectCapabilityNotices(storyboard, options._profile?.raw_capabilities);
|
|
1111
1259
|
const declared = storyboard.requires ?? [];
|
|
1112
1260
|
const implicit = detectImplicitRequires(storyboard);
|
|
1113
1261
|
const allRequires = [...declared, ...implicit.filter(r => !declared.includes(r))];
|
|
1114
1262
|
if (allRequires.length) {
|
|
1115
|
-
const unmet = checkRequires(allRequires, options);
|
|
1263
|
+
const unmet = checkRequires(allRequires, options, profile);
|
|
1116
1264
|
if (unmet) {
|
|
1117
1265
|
if (!options._client)
|
|
1118
1266
|
await (0, protocols_1.closeConnections)(options.protocol);
|
|
1119
|
-
return
|
|
1267
|
+
return {
|
|
1268
|
+
...buildRequirementUnmetResult(agentUrls, storyboard, unmet.requirement, unmet.detail),
|
|
1269
|
+
notices: preflightNotices,
|
|
1270
|
+
};
|
|
1120
1271
|
}
|
|
1121
1272
|
}
|
|
1122
1273
|
// Evaluate requires_capability predicate before any phase setup.
|
|
@@ -1142,7 +1293,7 @@ async function executeStoryboardPass(agentUrls, storyboard, options, dispatchOff
|
|
|
1142
1293
|
`agent declared ${JSON.stringify(actual)}.`;
|
|
1143
1294
|
if (!options._client)
|
|
1144
1295
|
await (0, protocols_1.closeConnections)(options.protocol);
|
|
1145
|
-
return buildCapabilityUnsupportedResult(agentUrls, storyboard, detail);
|
|
1296
|
+
return { ...buildCapabilityUnsupportedResult(agentUrls, storyboard, detail), notices: preflightNotices };
|
|
1146
1297
|
}
|
|
1147
1298
|
}
|
|
1148
1299
|
}
|
|
@@ -1162,7 +1313,10 @@ async function executeStoryboardPass(agentUrls, storyboard, options, dispatchOff
|
|
|
1162
1313
|
if (!hasAnyRequired) {
|
|
1163
1314
|
if (!options._client)
|
|
1164
1315
|
await (0, protocols_1.closeConnections)(options.protocol);
|
|
1165
|
-
return
|
|
1316
|
+
return {
|
|
1317
|
+
...buildRequiredToolsMissingResult(agentUrls, storyboard, `agent does not advertise any of [${storyboard.required_tools.join(', ')}]`),
|
|
1318
|
+
notices: preflightNotices,
|
|
1319
|
+
};
|
|
1166
1320
|
}
|
|
1167
1321
|
}
|
|
1168
1322
|
let context = { ...options.context };
|
|
@@ -1447,6 +1601,18 @@ async function executeStoryboardPass(agentUrls, storyboard, options, dispatchOff
|
|
|
1447
1601
|
});
|
|
1448
1602
|
continue;
|
|
1449
1603
|
}
|
|
1604
|
+
// Pre-empt OAuth-metadata probes when the agent's capabilities never
|
|
1605
|
+
// advertised OAuth at all (adcp-client#1702). Without this, an
|
|
1606
|
+
// API-key-only agent (e.g. Wonderstruck) has its PRM endpoint
|
|
1607
|
+
// probed; if the well-known path returns anything other than 404
|
|
1608
|
+
// (the existing reactive cascade trigger in `executeProbeStep`),
|
|
1609
|
+
// validations fail and `oauth_discovery` produces 5 false-negative
|
|
1610
|
+
// step failures even though the phase is `optional: true`. The skip
|
|
1611
|
+
// is phase-level — not storyboard-level — so the universal
|
|
1612
|
+
// `unauth_rejection` and `mechanism_required` phases still run.
|
|
1613
|
+
if (!phaseAbsent && phaseContainsOauthMetadataProbe(phase) && !agentAdvertisesOauth(profile)) {
|
|
1614
|
+
phaseAbsent = true;
|
|
1615
|
+
}
|
|
1450
1616
|
// Reset alias cache at this phase boundary (#1657). $generate:uuid_v4#alias
|
|
1451
1617
|
// is designed to be stable within a scenario — the initial call and its
|
|
1452
1618
|
// idempotency replay share the same UUID — but aliases must NOT bleed across
|
|
@@ -1650,6 +1816,20 @@ async function executeStoryboardPass(agentUrls, storyboard, options, dispatchOff
|
|
|
1650
1816
|
}
|
|
1651
1817
|
stepResults.push(result);
|
|
1652
1818
|
priorStepResults.set(step.id, result);
|
|
1819
|
+
// Schema-validation short-circuit (adcp-client#1709). When the
|
|
1820
|
+
// response unwrapper rejected the agent's response against the SDK's
|
|
1821
|
+
// Zod schema, the step-execution path synthesized a failing
|
|
1822
|
+
// `response_schema` ValidationResult on `result.validations`.
|
|
1823
|
+
// Running step-scope invariants against a response the SDK has
|
|
1824
|
+
// already declared malformed produces noise — the invariants
|
|
1825
|
+
// can't meaningfully grade a payload that didn't parse. Worse,
|
|
1826
|
+
// before #1709 the invariants' failure entries crowded out the
|
|
1827
|
+
// schema-validation entry in `extractFailures`, masking the root
|
|
1828
|
+
// cause across BidMachine's 10+ deploys (see adcp#4419). Skip the
|
|
1829
|
+
// invariant pass entirely on this step and emit a single skipped
|
|
1830
|
+
// `assertion` entry per invariant so consumers can still see WHICH
|
|
1831
|
+
// invariants were skipped and why.
|
|
1832
|
+
const schemaInvalidResponse = result.validations.some(v => v.check === 'response_schema' && !v.passed);
|
|
1653
1833
|
// Fire per-step assertions. Each result is appended to the step's
|
|
1654
1834
|
// `validations[]` under `check: "assertion"` so existing UI renders
|
|
1655
1835
|
// them alongside inline checks, and mirrored into `assertionResults`
|
|
@@ -1664,6 +1844,18 @@ async function executeStoryboardPass(agentUrls, storyboard, options, dispatchOff
|
|
|
1664
1844
|
// behavior the invariant would flag (validated at runner start).
|
|
1665
1845
|
if ((0, assertions_1.stepDisablesAssertion)(step.invariants, spec.id))
|
|
1666
1846
|
continue;
|
|
1847
|
+
if (schemaInvalidResponse) {
|
|
1848
|
+
// Emit a skipped marker so the report shows the invariant was
|
|
1849
|
+
// intentionally bypassed (not silently dropped). `passed: true`
|
|
1850
|
+
// keeps it from flipping `result.passed` — the schema-validation
|
|
1851
|
+
// failure already did that.
|
|
1852
|
+
result.validations.push({
|
|
1853
|
+
check: 'assertion',
|
|
1854
|
+
passed: true,
|
|
1855
|
+
description: `${spec.id}: skipped — response failed schema validation (adcp-client#1709)`,
|
|
1856
|
+
});
|
|
1857
|
+
continue;
|
|
1858
|
+
}
|
|
1667
1859
|
const raw = await spec.onStep(assertionContexts.get(spec.id), result);
|
|
1668
1860
|
for (const r of raw) {
|
|
1669
1861
|
const full = { ...r, assertion_id: spec.id, scope: 'step', step_id: step.id };
|
|
@@ -2025,6 +2217,10 @@ async function executeStoryboardPass(agentUrls, storyboard, options, dispatchOff
|
|
|
2025
2217
|
const schemasUsed = collectSchemasUsed(phaseResults);
|
|
2026
2218
|
const strictSummary = summarizeStrictValidation(phaseResults);
|
|
2027
2219
|
const validationsNotApplicable = countValidationsNotApplicable(phaseResults);
|
|
2220
|
+
// Use the fully-fetched profile for notice detection; fall back to pre-flight
|
|
2221
|
+
// notices (which used options._profile) when profile was not re-fetched in
|
|
2222
|
+
// this pass (standalone runner with options._profile pre-set skips the fetch).
|
|
2223
|
+
const notices = collectCapabilityNotices(storyboard, profile?.raw_capabilities ?? options._profile?.raw_capabilities);
|
|
2028
2224
|
const result = {
|
|
2029
2225
|
storyboard_id: storyboard.id,
|
|
2030
2226
|
storyboard_title: storyboard.title,
|
|
@@ -2047,6 +2243,7 @@ async function executeStoryboardPass(agentUrls, storyboard, options, dispatchOff
|
|
|
2047
2243
|
...(schemasUsed.length > 0 ? { schemas_used: schemasUsed } : {}),
|
|
2048
2244
|
...(assertionResults.length > 0 ? { assertions: assertionResults } : {}),
|
|
2049
2245
|
strict_validation_summary: strictSummary,
|
|
2246
|
+
notices,
|
|
2050
2247
|
};
|
|
2051
2248
|
// Close protocol connections when the runner created its own client. The
|
|
2052
2249
|
// connection pool is keyed by URL+auth, so a single closeConnections() call
|
|
@@ -2123,6 +2320,8 @@ async function runMultiPass(agentUrls, storyboard, options) {
|
|
|
2123
2320
|
// readers see a per-pass timeline; de-duplicating would hide a real
|
|
2124
2321
|
// "passed on pass 1, failed on pass 2" divergence.
|
|
2125
2322
|
const assertionsAgg = passResults.flatMap(r => r.assertions ?? []);
|
|
2323
|
+
// Notices are identical across passes (same agent, same capabilities); deduplicate by code.
|
|
2324
|
+
const noticesDedup = [...new Map(passResults.flatMap(r => r.notices).map(n => [n.code, n])).values()];
|
|
2126
2325
|
return {
|
|
2127
2326
|
storyboard_id: storyboard.id,
|
|
2128
2327
|
storyboard_title: storyboard.title,
|
|
@@ -2141,6 +2340,7 @@ async function runMultiPass(agentUrls, storyboard, options) {
|
|
|
2141
2340
|
tested_at: new Date().toISOString(),
|
|
2142
2341
|
...(schemasDedup.length > 0 ? { schemas_used: schemasDedup } : {}),
|
|
2143
2342
|
...(assertionsAgg.length > 0 ? { assertions: assertionsAgg } : {}),
|
|
2343
|
+
notices: noticesDedup,
|
|
2144
2344
|
};
|
|
2145
2345
|
}
|
|
2146
2346
|
/**
|
|
@@ -2614,6 +2814,11 @@ client, step, phaseId, context, allSteps, options, state) {
|
|
|
2614
2814
|
const useRawProbe = rawProbeHeaders !== undefined;
|
|
2615
2815
|
let taskResult;
|
|
2616
2816
|
let stepResult;
|
|
2817
|
+
// Raw caught error from the dispatch fn — preserved as `unknown` so the
|
|
2818
|
+
// schema-validation attribution path below can `instanceof` it against
|
|
2819
|
+
// `ResponseSchemaValidationError`. Undefined when the step succeeded or
|
|
2820
|
+
// failed for a non-typed reason. Spec: adcp-client#1709.
|
|
2821
|
+
let caughtError;
|
|
2617
2822
|
let httpResult;
|
|
2618
2823
|
let responseRecord;
|
|
2619
2824
|
let a2aEnvelope;
|
|
@@ -2827,6 +3032,7 @@ client, step, phaseId, context, allSteps, options, state) {
|
|
|
2827
3032
|
});
|
|
2828
3033
|
taskResult = run.result;
|
|
2829
3034
|
stepResult = run.step;
|
|
3035
|
+
caughtError = run.caughtError;
|
|
2830
3036
|
if (captureA2a && a2aCaptures) {
|
|
2831
3037
|
a2aEnvelope = parseLastA2aMessageSendCapture(a2aCaptures);
|
|
2832
3038
|
}
|
|
@@ -2893,10 +3099,10 @@ client, step, phaseId, context, allSteps, options, state) {
|
|
|
2893
3099
|
else {
|
|
2894
3100
|
passed = stepResult.passed && (taskResult?.success ?? false);
|
|
2895
3101
|
}
|
|
3102
|
+
let validations = [];
|
|
2896
3103
|
// Run validations. Resolve `$context.<key>` placeholders in `value` and
|
|
2897
3104
|
// `allowed_values` fields so expected values can reference prior steps
|
|
2898
3105
|
// (e.g., replay tests assert `media_buy_id === $context.initial_media_buy_id`).
|
|
2899
|
-
let validations = [];
|
|
2900
3106
|
if (step.validations?.length && (taskResult || httpResult)) {
|
|
2901
3107
|
const resolvedValidations = step.validations.map(v => {
|
|
2902
3108
|
const resolved = { ...v };
|
|
@@ -2989,6 +3195,41 @@ client, step, phaseId, context, allSteps, options, state) {
|
|
|
2989
3195
|
}
|
|
2990
3196
|
}
|
|
2991
3197
|
}
|
|
3198
|
+
// Schema-validation attribution (adcp-client#1709). When the response
|
|
3199
|
+
// unwrapper rejected the agent's response against the SDK's Zod schema
|
|
3200
|
+
// for the tool, it threw a typed `ResponseSchemaValidationError` that
|
|
3201
|
+
// surfaced on `caughtError`. Without this attribution, the rejection
|
|
3202
|
+
// would silently propagate into whichever step-scope invariant (e.g.
|
|
3203
|
+
// `context.no_secret_echo`) happened to fire next — the BidMachine
|
|
3204
|
+
// misdiagnosis trace from adcp-client#1709 / adcp#4419 ate 10+ deploys
|
|
3205
|
+
// chasing the wrong cause.
|
|
3206
|
+
//
|
|
3207
|
+
// Synthesize a canonical `response_schema` ValidationResult and prepend
|
|
3208
|
+
// it so `extractFailures.find(v => !v.passed)` resolves it before any
|
|
3209
|
+
// invariant entry. Step-scope invariants downstream of this point will
|
|
3210
|
+
// short-circuit on the schema-invalid response (see the invariant
|
|
3211
|
+
// dispatch loop in `executeStoryboardPass`).
|
|
3212
|
+
if (caughtError instanceof response_unwrapper_1.ResponseSchemaValidationError) {
|
|
3213
|
+
const issues = caughtError.issues
|
|
3214
|
+
.slice(0, 5)
|
|
3215
|
+
.map(i => `${i.path.join('.') || '(root)'}: ${i.message}`)
|
|
3216
|
+
.join('; ');
|
|
3217
|
+
const firstIssue = caughtError.issues[0];
|
|
3218
|
+
const jsonPointer = firstIssue ? '/' + firstIssue.path.map(s => String(s)).join('/') : null;
|
|
3219
|
+
const synthSchemaResult = {
|
|
3220
|
+
check: 'response_schema',
|
|
3221
|
+
passed: false,
|
|
3222
|
+
description: `Response schema validation for ${caughtError.toolName}`,
|
|
3223
|
+
error: issues,
|
|
3224
|
+
json_pointer: jsonPointer,
|
|
3225
|
+
expected: `response schema for ${caughtError.toolName}`,
|
|
3226
|
+
actual: caughtError.issues,
|
|
3227
|
+
};
|
|
3228
|
+
// Prepend so extractFailures picks it up before any inline validation
|
|
3229
|
+
// entry that may also be failing (e.g. `field_present` checks that
|
|
3230
|
+
// legitimately can't observe their target against an unparsed payload).
|
|
3231
|
+
validations = [synthSchemaResult, ...validations];
|
|
3232
|
+
}
|
|
2992
3233
|
// Persist the captured A2A envelope keyed by step id so cross-step
|
|
2993
3234
|
// validators (`a2a_context_continuity`) on subsequent steps can
|
|
2994
3235
|
// compare against it. Only fires when this step actually captured
|
|
@@ -3418,6 +3659,33 @@ function isTaskShape(result) {
|
|
|
3418
3659
|
// ────────────────────────────────────────────────────────────
|
|
3419
3660
|
// Phase / step skip predicates
|
|
3420
3661
|
// ────────────────────────────────────────────────────────────
|
|
3662
|
+
/**
|
|
3663
|
+
* True when the phase contains an OAuth-metadata probe step
|
|
3664
|
+
* (`protected_resource_metadata` or `oauth_auth_server_metadata`). Used
|
|
3665
|
+
* to pre-emptively trigger the existing `phaseAbsent` cascade when the
|
|
3666
|
+
* agent's capabilities never advertised OAuth in the first place, so we
|
|
3667
|
+
* don't burn step failures probing a well-known path the agent doesn't
|
|
3668
|
+
* claim. Spec: adcp-client#1702.
|
|
3669
|
+
*/
|
|
3670
|
+
function phaseContainsOauthMetadataProbe(phase) {
|
|
3671
|
+
return phase.steps.some(s => s.task === 'protected_resource_metadata' || s.task === 'oauth_auth_server_metadata');
|
|
3672
|
+
}
|
|
3673
|
+
/**
|
|
3674
|
+
* True when the agent's `get_adcp_capabilities` response declares an
|
|
3675
|
+
* OAuth issuer (today: `account.authorization_endpoint`). When the
|
|
3676
|
+
* profile or its capabilities aren't available — e.g. external
|
|
3677
|
+
* `_client` mode — we conservatively return `true` so the runner falls
|
|
3678
|
+
* through to the existing reactive cascade (`phaseAbsent` flips on a
|
|
3679
|
+
* 404 from the PRM probe). That keeps backward compatibility for
|
|
3680
|
+
* callers that haven't threaded a profile through. Spec: adcp-client#1702.
|
|
3681
|
+
*/
|
|
3682
|
+
function agentAdvertisesOauth(profile) {
|
|
3683
|
+
const rawCaps = profile?.raw_capabilities;
|
|
3684
|
+
if (rawCaps === undefined)
|
|
3685
|
+
return true;
|
|
3686
|
+
const endpoint = resolveCapabilityPath(rawCaps, 'account.authorization_endpoint');
|
|
3687
|
+
return typeof endpoint === 'string' && endpoint.length > 0;
|
|
3688
|
+
}
|
|
3421
3689
|
/**
|
|
3422
3690
|
* Evaluate a phase's `skip_if` expression against the runtime options. Only
|
|
3423
3691
|
* a tiny grammar is supported today; unknown expressions fail closed (phase runs).
|