@adcp/sdk 7.11.6 → 7.11.7
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.js +3 -1
- package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
- package/dist/lib/testing/client.d.ts +3 -5
- package/dist/lib/testing/client.d.ts.map +1 -1
- package/dist/lib/testing/client.js +3 -5
- package/dist/lib/testing/client.js.map +1 -1
- package/dist/lib/testing/compliance/comply.d.ts.map +1 -1
- package/dist/lib/testing/compliance/comply.js +337 -303
- package/dist/lib/testing/compliance/comply.js.map +1 -1
- package/dist/lib/testing/storyboard/runner.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/runner.js +4 -0
- package/dist/lib/testing/storyboard/runner.js.map +1 -1
- package/dist/lib/testing/storyboard/task-map.d.ts +1 -0
- package/dist/lib/testing/storyboard/task-map.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/task-map.js +23 -4
- package/dist/lib/testing/storyboard/task-map.js.map +1 -1
- package/dist/lib/version.d.ts +3 -3
- package/dist/lib/version.js +3 -3
- package/package.json +8 -3
|
@@ -866,328 +866,334 @@ async function complyImpl(agentUrl, options) {
|
|
|
866
866
|
}
|
|
867
867
|
// Fail fast on malformed test kits before we spin up any agent connection.
|
|
868
868
|
(0, test_kit_1.validateTestKit)(testOptions.test_kit);
|
|
869
|
-
//
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
const
|
|
874
|
-
|
|
875
|
-
|
|
869
|
+
// `signal` is hard external cancellation. `timeout_ms` is a soft comply()
|
|
870
|
+
// scheduling budget: once exceeded, stop starting new storyboards, but do
|
|
871
|
+
// not abort discovery or the storyboard currently in flight.
|
|
872
|
+
const signal = externalSignal;
|
|
873
|
+
const complianceIndex = (0, compliance_1.loadComplianceIndex)();
|
|
874
|
+
let effectiveOptions = (0, runner_1.applyAdcpVersionRunOptions)(complianceIndex.adcp_version, {
|
|
875
|
+
...testOptions,
|
|
876
|
+
sandbox: testOptions.sandbox !== false,
|
|
877
|
+
test_session_id: testOptions.test_session_id || `comply-${Date.now()}`,
|
|
878
|
+
});
|
|
879
|
+
// Check for abort before starting
|
|
880
|
+
signal?.throwIfAborted();
|
|
881
|
+
// Collect observations across all tracks
|
|
882
|
+
const allObservations = [];
|
|
883
|
+
// Discover agent capabilities once and share across all storyboards.
|
|
884
|
+
// External cancellation still aborts discovery; timeout_ms is enforced
|
|
885
|
+
// later as a soft storyboard-start budget.
|
|
886
|
+
const discoveryOptions = testOptions.versionEnvelope === undefined
|
|
887
|
+
? { ...effectiveOptions, versionEnvelope: 'major-only' }
|
|
888
|
+
: effectiveOptions;
|
|
889
|
+
const discoveryClient = (0, client_1.createTestClient)(agentUrl, effectiveOptions.protocol ?? 'mcp', discoveryOptions);
|
|
890
|
+
const { profile, step: profileStep } = await (0, client_1.discoverAgentProfile)(discoveryClient, signal);
|
|
891
|
+
effectiveOptions = applyNegotiatedComplianceVersionOptions(profile, effectiveOptions, {
|
|
892
|
+
complianceVersion: complianceIndex.adcp_version,
|
|
893
|
+
...(testOptions.adcpVersion !== undefined && { callerAdcpVersion: testOptions.adcpVersion }),
|
|
894
|
+
...(testOptions.versionEnvelope !== undefined && { callerVersionEnvelope: testOptions.versionEnvelope }),
|
|
895
|
+
});
|
|
896
|
+
const client = discoveryOptions === effectiveOptions
|
|
897
|
+
? discoveryClient
|
|
898
|
+
: (0, client_1.createTestClient)(agentUrl, effectiveOptions.protocol ?? 'mcp', effectiveOptions);
|
|
899
|
+
effectiveOptions._client = client;
|
|
900
|
+
effectiveOptions._profile = profile;
|
|
901
|
+
// Log discovered tools
|
|
902
|
+
if (profileStep.passed) {
|
|
903
|
+
allObservations.push({
|
|
904
|
+
category: 'tool_discovery',
|
|
905
|
+
severity: 'info',
|
|
906
|
+
message: `Discovered ${profile.tools.length} tools: [${profile.tools.join(', ')}]`,
|
|
907
|
+
evidence: { tools: profile.tools },
|
|
908
|
+
source: { kind: 'profile', code: 'tools-discovered' },
|
|
909
|
+
});
|
|
876
910
|
}
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
911
|
+
// Warn loudly when the runner can't make a capability-driven decision — either
|
|
912
|
+
// the agent doesn't advertise get_adcp_capabilities at all, or the call failed.
|
|
913
|
+
// Without this, an agent that just passes universal storyboards looks "compliant"
|
|
914
|
+
// when in fact none of its declared domains or specialisms were tested.
|
|
915
|
+
if (profileStep.passed && !explicitStoryboards?.length) {
|
|
916
|
+
if (profile.capabilities_probe_error) {
|
|
917
|
+
// The probe error text is agent-controlled. Fence it so downstream
|
|
918
|
+
// LLM summarizers of a shared ComplianceResult don't follow any
|
|
919
|
+
// instructions a hostile agent may have embedded. Raw text is kept
|
|
920
|
+
// in `evidence` for operator diagnosis — `evidence` is operator-only
|
|
921
|
+
// and MUST NOT be fed into an LLM summarizer.
|
|
922
|
+
allObservations.push({
|
|
923
|
+
category: 'tool_discovery',
|
|
924
|
+
severity: 'error',
|
|
925
|
+
message: `get_adcp_capabilities is advertised but the call failed. ` +
|
|
926
|
+
`Only universal storyboards ran — domain and specialism bundles were skipped. ` +
|
|
927
|
+
`Agent-reported error: ${fenceAgentText(profile.capabilities_probe_error)}`,
|
|
928
|
+
evidence: { agent_reported_error: profile.capabilities_probe_error },
|
|
929
|
+
source: { kind: 'profile', code: 'capabilities-probe-failed' },
|
|
930
|
+
});
|
|
880
931
|
}
|
|
881
|
-
else {
|
|
882
|
-
|
|
932
|
+
else if (!profile.tools.includes('get_adcp_capabilities')) {
|
|
933
|
+
allObservations.push({
|
|
934
|
+
category: 'tool_discovery',
|
|
935
|
+
severity: 'warning',
|
|
936
|
+
message: 'Agent does not implement get_adcp_capabilities — ran universal storyboards only. ' +
|
|
937
|
+
'Domain baselines and specialisms cannot be tested without a capabilities response.',
|
|
938
|
+
source: { kind: 'profile', code: 'capabilities-missing' },
|
|
939
|
+
});
|
|
883
940
|
}
|
|
884
|
-
|
|
885
|
-
const signal = abortController?.signal;
|
|
886
|
-
try {
|
|
887
|
-
const complianceIndex = (0, compliance_1.loadComplianceIndex)();
|
|
888
|
-
let effectiveOptions = (0, runner_1.applyAdcpVersionRunOptions)(complianceIndex.adcp_version, {
|
|
889
|
-
...testOptions,
|
|
890
|
-
sandbox: testOptions.sandbox !== false,
|
|
891
|
-
test_session_id: testOptions.test_session_id || `comply-${Date.now()}`,
|
|
892
|
-
});
|
|
893
|
-
// Check for abort before starting
|
|
894
|
-
signal?.throwIfAborted();
|
|
895
|
-
// Collect observations across all tracks
|
|
896
|
-
const allObservations = [];
|
|
897
|
-
// Discover agent capabilities once and share across all storyboards.
|
|
898
|
-
// Pass the combined signal so a slow/unresponsive agent can't hold the
|
|
899
|
-
// comply pipeline past its own timeout (adcp-client#1612).
|
|
900
|
-
const discoveryOptions = testOptions.versionEnvelope === undefined
|
|
901
|
-
? { ...effectiveOptions, versionEnvelope: 'major-only' }
|
|
902
|
-
: effectiveOptions;
|
|
903
|
-
const discoveryClient = (0, client_1.createTestClient)(agentUrl, effectiveOptions.protocol ?? 'mcp', discoveryOptions);
|
|
904
|
-
const { profile, step: profileStep } = await (0, client_1.discoverAgentProfile)(discoveryClient, signal);
|
|
905
|
-
effectiveOptions = applyNegotiatedComplianceVersionOptions(profile, effectiveOptions, {
|
|
906
|
-
complianceVersion: complianceIndex.adcp_version,
|
|
907
|
-
...(testOptions.adcpVersion !== undefined && { callerAdcpVersion: testOptions.adcpVersion }),
|
|
908
|
-
...(testOptions.versionEnvelope !== undefined && { callerVersionEnvelope: testOptions.versionEnvelope }),
|
|
909
|
-
});
|
|
910
|
-
const client = discoveryOptions === effectiveOptions
|
|
911
|
-
? discoveryClient
|
|
912
|
-
: (0, client_1.createTestClient)(agentUrl, effectiveOptions.protocol ?? 'mcp', effectiveOptions);
|
|
913
|
-
effectiveOptions._client = client;
|
|
914
|
-
effectiveOptions._profile = profile;
|
|
915
|
-
// Log discovered tools
|
|
916
|
-
if (profileStep.passed) {
|
|
941
|
+
else if (!profile.supported_protocols?.length) {
|
|
917
942
|
allObservations.push({
|
|
918
943
|
category: 'tool_discovery',
|
|
919
|
-
severity: '
|
|
920
|
-
message:
|
|
921
|
-
|
|
922
|
-
source: { kind: 'profile', code: '
|
|
944
|
+
severity: 'warning',
|
|
945
|
+
message: 'get_adcp_capabilities returned no supported_protocols — ran universal storyboards only. ' +
|
|
946
|
+
'Agent must declare at least one domain protocol to be fully tested.',
|
|
947
|
+
source: { kind: 'profile', code: 'no-supported-protocols' },
|
|
923
948
|
});
|
|
924
949
|
}
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
950
|
+
}
|
|
951
|
+
// Detect test controller for deterministic mode
|
|
952
|
+
let controllerDetection = { detected: false };
|
|
953
|
+
if (profileStep.passed && (0, test_controller_1.hasTestController)(profile)) {
|
|
954
|
+
controllerDetection = await (0, test_controller_1.detectController)(client, profile, effectiveOptions);
|
|
955
|
+
if (controllerDetection.detected) {
|
|
956
|
+
effectiveOptions._controllerCapabilities = controllerDetection;
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
if (!profileStep.passed) {
|
|
960
|
+
// Capability discovery failed. If it's an auth rejection, we can still
|
|
961
|
+
// run storyboards that don't need tool discovery — crucially
|
|
962
|
+
// universal/security_baseline, which is designed precisely to diagnose
|
|
963
|
+
// agents that mishandle auth. Fall back to the unreachable result only
|
|
964
|
+
// when no such storyboards are available.
|
|
965
|
+
const authCheck = await detectAuthRejection(agentUrl, profileStep.error, signal);
|
|
966
|
+
if (authCheck.isAuth) {
|
|
967
|
+
const degraded = { name: profile.name || 'Unknown (auth required)', tools: [] };
|
|
968
|
+
const candidate = explicitStoryboards?.length
|
|
969
|
+
? resolveExplicitStoryboards(explicitStoryboards)
|
|
970
|
+
: resolveFromCapabilities(degraded).storyboards;
|
|
971
|
+
const runnable = candidate.filter(sb => (sb.required_tools?.length ?? 0) === 0 || sb.track === 'security');
|
|
972
|
+
if (runnable.length > 0) {
|
|
973
|
+
allObservations.push(...authCheck.observations);
|
|
974
|
+
effectiveOptions._profile = degraded;
|
|
975
|
+
// Skip the rest of the "reachable" setup — no test controller, no
|
|
976
|
+
// capability-warning observations — and jump straight to storyboard
|
|
977
|
+
// execution below with the filtered, runnable subset.
|
|
978
|
+
return await runWithDegradedProfile(agentUrl, degraded, runnable, options, effectiveOptions, allObservations, start, signal);
|
|
954
979
|
}
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
980
|
+
}
|
|
981
|
+
return buildUnreachableResult(agentUrl, profile, profileStep.error, start, effectiveOptions, signal);
|
|
982
|
+
}
|
|
983
|
+
// Resolve storyboards: explicit IDs override capability-driven selection.
|
|
984
|
+
let initialStoryboards;
|
|
985
|
+
let notApplicable = [];
|
|
986
|
+
let missingToolStoryboards = [];
|
|
987
|
+
if (explicitStoryboards?.length) {
|
|
988
|
+
initialStoryboards = resolveExplicitStoryboards(explicitStoryboards);
|
|
989
|
+
}
|
|
990
|
+
else {
|
|
991
|
+
const resolved = resolveFromCapabilities(profile);
|
|
992
|
+
initialStoryboards = resolved.storyboards;
|
|
993
|
+
notApplicable = resolved.not_applicable;
|
|
994
|
+
}
|
|
995
|
+
const applicableStoryboards = expandScenarios(initialStoryboards);
|
|
996
|
+
// For capability-resolved runs, exclude storyboards and injected scenarios whose
|
|
997
|
+
// required_tools are absent from the agent's discovered toolset. These are
|
|
998
|
+
// not-applicable — the agent doesn't claim the specialism being tested. Running
|
|
999
|
+
// them produces cascading skips that pull the track to `partial`, which is a false
|
|
1000
|
+
// signal for AAO badge grading (adcp-client#1680).
|
|
1001
|
+
// Explicit storyboard IDs (options.storyboards) bypass this filter — they are an
|
|
1002
|
+
// operator override and should run regardless of required_tools.
|
|
1003
|
+
let runnableStoryboards;
|
|
1004
|
+
if (explicitStoryboards?.length) {
|
|
1005
|
+
runnableStoryboards = applicableStoryboards;
|
|
1006
|
+
}
|
|
1007
|
+
else {
|
|
1008
|
+
const discoveredToolNames = new Set(profile.tools);
|
|
1009
|
+
const filtered = [];
|
|
1010
|
+
for (const sb of applicableStoryboards) {
|
|
1011
|
+
const missing = (sb.required_tools ?? []).filter(t => !discoveredToolNames.has(t));
|
|
1012
|
+
if (missing.length > 0) {
|
|
1013
|
+
missingToolStoryboards.push({
|
|
1014
|
+
storyboard_id: sb.id,
|
|
1015
|
+
storyboard_title: sb.title,
|
|
1016
|
+
track: sb.track,
|
|
1017
|
+
reason: `missing required_tools: ${missing.join(', ')}`,
|
|
962
1018
|
});
|
|
963
1019
|
}
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
let controllerDetection = { detected: false };
|
|
967
|
-
if (profileStep.passed && (0, test_controller_1.hasTestController)(profile)) {
|
|
968
|
-
controllerDetection = await (0, test_controller_1.detectController)(client, profile, effectiveOptions);
|
|
969
|
-
if (controllerDetection.detected) {
|
|
970
|
-
effectiveOptions._controllerCapabilities = controllerDetection;
|
|
1020
|
+
else {
|
|
1021
|
+
filtered.push(sb);
|
|
971
1022
|
}
|
|
972
1023
|
}
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
// execution below with the filtered, runnable subset.
|
|
992
|
-
return await runWithDegradedProfile(agentUrl, degraded, runnable, options, effectiveOptions, allObservations, start, signal);
|
|
993
|
-
}
|
|
994
|
-
}
|
|
995
|
-
return buildUnreachableResult(agentUrl, profile, profileStep.error, start, effectiveOptions, signal);
|
|
1024
|
+
runnableStoryboards = filtered;
|
|
1025
|
+
}
|
|
1026
|
+
// Run storyboards
|
|
1027
|
+
const storyboardResults = [];
|
|
1028
|
+
const executedStoryboards = [];
|
|
1029
|
+
const runOptions = {
|
|
1030
|
+
...effectiveOptions,
|
|
1031
|
+
agentTools: profile.tools,
|
|
1032
|
+
...(webhook_receiver !== undefined && { webhook_receiver }),
|
|
1033
|
+
...(contracts !== undefined && { contracts }),
|
|
1034
|
+
...(signal !== undefined && { signal }),
|
|
1035
|
+
};
|
|
1036
|
+
let stoppedForTimeoutBudget = false;
|
|
1037
|
+
for (const sb of runnableStoryboards) {
|
|
1038
|
+
signal?.throwIfAborted();
|
|
1039
|
+
if (hasComplyTimeoutBudgetExpired(start, timeout_ms)) {
|
|
1040
|
+
stoppedForTimeoutBudget = true;
|
|
1041
|
+
break;
|
|
996
1042
|
}
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1043
|
+
const result = await (0, runner_1.runStoryboard)(agentUrl, sb, runOptions);
|
|
1044
|
+
storyboardResults.push(result);
|
|
1045
|
+
executedStoryboards.push(sb);
|
|
1046
|
+
}
|
|
1047
|
+
if (stoppedForTimeoutBudget) {
|
|
1048
|
+
allObservations.push(buildComplyTimeoutBudgetObservation(timeout_ms, storyboardResults.length, runnableStoryboards.length));
|
|
1049
|
+
}
|
|
1050
|
+
// Surface storyboards the agent's declared major version predates as a
|
|
1051
|
+
// distinct skip row. Not running them is correct (they didn't exist at
|
|
1052
|
+
// the spec the agent certified against), but hiding them risks silent
|
|
1053
|
+
// green builds against agents that haven't bumped their declared
|
|
1054
|
+
// major_versions.
|
|
1055
|
+
for (const na of [...notApplicable, ...missingToolStoryboards]) {
|
|
1056
|
+
storyboardResults.push(buildNotApplicableStoryboardResult(agentUrl, na));
|
|
1057
|
+
}
|
|
1058
|
+
// Cross-storyboard spec-conformance gates. Push synthetic StoryboardResults
|
|
1059
|
+
// for protocol-level invariants the AdCP spec mandates regardless of which
|
|
1060
|
+
// specialism is being tested. Currently wires the universal account-discovery
|
|
1061
|
+
// gate (adcp-client#1624 / adcp#4302; AdCP 3.0.9 §accounts/overview).
|
|
1062
|
+
// Will migrate to per-storyboard `required_any_of_tools` tags once
|
|
1063
|
+
// adcp#4325 lands; tracked in #1642.
|
|
1064
|
+
const accountDiscoveryFailure = (0, spec_conformance_1.checkAccountDiscoveryGate)(profile, agentUrl);
|
|
1065
|
+
if (accountDiscoveryFailure) {
|
|
1066
|
+
storyboardResults.push(accountDiscoveryFailure);
|
|
1067
|
+
}
|
|
1068
|
+
// Group results by track and build TrackResults
|
|
1069
|
+
const grouped = groupByTrack(storyboardResults, runnableStoryboards, [...notApplicable, ...missingToolStoryboards]);
|
|
1070
|
+
const trackResults = [];
|
|
1071
|
+
// Tracks represented by the selected storyboards (used for deciding which rows to emit).
|
|
1072
|
+
// Includes not-applicable entries so a version-gated track still gets a row.
|
|
1073
|
+
const poolTrackSet = new Set();
|
|
1074
|
+
for (const sb of runnableStoryboards) {
|
|
1075
|
+
if (sb.track)
|
|
1076
|
+
poolTrackSet.add(sb.track);
|
|
1077
|
+
}
|
|
1078
|
+
// Synthetic spec-conformance gates always land in `core`; ensure `core`
|
|
1079
|
+
// is in the pool so its track row renders even when the run targeted a
|
|
1080
|
+
// non-core specialism bundle that excluded universal storyboards.
|
|
1081
|
+
if (accountDiscoveryFailure)
|
|
1082
|
+
poolTrackSet.add('core');
|
|
1083
|
+
for (const na of [...notApplicable, ...missingToolStoryboards]) {
|
|
1084
|
+
if (na.track)
|
|
1085
|
+
poolTrackSet.add(na.track);
|
|
1086
|
+
}
|
|
1087
|
+
const trackFilterSet = trackFilter?.length ? new Set(trackFilter) : null;
|
|
1088
|
+
for (const track of TRACK_ORDER) {
|
|
1089
|
+
if (!poolTrackSet.has(track))
|
|
1090
|
+
continue;
|
|
1091
|
+
if (trackFilterSet && !trackFilterSet.has(track))
|
|
1092
|
+
continue;
|
|
1093
|
+
const results = grouped.get(track) ?? [];
|
|
1094
|
+
if (results.length > 0) {
|
|
1095
|
+
const trackResult = (0, storyboard_tracks_1.mapStoryboardResultsToTrackResult)(track, results, profile);
|
|
1096
|
+
const observations = collectObservations(track, trackResult.scenarios, profile);
|
|
1097
|
+
trackResult.observations = observations;
|
|
1098
|
+
allObservations.push(...observations);
|
|
1099
|
+
trackResults.push(trackResult);
|
|
1003
1100
|
}
|
|
1004
1101
|
else {
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
// signal for AAO badge grading (adcp-client#1680).
|
|
1015
|
-
// Explicit storyboard IDs (options.storyboards) bypass this filter — they are an
|
|
1016
|
-
// operator override and should run regardless of required_tools.
|
|
1017
|
-
let runnableStoryboards;
|
|
1018
|
-
if (explicitStoryboards?.length) {
|
|
1019
|
-
runnableStoryboards = applicableStoryboards;
|
|
1102
|
+
trackResults.push({
|
|
1103
|
+
track,
|
|
1104
|
+
status: 'skip',
|
|
1105
|
+
label: storyboard_tracks_1.TRACK_LABELS[track] || track,
|
|
1106
|
+
scenarios: [],
|
|
1107
|
+
skipped_scenarios: [],
|
|
1108
|
+
observations: [],
|
|
1109
|
+
duration_ms: 0,
|
|
1110
|
+
});
|
|
1020
1111
|
}
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1112
|
+
}
|
|
1113
|
+
const summary = buildSummary(trackResults, storyboardResults);
|
|
1114
|
+
// Tag `_view` so grep-style triage can distinguish the canonical
|
|
1115
|
+
// `tracks` entry from its appearance under the `tested_tracks` filter
|
|
1116
|
+
// (adcp-client#1674). Shallow-copy on the `tested_tracks` side keeps
|
|
1117
|
+
// the shared nested `scenarios` references intact while preventing
|
|
1118
|
+
// the marker from colliding on the same object.
|
|
1119
|
+
for (const t of trackResults)
|
|
1120
|
+
t._view = 'canonical';
|
|
1121
|
+
const testedTracks = trackResults
|
|
1122
|
+
.filter(t => t.status === 'pass' || t.status === 'fail' || t.status === 'partial' || t.status === 'silent')
|
|
1123
|
+
.map(t => ({ ...t, _view: 'reference' }));
|
|
1124
|
+
const skippedTracks = trackResults
|
|
1125
|
+
.filter(t => t.status === 'skip')
|
|
1126
|
+
.map(t => ({
|
|
1127
|
+
track: t.track,
|
|
1128
|
+
label: t.label,
|
|
1129
|
+
reason: 'No storyboards produced results for this track',
|
|
1130
|
+
}));
|
|
1131
|
+
const overallStatus = stoppedForTimeoutBudget ? 'partial' : computeOverallStatus(summary);
|
|
1132
|
+
const agentRef = options.agent_alias || agentUrl;
|
|
1133
|
+
const failures = extractFailures(storyboardResults, runnableStoryboards, agentRef);
|
|
1134
|
+
// Aggregate notices from all storyboard runs. Dedup is by `code` (each
|
|
1135
|
+
// notice type appears once in the rollup), but the per-occurrence
|
|
1136
|
+
// `storyboard_ids` arrays are merged so auditors can see how widespread
|
|
1137
|
+
// a deprecation or future-required signal is without re-walking the
|
|
1138
|
+
// per-storyboard arrays. Order is stable: first occurrence wins for
|
|
1139
|
+
// the notice body; storyboard_ids preserves insertion order across the
|
|
1140
|
+
// run's storyboard execution order.
|
|
1141
|
+
const aggregatedNotices = new Map();
|
|
1142
|
+
for (const sbResult of storyboardResults) {
|
|
1143
|
+
for (const notice of sbResult.notices) {
|
|
1144
|
+
const existing = aggregatedNotices.get(notice.code);
|
|
1145
|
+
if (existing) {
|
|
1146
|
+
for (const sid of notice.storyboard_ids) {
|
|
1147
|
+
if (!existing.storyboard_ids.includes(sid))
|
|
1148
|
+
existing.storyboard_ids.push(sid);
|
|
1036
1149
|
}
|
|
1037
1150
|
}
|
|
1038
|
-
runnableStoryboards = filtered;
|
|
1039
|
-
}
|
|
1040
|
-
// Run storyboards
|
|
1041
|
-
const storyboardResults = [];
|
|
1042
|
-
const runOptions = {
|
|
1043
|
-
...effectiveOptions,
|
|
1044
|
-
agentTools: profile.tools,
|
|
1045
|
-
...(webhook_receiver !== undefined && { webhook_receiver }),
|
|
1046
|
-
...(contracts !== undefined && { contracts }),
|
|
1047
|
-
...(signal !== undefined && { signal }),
|
|
1048
|
-
};
|
|
1049
|
-
for (const sb of runnableStoryboards) {
|
|
1050
|
-
signal?.throwIfAborted();
|
|
1051
|
-
const result = await (0, runner_1.runStoryboard)(agentUrl, sb, runOptions);
|
|
1052
|
-
storyboardResults.push(result);
|
|
1053
|
-
}
|
|
1054
|
-
// Surface storyboards the agent's declared major version predates as a
|
|
1055
|
-
// distinct skip row. Not running them is correct (they didn't exist at
|
|
1056
|
-
// the spec the agent certified against), but hiding them risks silent
|
|
1057
|
-
// green builds against agents that haven't bumped their declared
|
|
1058
|
-
// major_versions.
|
|
1059
|
-
for (const na of [...notApplicable, ...missingToolStoryboards]) {
|
|
1060
|
-
storyboardResults.push(buildNotApplicableStoryboardResult(agentUrl, na));
|
|
1061
|
-
}
|
|
1062
|
-
// Cross-storyboard spec-conformance gates. Push synthetic StoryboardResults
|
|
1063
|
-
// for protocol-level invariants the AdCP spec mandates regardless of which
|
|
1064
|
-
// specialism is being tested. Currently wires the universal account-discovery
|
|
1065
|
-
// gate (adcp-client#1624 / adcp#4302; AdCP 3.0.9 §accounts/overview).
|
|
1066
|
-
// Will migrate to per-storyboard `required_any_of_tools` tags once
|
|
1067
|
-
// adcp#4325 lands; tracked in #1642.
|
|
1068
|
-
const accountDiscoveryFailure = (0, spec_conformance_1.checkAccountDiscoveryGate)(profile, agentUrl);
|
|
1069
|
-
if (accountDiscoveryFailure) {
|
|
1070
|
-
storyboardResults.push(accountDiscoveryFailure);
|
|
1071
|
-
}
|
|
1072
|
-
// Group results by track and build TrackResults
|
|
1073
|
-
const grouped = groupByTrack(storyboardResults, runnableStoryboards, [...notApplicable, ...missingToolStoryboards]);
|
|
1074
|
-
const trackResults = [];
|
|
1075
|
-
// Tracks represented by the selected storyboards (used for deciding which rows to emit).
|
|
1076
|
-
// Includes not-applicable entries so a version-gated track still gets a row.
|
|
1077
|
-
const poolTrackSet = new Set();
|
|
1078
|
-
for (const sb of runnableStoryboards) {
|
|
1079
|
-
if (sb.track)
|
|
1080
|
-
poolTrackSet.add(sb.track);
|
|
1081
|
-
}
|
|
1082
|
-
// Synthetic spec-conformance gates always land in `core`; ensure `core`
|
|
1083
|
-
// is in the pool so its track row renders even when the run targeted a
|
|
1084
|
-
// non-core specialism bundle that excluded universal storyboards.
|
|
1085
|
-
if (accountDiscoveryFailure)
|
|
1086
|
-
poolTrackSet.add('core');
|
|
1087
|
-
for (const na of [...notApplicable, ...missingToolStoryboards]) {
|
|
1088
|
-
if (na.track)
|
|
1089
|
-
poolTrackSet.add(na.track);
|
|
1090
|
-
}
|
|
1091
|
-
const trackFilterSet = trackFilter?.length ? new Set(trackFilter) : null;
|
|
1092
|
-
for (const track of TRACK_ORDER) {
|
|
1093
|
-
if (!poolTrackSet.has(track))
|
|
1094
|
-
continue;
|
|
1095
|
-
if (trackFilterSet && !trackFilterSet.has(track))
|
|
1096
|
-
continue;
|
|
1097
|
-
const results = grouped.get(track) ?? [];
|
|
1098
|
-
if (results.length > 0) {
|
|
1099
|
-
const trackResult = (0, storyboard_tracks_1.mapStoryboardResultsToTrackResult)(track, results, profile);
|
|
1100
|
-
const observations = collectObservations(track, trackResult.scenarios, profile);
|
|
1101
|
-
trackResult.observations = observations;
|
|
1102
|
-
allObservations.push(...observations);
|
|
1103
|
-
trackResults.push(trackResult);
|
|
1104
|
-
}
|
|
1105
1151
|
else {
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
status: 'skip',
|
|
1109
|
-
label: storyboard_tracks_1.TRACK_LABELS[track] || track,
|
|
1110
|
-
scenarios: [],
|
|
1111
|
-
skipped_scenarios: [],
|
|
1112
|
-
observations: [],
|
|
1113
|
-
duration_ms: 0,
|
|
1114
|
-
});
|
|
1152
|
+
// Clone so subsequent merges don't mutate the per-storyboard array.
|
|
1153
|
+
aggregatedNotices.set(notice.code, { ...notice, storyboard_ids: [...notice.storyboard_ids] });
|
|
1115
1154
|
}
|
|
1116
1155
|
}
|
|
1117
|
-
const summary = buildSummary(trackResults, storyboardResults);
|
|
1118
|
-
// Tag `_view` so grep-style triage can distinguish the canonical
|
|
1119
|
-
// `tracks` entry from its appearance under the `tested_tracks` filter
|
|
1120
|
-
// (adcp-client#1674). Shallow-copy on the `tested_tracks` side keeps
|
|
1121
|
-
// the shared nested `scenarios` references intact while preventing
|
|
1122
|
-
// the marker from colliding on the same object.
|
|
1123
|
-
for (const t of trackResults)
|
|
1124
|
-
t._view = 'canonical';
|
|
1125
|
-
const testedTracks = trackResults
|
|
1126
|
-
.filter(t => t.status === 'pass' || t.status === 'fail' || t.status === 'partial' || t.status === 'silent')
|
|
1127
|
-
.map(t => ({ ...t, _view: 'reference' }));
|
|
1128
|
-
const skippedTracks = trackResults
|
|
1129
|
-
.filter(t => t.status === 'skip')
|
|
1130
|
-
.map(t => ({
|
|
1131
|
-
track: t.track,
|
|
1132
|
-
label: t.label,
|
|
1133
|
-
reason: 'No storyboards produced results for this track',
|
|
1134
|
-
}));
|
|
1135
|
-
const overallStatus = computeOverallStatus(summary);
|
|
1136
|
-
const agentRef = options.agent_alias || agentUrl;
|
|
1137
|
-
const failures = extractFailures(storyboardResults, runnableStoryboards, agentRef);
|
|
1138
|
-
// Aggregate notices from all storyboard runs. Dedup is by `code` (each
|
|
1139
|
-
// notice type appears once in the rollup), but the per-occurrence
|
|
1140
|
-
// `storyboard_ids` arrays are merged so auditors can see how widespread
|
|
1141
|
-
// a deprecation or future-required signal is without re-walking the
|
|
1142
|
-
// per-storyboard arrays. Order is stable: first occurrence wins for
|
|
1143
|
-
// the notice body; storyboard_ids preserves insertion order across the
|
|
1144
|
-
// run's storyboard execution order.
|
|
1145
|
-
const aggregatedNotices = new Map();
|
|
1146
|
-
for (const sbResult of storyboardResults) {
|
|
1147
|
-
for (const notice of sbResult.notices) {
|
|
1148
|
-
const existing = aggregatedNotices.get(notice.code);
|
|
1149
|
-
if (existing) {
|
|
1150
|
-
for (const sid of notice.storyboard_ids) {
|
|
1151
|
-
if (!existing.storyboard_ids.includes(sid))
|
|
1152
|
-
existing.storyboard_ids.push(sid);
|
|
1153
|
-
}
|
|
1154
|
-
}
|
|
1155
|
-
else {
|
|
1156
|
-
// Clone so subsequent merges don't mutate the per-storyboard array.
|
|
1157
|
-
aggregatedNotices.set(notice.code, { ...notice, storyboard_ids: [...notice.storyboard_ids] });
|
|
1158
|
-
}
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
const noticesDedup = [...aggregatedNotices.values()];
|
|
1162
|
-
return {
|
|
1163
|
-
agent_url: agentUrl,
|
|
1164
|
-
agent_profile: profile,
|
|
1165
|
-
overall_status: overallStatus,
|
|
1166
|
-
tracks: trackResults,
|
|
1167
|
-
tested_tracks: testedTracks,
|
|
1168
|
-
skipped_tracks: skippedTracks,
|
|
1169
|
-
summary,
|
|
1170
|
-
observations: allObservations,
|
|
1171
|
-
failures: failures.length > 0 ? failures : undefined,
|
|
1172
|
-
storyboards_executed: runnableStoryboards.map(sb => sb.id),
|
|
1173
|
-
...(notApplicable.length > 0 && { storyboards_not_applicable: notApplicable.map(na => na.storyboard_id) }),
|
|
1174
|
-
...(missingToolStoryboards.length > 0 && {
|
|
1175
|
-
storyboards_missing_tools: missingToolStoryboards.map(na => na.storyboard_id),
|
|
1176
|
-
}),
|
|
1177
|
-
controller_detected: controllerDetection.detected,
|
|
1178
|
-
controller_scenarios: controllerDetection.detected ? controllerDetection.scenarios : undefined,
|
|
1179
|
-
tested_at: new Date().toISOString(),
|
|
1180
|
-
total_duration_ms: Date.now() - start,
|
|
1181
|
-
notices: noticesDedup,
|
|
1182
|
-
};
|
|
1183
|
-
}
|
|
1184
|
-
finally {
|
|
1185
|
-
if (timeoutId !== undefined)
|
|
1186
|
-
clearTimeout(timeoutId);
|
|
1187
|
-
if (onExternalAbort && externalSignal) {
|
|
1188
|
-
externalSignal.removeEventListener('abort', onExternalAbort);
|
|
1189
|
-
}
|
|
1190
1156
|
}
|
|
1157
|
+
const noticesDedup = [...aggregatedNotices.values()];
|
|
1158
|
+
return {
|
|
1159
|
+
agent_url: agentUrl,
|
|
1160
|
+
agent_profile: profile,
|
|
1161
|
+
overall_status: overallStatus,
|
|
1162
|
+
tracks: trackResults,
|
|
1163
|
+
tested_tracks: testedTracks,
|
|
1164
|
+
skipped_tracks: skippedTracks,
|
|
1165
|
+
summary,
|
|
1166
|
+
observations: allObservations,
|
|
1167
|
+
failures: failures.length > 0 ? failures : undefined,
|
|
1168
|
+
storyboards_executed: executedStoryboards.map(sb => sb.id),
|
|
1169
|
+
...(notApplicable.length > 0 && { storyboards_not_applicable: notApplicable.map(na => na.storyboard_id) }),
|
|
1170
|
+
...(missingToolStoryboards.length > 0 && {
|
|
1171
|
+
storyboards_missing_tools: missingToolStoryboards.map(na => na.storyboard_id),
|
|
1172
|
+
}),
|
|
1173
|
+
controller_detected: controllerDetection.detected,
|
|
1174
|
+
controller_scenarios: controllerDetection.detected ? controllerDetection.scenarios : undefined,
|
|
1175
|
+
tested_at: new Date().toISOString(),
|
|
1176
|
+
total_duration_ms: Date.now() - start,
|
|
1177
|
+
notices: noticesDedup,
|
|
1178
|
+
};
|
|
1179
|
+
}
|
|
1180
|
+
function hasComplyTimeoutBudgetExpired(start, timeout_ms, now = Date.now()) {
|
|
1181
|
+
return timeout_ms !== undefined && now - start >= timeout_ms;
|
|
1182
|
+
}
|
|
1183
|
+
function buildComplyTimeoutBudgetObservation(timeout_ms, storyboardsExecuted, storyboardsSelected) {
|
|
1184
|
+
return {
|
|
1185
|
+
category: 'performance',
|
|
1186
|
+
severity: 'warning',
|
|
1187
|
+
message: `Compliance timeout budget of ${timeout_ms}ms was reached. ` +
|
|
1188
|
+
`Stopped starting new storyboards after ${storyboardsExecuted}/${storyboardsSelected} selected storyboard(s).`,
|
|
1189
|
+
evidence: {
|
|
1190
|
+
timeout_ms,
|
|
1191
|
+
storyboards_executed: storyboardsExecuted,
|
|
1192
|
+
storyboards_selected: storyboardsSelected,
|
|
1193
|
+
storyboards_remaining: Math.max(0, storyboardsSelected - storyboardsExecuted),
|
|
1194
|
+
},
|
|
1195
|
+
source: { kind: 'profile', code: 'timeout-budget-exceeded' },
|
|
1196
|
+
};
|
|
1191
1197
|
}
|
|
1192
1198
|
/**
|
|
1193
1199
|
* Detect whether a capability-discovery failure is an auth rejection.
|
|
@@ -1302,10 +1308,20 @@ async function runWithDegradedProfile(agentUrl, profile, storyboards, options, e
|
|
|
1302
1308
|
...(options.contracts !== undefined && { contracts: options.contracts }),
|
|
1303
1309
|
...(signal !== undefined && { signal }),
|
|
1304
1310
|
};
|
|
1311
|
+
let stoppedForTimeoutBudget = false;
|
|
1312
|
+
const executedStoryboards = [];
|
|
1305
1313
|
for (const sb of storyboards) {
|
|
1306
1314
|
signal?.throwIfAborted();
|
|
1315
|
+
if (hasComplyTimeoutBudgetExpired(start, options.timeout_ms)) {
|
|
1316
|
+
stoppedForTimeoutBudget = true;
|
|
1317
|
+
break;
|
|
1318
|
+
}
|
|
1307
1319
|
const result = await (0, runner_1.runStoryboard)(agentUrl, sb, runOptions);
|
|
1308
1320
|
storyboardResults.push(result);
|
|
1321
|
+
executedStoryboards.push(sb);
|
|
1322
|
+
}
|
|
1323
|
+
if (stoppedForTimeoutBudget && options.timeout_ms !== undefined) {
|
|
1324
|
+
allObservations.push(buildComplyTimeoutBudgetObservation(options.timeout_ms, storyboardResults.length, storyboards.length));
|
|
1309
1325
|
}
|
|
1310
1326
|
const grouped = groupByTrack(storyboardResults, storyboards);
|
|
1311
1327
|
const trackResults = [];
|
|
@@ -1324,9 +1340,20 @@ async function runWithDegradedProfile(agentUrl, profile, storyboards, options, e
|
|
|
1324
1340
|
allObservations.push(...obs);
|
|
1325
1341
|
trackResults.push(trackResult);
|
|
1326
1342
|
}
|
|
1343
|
+
else {
|
|
1344
|
+
trackResults.push({
|
|
1345
|
+
track,
|
|
1346
|
+
status: 'skip',
|
|
1347
|
+
label: storyboard_tracks_1.TRACK_LABELS[track] || track,
|
|
1348
|
+
scenarios: [],
|
|
1349
|
+
skipped_scenarios: [],
|
|
1350
|
+
observations: [],
|
|
1351
|
+
duration_ms: 0,
|
|
1352
|
+
});
|
|
1353
|
+
}
|
|
1327
1354
|
}
|
|
1328
1355
|
const summary = buildSummary(trackResults, storyboardResults);
|
|
1329
|
-
const overallStatus = computeOverallStatus(summary);
|
|
1356
|
+
const overallStatus = stoppedForTimeoutBudget ? 'partial' : computeOverallStatus(summary);
|
|
1330
1357
|
const agentRef = options.agent_alias || agentUrl;
|
|
1331
1358
|
const failures = extractFailures(storyboardResults, storyboards, agentRef);
|
|
1332
1359
|
// Tag canonical vs reference views to disambiguate the same
|
|
@@ -1334,6 +1361,13 @@ async function runWithDegradedProfile(agentUrl, profile, storyboards, options, e
|
|
|
1334
1361
|
// (adcp-client#1674).
|
|
1335
1362
|
for (const t of trackResults)
|
|
1336
1363
|
t._view = 'canonical';
|
|
1364
|
+
const skippedTracks = trackResults
|
|
1365
|
+
.filter(t => t.status === 'skip')
|
|
1366
|
+
.map(t => ({
|
|
1367
|
+
track: t.track,
|
|
1368
|
+
label: t.label,
|
|
1369
|
+
reason: 'No storyboards produced results for this track',
|
|
1370
|
+
}));
|
|
1337
1371
|
return {
|
|
1338
1372
|
agent_url: agentUrl,
|
|
1339
1373
|
agent_profile: profile,
|
|
@@ -1342,11 +1376,11 @@ async function runWithDegradedProfile(agentUrl, profile, storyboards, options, e
|
|
|
1342
1376
|
tested_tracks: trackResults
|
|
1343
1377
|
.filter(t => t.status === 'pass' || t.status === 'fail' || t.status === 'partial' || t.status === 'silent')
|
|
1344
1378
|
.map(t => ({ ...t, _view: 'reference' })),
|
|
1345
|
-
skipped_tracks:
|
|
1379
|
+
skipped_tracks: skippedTracks,
|
|
1346
1380
|
summary,
|
|
1347
1381
|
observations: allObservations,
|
|
1348
1382
|
failures: failures.length > 0 ? failures : undefined,
|
|
1349
|
-
storyboards_executed:
|
|
1383
|
+
storyboards_executed: executedStoryboards.map(sb => sb.id),
|
|
1350
1384
|
controller_detected: false,
|
|
1351
1385
|
tested_at: new Date().toISOString(),
|
|
1352
1386
|
total_duration_ms: Date.now() - start,
|