@adcp/client 4.22.0 → 4.23.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/dist/lib/index.d.ts +2 -2
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/server/index.d.ts +1 -1
- package/dist/lib/server/index.d.ts.map +1 -1
- package/dist/lib/server/serve.d.ts +32 -4
- package/dist/lib/server/serve.d.ts.map +1 -1
- package/dist/lib/server/serve.js +12 -4
- package/dist/lib/server/serve.js.map +1 -1
- package/dist/lib/testing/compliance/comply.d.ts +22 -5
- package/dist/lib/testing/compliance/comply.d.ts.map +1 -1
- package/dist/lib/testing/compliance/comply.js +242 -276
- package/dist/lib/testing/compliance/comply.js.map +1 -1
- package/dist/lib/testing/compliance/index.d.ts +1 -0
- package/dist/lib/testing/compliance/index.d.ts.map +1 -1
- package/dist/lib/testing/compliance/index.js +6 -1
- package/dist/lib/testing/compliance/index.js.map +1 -1
- package/dist/lib/testing/compliance/platform-storyboards.d.ts +44 -0
- package/dist/lib/testing/compliance/platform-storyboards.d.ts.map +1 -0
- package/dist/lib/testing/compliance/platform-storyboards.js +230 -0
- package/dist/lib/testing/compliance/platform-storyboards.js.map +1 -0
- package/dist/lib/testing/compliance/storyboard-tracks.d.ts +2 -9
- package/dist/lib/testing/compliance/storyboard-tracks.d.ts.map +1 -1
- package/dist/lib/testing/compliance/storyboard-tracks.js +4 -44
- package/dist/lib/testing/compliance/storyboard-tracks.js.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 +6 -1
- package/dist/lib/testing/index.js.map +1 -1
- package/dist/lib/types/core.generated.d.ts +241 -2
- package/dist/lib/types/core.generated.d.ts.map +1 -1
- package/dist/lib/types/core.generated.js +1 -1
- package/dist/lib/types/schemas.generated.d.ts +3380 -3154
- package/dist/lib/types/schemas.generated.d.ts.map +1 -1
- package/dist/lib/types/schemas.generated.js +220 -115
- package/dist/lib/types/schemas.generated.js.map +1 -1
- package/dist/lib/types/tools.generated.d.ts +267 -74
- package/dist/lib/types/tools.generated.d.ts.map +1 -1
- package/dist/lib/version.d.ts +3 -3
- package/dist/lib/version.js +3 -3
- package/docs/guides/BUILD-AN-AGENT.md +5 -3
- package/docs/llms.txt +48 -32
- package/examples/signals-agent.ts +3 -2
- package/package.json +1 -1
- package/storyboards/audience_sync.yaml +18 -29
- package/storyboards/behavioral_analysis.yaml +40 -72
- package/storyboards/brand_rights.yaml +172 -75
- package/storyboards/campaign_governance_conditions.yaml +187 -0
- package/storyboards/campaign_governance_delivery.yaml +231 -0
- package/storyboards/campaign_governance_denied.yaml +135 -0
- package/storyboards/capability_discovery.yaml +106 -0
- package/storyboards/content_standards.yaml +251 -0
- package/storyboards/creative_ad_server.yaml +108 -16
- package/storyboards/creative_lifecycle.yaml +284 -0
- package/storyboards/creative_sales_agent.yaml +2 -6
- package/storyboards/creative_template.yaml +1 -5
- package/storyboards/error_compliance.yaml +105 -108
- package/storyboards/media_buy_catalog_creative.yaml +6 -4
- package/storyboards/media_buy_governance_escalation.yaml +9 -5
- package/storyboards/media_buy_guaranteed_approval.yaml +9 -7
- package/storyboards/media_buy_non_guaranteed.yaml +7 -6
- package/storyboards/media_buy_proposal_mode.yaml +9 -8
- package/storyboards/media_buy_seller.yaml +153 -165
- package/storyboards/media_buy_state_machine.yaml +100 -99
- package/storyboards/property_governance.yaml +239 -0
- package/storyboards/schema.yaml +2 -2
- package/storyboards/schema_validation.yaml +58 -51
- package/storyboards/si_session.yaml +99 -317
- package/storyboards/signal_marketplace.yaml +6 -5
- package/storyboards/signal_owned.yaml +5 -5
- package/storyboards/social_platform.yaml +274 -0
- package/storyboards/governance_content_standards.yaml +0 -213
- package/storyboards/governance_property_lists.yaml +0 -372
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Compliance Engine
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Storyboard-driven compliance assessment. Storyboards are the routing
|
|
6
|
+
* mechanism; tracks are a reporting layer derived from storyboard results.
|
|
7
|
+
*
|
|
8
|
+
* Resolution priority: storyboards > platform_type > all applicable.
|
|
7
9
|
*/
|
|
8
10
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
11
|
if (k2 === undefined) k2 = k;
|
|
@@ -45,98 +47,15 @@ exports.formatComplianceResults = formatComplianceResults;
|
|
|
45
47
|
exports.formatComplianceResultsJSON = formatComplianceResultsJSON;
|
|
46
48
|
const client_1 = require("../client");
|
|
47
49
|
const storyboard_tracks_1 = require("./storyboard-tracks");
|
|
50
|
+
const runner_1 = require("../storyboard/runner");
|
|
51
|
+
const loader_1 = require("../storyboard/loader");
|
|
52
|
+
const platform_storyboards_1 = require("./platform-storyboards");
|
|
48
53
|
const profiles_1 = require("./profiles");
|
|
49
54
|
const mcp_1 = require("../../protocols/mcp");
|
|
50
55
|
const test_controller_1 = require("../test-controller");
|
|
51
56
|
/**
|
|
52
|
-
*
|
|
53
|
-
*/
|
|
54
|
-
const TRACK_DEFINITIONS = {
|
|
55
|
-
core: {
|
|
56
|
-
label: 'Core Protocol',
|
|
57
|
-
scenarios: [
|
|
58
|
-
'health_check',
|
|
59
|
-
'discovery',
|
|
60
|
-
'capability_discovery',
|
|
61
|
-
'schema_compliance',
|
|
62
|
-
'controller_validation',
|
|
63
|
-
'deterministic_account',
|
|
64
|
-
],
|
|
65
|
-
},
|
|
66
|
-
products: {
|
|
67
|
-
label: 'Product Discovery',
|
|
68
|
-
scenarios: ['pricing_edge_cases', 'behavior_analysis', 'response_consistency'],
|
|
69
|
-
},
|
|
70
|
-
media_buy: {
|
|
71
|
-
label: 'Media Buy Lifecycle',
|
|
72
|
-
scenarios: [
|
|
73
|
-
'create_media_buy',
|
|
74
|
-
'full_sales_flow',
|
|
75
|
-
'creative_inline',
|
|
76
|
-
'temporal_validation',
|
|
77
|
-
'media_buy_lifecycle',
|
|
78
|
-
'terminal_state_enforcement',
|
|
79
|
-
'package_lifecycle',
|
|
80
|
-
'seller_governance_context',
|
|
81
|
-
'deterministic_media_buy',
|
|
82
|
-
'deterministic_budget',
|
|
83
|
-
],
|
|
84
|
-
},
|
|
85
|
-
creative: {
|
|
86
|
-
label: 'Creative Management',
|
|
87
|
-
scenarios: ['creative_sync', 'creative_flow', 'deterministic_creative'],
|
|
88
|
-
},
|
|
89
|
-
reporting: {
|
|
90
|
-
label: 'Reporting',
|
|
91
|
-
scenarios: ['reporting_flow', 'deterministic_delivery'],
|
|
92
|
-
},
|
|
93
|
-
governance: {
|
|
94
|
-
label: 'Governance',
|
|
95
|
-
scenarios: ['governance_property_lists', 'governance_content_standards', 'property_list_filters'],
|
|
96
|
-
},
|
|
97
|
-
campaign_governance: {
|
|
98
|
-
label: 'Campaign Governance',
|
|
99
|
-
scenarios: [
|
|
100
|
-
'campaign_governance',
|
|
101
|
-
'campaign_governance_denied',
|
|
102
|
-
'campaign_governance_conditions',
|
|
103
|
-
'campaign_governance_delivery',
|
|
104
|
-
],
|
|
105
|
-
},
|
|
106
|
-
signals: {
|
|
107
|
-
label: 'Signals',
|
|
108
|
-
scenarios: ['signals_flow'],
|
|
109
|
-
},
|
|
110
|
-
si: {
|
|
111
|
-
label: 'Sponsored Intelligence',
|
|
112
|
-
scenarios: ['si_session_lifecycle', 'si_availability', 'si_handoff', 'deterministic_session'],
|
|
113
|
-
},
|
|
114
|
-
audiences: {
|
|
115
|
-
label: 'Audience Management',
|
|
116
|
-
scenarios: ['sync_audiences'],
|
|
117
|
-
},
|
|
118
|
-
error_handling: {
|
|
119
|
-
label: 'Error Compliance',
|
|
120
|
-
scenarios: ['error_codes', 'error_structure', 'error_transport'],
|
|
121
|
-
},
|
|
122
|
-
};
|
|
123
|
-
/**
|
|
124
|
-
* Which tools make a track "applicable" — if the agent has at least one
|
|
125
|
-
* of these tools, the track should be attempted.
|
|
57
|
+
* All compliance tracks in display order.
|
|
126
58
|
*/
|
|
127
|
-
const TRACK_RELEVANCE = {
|
|
128
|
-
core: [], // always applicable
|
|
129
|
-
products: ['get_products'],
|
|
130
|
-
media_buy: ['create_media_buy', 'update_media_buy', 'get_media_buys'],
|
|
131
|
-
creative: ['sync_creatives', 'build_creative', 'list_creative_formats'],
|
|
132
|
-
reporting: ['get_media_buy_delivery'],
|
|
133
|
-
governance: ['create_property_list', 'list_content_standards'],
|
|
134
|
-
campaign_governance: ['sync_plans', 'check_governance'],
|
|
135
|
-
signals: ['get_signals'],
|
|
136
|
-
si: ['si_initiate_session'],
|
|
137
|
-
audiences: ['sync_audiences'],
|
|
138
|
-
error_handling: ['create_media_buy'],
|
|
139
|
-
};
|
|
140
59
|
const TRACK_ORDER = [
|
|
141
60
|
'core',
|
|
142
61
|
'products',
|
|
@@ -150,51 +69,6 @@ const TRACK_ORDER = [
|
|
|
150
69
|
'audiences',
|
|
151
70
|
'error_handling',
|
|
152
71
|
];
|
|
153
|
-
function isTrackApplicable(track, tools) {
|
|
154
|
-
const relevantTools = TRACK_RELEVANCE[track];
|
|
155
|
-
if (relevantTools.length === 0)
|
|
156
|
-
return true;
|
|
157
|
-
return relevantTools.some(t => tools.includes(t));
|
|
158
|
-
}
|
|
159
|
-
function isAuthError(step) {
|
|
160
|
-
if (!step.error || step.passed)
|
|
161
|
-
return false;
|
|
162
|
-
const e = step.error.toLowerCase();
|
|
163
|
-
return (e.includes('authentication') ||
|
|
164
|
-
e.includes('x-adcp-auth') ||
|
|
165
|
-
e.includes('unauthorized') ||
|
|
166
|
-
e.includes('missing auth') ||
|
|
167
|
-
e.includes('401'));
|
|
168
|
-
}
|
|
169
|
-
/**
|
|
170
|
-
* Check if a scenario failed entirely due to auth errors.
|
|
171
|
-
* Returns true if every failed step is an auth error.
|
|
172
|
-
*/
|
|
173
|
-
function isAuthOnlyFailure(result) {
|
|
174
|
-
if (result.overall_passed)
|
|
175
|
-
return false;
|
|
176
|
-
const failedSteps = (result.steps ?? []).filter(s => !s.passed);
|
|
177
|
-
return failedSteps.length > 0 && failedSteps.every(isAuthError);
|
|
178
|
-
}
|
|
179
|
-
function computeTrackStatus(results, skippedCount, hasAuth) {
|
|
180
|
-
if (results.length === 0)
|
|
181
|
-
return 'skip';
|
|
182
|
-
// When running without auth, scenarios that failed only due to auth
|
|
183
|
-
// don't count as failures
|
|
184
|
-
const effectiveResults = results.map(r => {
|
|
185
|
-
if (!hasAuth && isAuthOnlyFailure(r)) {
|
|
186
|
-
return { ...r, _authSkipped: true, overall_passed: true };
|
|
187
|
-
}
|
|
188
|
-
return r;
|
|
189
|
-
});
|
|
190
|
-
const passed = effectiveResults.filter(r => r.overall_passed).length;
|
|
191
|
-
const total = effectiveResults.length;
|
|
192
|
-
if (passed === total)
|
|
193
|
-
return 'pass';
|
|
194
|
-
if (passed === 0)
|
|
195
|
-
return 'fail';
|
|
196
|
-
return 'partial';
|
|
197
|
-
}
|
|
198
72
|
/**
|
|
199
73
|
* Collect advisory observations from test results.
|
|
200
74
|
* Analyzes the actual data for quality signals that aren't pass/fail.
|
|
@@ -264,7 +138,6 @@ function collectObservations(track, results, profile) {
|
|
|
264
138
|
// Media buy track observations
|
|
265
139
|
if (track === 'media_buy') {
|
|
266
140
|
// Check for valid_actions support (first match only)
|
|
267
|
-
// Only steps with observation_data are considered — snapshot-only steps don't set it.
|
|
268
141
|
let checkedValidActions = false;
|
|
269
142
|
for (const result of results) {
|
|
270
143
|
if (checkedValidActions)
|
|
@@ -281,7 +154,6 @@ function collectObservations(track, results, profile) {
|
|
|
281
154
|
'Without valid_actions, buyer agents must hardcode the state machine to know what operations are permitted.',
|
|
282
155
|
});
|
|
283
156
|
}
|
|
284
|
-
// Check creative_deadline support
|
|
285
157
|
if (obs.has_creative_deadline === false) {
|
|
286
158
|
observations.push({
|
|
287
159
|
category: 'best_practice',
|
|
@@ -291,7 +163,6 @@ function collectObservations(track, results, profile) {
|
|
|
291
163
|
'Buyers need to know when creative uploads must be finalized to avoid rejected submissions.',
|
|
292
164
|
});
|
|
293
165
|
}
|
|
294
|
-
// Check history entry shape when present
|
|
295
166
|
if (obs.history_entries && obs.history_entries > 0 && obs.history_valid === false) {
|
|
296
167
|
observations.push({
|
|
297
168
|
category: 'best_practice',
|
|
@@ -301,7 +172,6 @@ function collectObservations(track, results, profile) {
|
|
|
301
172
|
'History entries must include at least timestamp and action to be useful for audit.',
|
|
302
173
|
});
|
|
303
174
|
}
|
|
304
|
-
// Check dry_run/sandbox confirmation
|
|
305
175
|
if (obs.sandbox === undefined || obs.sandbox === null) {
|
|
306
176
|
observations.push({
|
|
307
177
|
category: 'best_practice',
|
|
@@ -387,6 +257,15 @@ function collectObservations(track, results, profile) {
|
|
|
387
257
|
'Buyers need to distinguish buyer-initiated from seller-initiated cancellations.',
|
|
388
258
|
});
|
|
389
259
|
}
|
|
260
|
+
if (!obs.canceled_at) {
|
|
261
|
+
observations.push({
|
|
262
|
+
category: 'completeness',
|
|
263
|
+
severity: 'warning',
|
|
264
|
+
track,
|
|
265
|
+
message: 'Agent transitions to canceled status but does not include canceled_at timestamp. ' +
|
|
266
|
+
'A cancellation timestamp is required for audit and reconciliation.',
|
|
267
|
+
});
|
|
268
|
+
}
|
|
390
269
|
checkedCancellation = true;
|
|
391
270
|
}
|
|
392
271
|
}
|
|
@@ -484,7 +363,16 @@ function collectObservations(track, results, profile) {
|
|
|
484
363
|
}
|
|
485
364
|
/**
|
|
486
365
|
* Run compliance assessment against an agent.
|
|
487
|
-
* Assesses all applicable
|
|
366
|
+
* Assesses all applicable storyboards and reports results grouped by track.
|
|
367
|
+
*
|
|
368
|
+
* Resolution priority:
|
|
369
|
+
* 1. options.storyboards — run exactly these storyboard IDs
|
|
370
|
+
* 2. options.platform_type (when tracks is not set) — resolve via PLATFORM_STORYBOARDS
|
|
371
|
+
* 3. options.tracks — run all storyboards for these tracks
|
|
372
|
+
* 4. Default — run all applicable storyboards
|
|
373
|
+
*
|
|
374
|
+
* When platform_type is set, it always drives coherence checking regardless
|
|
375
|
+
* of how the storyboard pool was resolved.
|
|
488
376
|
*/
|
|
489
377
|
async function comply(agentUrl, options = {}) {
|
|
490
378
|
try {
|
|
@@ -494,10 +382,106 @@ async function comply(agentUrl, options = {}) {
|
|
|
494
382
|
await (0, mcp_1.closeMCPConnections)();
|
|
495
383
|
}
|
|
496
384
|
}
|
|
385
|
+
// ────────────────────────────────────────────────────────────
|
|
386
|
+
// Storyboard resolution
|
|
387
|
+
// ────────────────────────────────────────────────────────────
|
|
388
|
+
/**
|
|
389
|
+
* Resolve the storyboard pool based on options.
|
|
390
|
+
* Priority: storyboards > platform_type (when tracks is not set) > tracks > all bundled.
|
|
391
|
+
*/
|
|
392
|
+
function resolveStoryboards(options) {
|
|
393
|
+
// Explicit storyboard IDs — highest priority
|
|
394
|
+
if (options.storyboards?.length) {
|
|
395
|
+
const resolved = [];
|
|
396
|
+
for (const id of options.storyboards) {
|
|
397
|
+
const sb = (0, loader_1.getStoryboardById)(id);
|
|
398
|
+
if (!sb) {
|
|
399
|
+
throw new Error(`Unknown storyboard ID: "${id}". Use listStoryboards() to see available IDs.`);
|
|
400
|
+
}
|
|
401
|
+
resolved.push(sb);
|
|
402
|
+
}
|
|
403
|
+
return resolved;
|
|
404
|
+
}
|
|
405
|
+
// Platform type — resolve via PLATFORM_STORYBOARDS
|
|
406
|
+
if (options.platform_type && !options.tracks) {
|
|
407
|
+
const pt = options.platform_type;
|
|
408
|
+
const ids = platform_storyboards_1.PLATFORM_STORYBOARDS[pt];
|
|
409
|
+
if (ids) {
|
|
410
|
+
const resolved = [];
|
|
411
|
+
for (const id of ids) {
|
|
412
|
+
const sb = (0, loader_1.getStoryboardById)(id);
|
|
413
|
+
if (sb) {
|
|
414
|
+
resolved.push(sb);
|
|
415
|
+
}
|
|
416
|
+
else {
|
|
417
|
+
// Data integrity issue — storyboard declared in PLATFORM_STORYBOARDS
|
|
418
|
+
// but not found in bundled set. This is a packaging bug.
|
|
419
|
+
console.warn(`PLATFORM_STORYBOARDS[${pt}] references unknown storyboard "${id}"`);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
// Also include universal storyboards (no platform_types) not already in the set
|
|
423
|
+
const resolvedIds = new Set(resolved.map(s => s.id));
|
|
424
|
+
for (const sb of (0, loader_1.loadBundledStoryboards)()) {
|
|
425
|
+
if (!sb.track)
|
|
426
|
+
continue;
|
|
427
|
+
if (resolvedIds.has(sb.id))
|
|
428
|
+
continue;
|
|
429
|
+
if (!sb.platform_types?.length) {
|
|
430
|
+
resolved.push(sb);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
return resolved;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
// Track filter — run storyboards whose track field matches
|
|
437
|
+
if (options.tracks?.length) {
|
|
438
|
+
const trackSet = new Set(options.tracks);
|
|
439
|
+
return (0, loader_1.loadBundledStoryboards)().filter(sb => sb.track && trackSet.has(sb.track));
|
|
440
|
+
}
|
|
441
|
+
// Default — all compliance storyboards (those with a track field)
|
|
442
|
+
return (0, loader_1.loadBundledStoryboards)().filter(sb => sb.track);
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Filter storyboards to those applicable for the agent's tools.
|
|
446
|
+
* A storyboard is applicable if the agent has at least one of its required_tools,
|
|
447
|
+
* or if it has no required_tools at all.
|
|
448
|
+
*/
|
|
449
|
+
function filterApplicable(storyboards, agentTools) {
|
|
450
|
+
return storyboards.filter(sb => {
|
|
451
|
+
if (!sb.required_tools?.length)
|
|
452
|
+
return true;
|
|
453
|
+
return sb.required_tools.some(tool => agentTools.includes(tool));
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Group storyboard results by track.
|
|
458
|
+
*/
|
|
459
|
+
function groupByTrack(results, storyboards) {
|
|
460
|
+
// Build a storyboard ID → track lookup
|
|
461
|
+
const trackLookup = new Map();
|
|
462
|
+
for (const sb of storyboards) {
|
|
463
|
+
if (sb.track) {
|
|
464
|
+
trackLookup.set(sb.id, sb.track);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
const grouped = new Map();
|
|
468
|
+
for (const result of results) {
|
|
469
|
+
const track = trackLookup.get(result.storyboard_id);
|
|
470
|
+
if (!track)
|
|
471
|
+
continue;
|
|
472
|
+
if (!grouped.has(track))
|
|
473
|
+
grouped.set(track, []);
|
|
474
|
+
grouped.get(track).push(result);
|
|
475
|
+
}
|
|
476
|
+
return grouped;
|
|
477
|
+
}
|
|
478
|
+
// ────────────────────────────────────────────────────────────
|
|
479
|
+
// Core implementation
|
|
480
|
+
// ────────────────────────────────────────────────────────────
|
|
497
481
|
async function complyImpl(agentUrl, options) {
|
|
498
482
|
const start = Date.now();
|
|
499
|
-
const { tracks:
|
|
500
|
-
// Validate platform_type if provided
|
|
483
|
+
const { storyboards: _storyboardIds, tracks: _trackFilter, platform_type, timeout_ms, signal: externalSignal, ...testOptions } = options;
|
|
484
|
+
// Validate platform_type if provided
|
|
501
485
|
let platformProfile;
|
|
502
486
|
if (platform_type) {
|
|
503
487
|
const validTypes = (0, profiles_1.getAllPlatformTypes)();
|
|
@@ -537,14 +521,14 @@ async function complyImpl(agentUrl, options) {
|
|
|
537
521
|
};
|
|
538
522
|
// Check for abort before starting
|
|
539
523
|
signal?.throwIfAborted();
|
|
540
|
-
// Collect observations across all tracks
|
|
524
|
+
// Collect observations across all tracks
|
|
541
525
|
const allObservations = [];
|
|
542
|
-
// Discover agent capabilities once and share across all
|
|
526
|
+
// Discover agent capabilities once and share across all storyboards
|
|
543
527
|
const client = (0, client_1.createTestClient)(agentUrl, effectiveOptions.protocol ?? 'mcp', effectiveOptions);
|
|
544
528
|
const { profile, step: profileStep } = await (0, client_1.discoverAgentProfile)(client);
|
|
545
529
|
effectiveOptions._client = client;
|
|
546
530
|
effectiveOptions._profile = profile;
|
|
547
|
-
// Log discovered tools
|
|
531
|
+
// Log discovered tools
|
|
548
532
|
if (profileStep.passed) {
|
|
549
533
|
allObservations.push({
|
|
550
534
|
category: 'tool_discovery',
|
|
@@ -562,145 +546,62 @@ async function complyImpl(agentUrl, options) {
|
|
|
562
546
|
}
|
|
563
547
|
}
|
|
564
548
|
if (!profileStep.passed) {
|
|
565
|
-
|
|
566
|
-
const observations = [];
|
|
567
|
-
// Check for auth errors — either explicit 401/Unauthorized or MCP SDK's generic
|
|
568
|
-
// "Failed to discover" which often wraps a 401
|
|
569
|
-
const isExplicitAuthError = errorMsg.includes('401') ||
|
|
570
|
-
errorMsg.includes('Unauthorized') ||
|
|
571
|
-
errorMsg.includes('unauthorized') ||
|
|
572
|
-
errorMsg.includes('authentication') ||
|
|
573
|
-
errorMsg.includes('JWS') ||
|
|
574
|
-
errorMsg.includes('JWT') ||
|
|
575
|
-
errorMsg.includes('signature verification');
|
|
576
|
-
// When MCP SDK wraps the error, probe the endpoint directly
|
|
577
|
-
let isAuthError = isExplicitAuthError;
|
|
578
|
-
if (!isAuthError && errorMsg.includes('Failed to discover')) {
|
|
579
|
-
try {
|
|
580
|
-
const probe = await fetch(agentUrl, {
|
|
581
|
-
method: 'POST',
|
|
582
|
-
headers: { 'Content-Type': 'application/json' },
|
|
583
|
-
signal,
|
|
584
|
-
});
|
|
585
|
-
if (probe.status === 401 || probe.status === 403) {
|
|
586
|
-
isAuthError = true;
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
catch {
|
|
590
|
-
// Network error — not an auth issue
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
const headline = isAuthError ? `Authentication required` : `Agent unreachable — ${errorMsg}`;
|
|
594
|
-
if (isAuthError) {
|
|
595
|
-
// Check if agent supports OAuth
|
|
596
|
-
const { discoverOAuthMetadata } = await Promise.resolve().then(() => __importStar(require('../../auth/oauth/discovery')));
|
|
597
|
-
const oauthMeta = await discoverOAuthMetadata(agentUrl);
|
|
598
|
-
if (oauthMeta) {
|
|
599
|
-
observations.push({
|
|
600
|
-
category: 'auth',
|
|
601
|
-
severity: 'error',
|
|
602
|
-
message: `Agent requires OAuth (issuer: ${oauthMeta.issuer || 'unknown'}). Save credentials: adcp --save-auth <alias> ${agentUrl} --oauth`,
|
|
603
|
-
});
|
|
604
|
-
}
|
|
605
|
-
else {
|
|
606
|
-
observations.push({
|
|
607
|
-
category: 'auth',
|
|
608
|
-
severity: 'error',
|
|
609
|
-
message: 'Agent returned 401. Check your --auth token.',
|
|
610
|
-
});
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
return {
|
|
614
|
-
agent_url: agentUrl,
|
|
615
|
-
agent_profile: profile,
|
|
616
|
-
overall_status: (isAuthError ? 'auth_required' : 'unreachable'),
|
|
617
|
-
tracks: [],
|
|
618
|
-
tested_tracks: [],
|
|
619
|
-
skipped_tracks: [],
|
|
620
|
-
expected_tracks: [],
|
|
621
|
-
summary: {
|
|
622
|
-
tracks_passed: 0,
|
|
623
|
-
tracks_failed: 0,
|
|
624
|
-
tracks_skipped: 0,
|
|
625
|
-
tracks_partial: 0,
|
|
626
|
-
tracks_expected: 0,
|
|
627
|
-
headline,
|
|
628
|
-
},
|
|
629
|
-
observations,
|
|
630
|
-
tested_at: new Date().toISOString(),
|
|
631
|
-
total_duration_ms: Date.now() - start,
|
|
632
|
-
dry_run: effectiveOptions.dry_run !== false,
|
|
633
|
-
};
|
|
549
|
+
return buildUnreachableResult(agentUrl, profile, profileStep.error, start, effectiveOptions, signal);
|
|
634
550
|
}
|
|
635
|
-
|
|
636
|
-
const
|
|
637
|
-
|
|
638
|
-
|
|
551
|
+
// Resolve and filter storyboard pool
|
|
552
|
+
const allStoryboards = resolveStoryboards(options);
|
|
553
|
+
const applicableStoryboards = filterApplicable(allStoryboards, profile.tools);
|
|
554
|
+
// Run storyboards
|
|
555
|
+
const storyboardResults = [];
|
|
556
|
+
const runOptions = {
|
|
557
|
+
...effectiveOptions,
|
|
558
|
+
agentTools: profile.tools,
|
|
559
|
+
};
|
|
560
|
+
for (const sb of applicableStoryboards) {
|
|
639
561
|
signal?.throwIfAborted();
|
|
640
|
-
const
|
|
641
|
-
|
|
562
|
+
const result = await (0, runner_1.runStoryboard)(agentUrl, sb, runOptions);
|
|
563
|
+
storyboardResults.push(result);
|
|
564
|
+
}
|
|
565
|
+
// Group results by track and build TrackResults
|
|
566
|
+
const grouped = groupByTrack(storyboardResults, applicableStoryboards);
|
|
567
|
+
const trackResults = [];
|
|
568
|
+
// Determine which tracks had storyboards in the pool (even if filtered out by tools)
|
|
569
|
+
const poolTrackSet = new Set();
|
|
570
|
+
for (const sb of allStoryboards) {
|
|
571
|
+
if (sb.track)
|
|
572
|
+
poolTrackSet.add(sb.track);
|
|
573
|
+
}
|
|
574
|
+
for (const track of TRACK_ORDER) {
|
|
575
|
+
if (!poolTrackSet.has(track))
|
|
642
576
|
continue;
|
|
643
|
-
|
|
577
|
+
const results = grouped.get(track) ?? [];
|
|
578
|
+
if (results.length > 0) {
|
|
579
|
+
const trackResult = (0, storyboard_tracks_1.mapStoryboardResultsToTrackResult)(track, results, profile);
|
|
580
|
+
const observations = collectObservations(track, trackResult.scenarios, profile);
|
|
581
|
+
trackResult.observations = observations;
|
|
582
|
+
allObservations.push(...observations);
|
|
583
|
+
trackResults.push(trackResult);
|
|
584
|
+
}
|
|
585
|
+
else {
|
|
586
|
+
// Track was in the pool but no storyboards ran (agent lacks tools)
|
|
644
587
|
const isExpected = track !== 'core' && (platformProfile?.expected_tracks.includes(track) ?? false);
|
|
645
|
-
const requiredTools = TRACK_RELEVANCE[track];
|
|
646
|
-
const trackObservations = [];
|
|
647
|
-
if (requiredTools.length > 0) {
|
|
648
|
-
trackObservations.push({
|
|
649
|
-
category: 'tool_discovery',
|
|
650
|
-
severity: isExpected ? 'warning' : 'info',
|
|
651
|
-
message: `Track "${track}" skipped: agent does not advertise any of [${requiredTools.join(', ')}]. ` +
|
|
652
|
-
`Agent tools: [${profile.tools.join(', ')}]`,
|
|
653
|
-
evidence: { expected_tools: requiredTools, agent_tool_count: profile.tools.length },
|
|
654
|
-
});
|
|
655
|
-
}
|
|
656
|
-
allObservations.push(...trackObservations);
|
|
657
588
|
trackResults.push({
|
|
658
589
|
track,
|
|
659
590
|
status: isExpected ? 'expected' : 'skip',
|
|
660
|
-
label:
|
|
661
|
-
scenarios: [],
|
|
662
|
-
skipped_scenarios: def.scenarios,
|
|
663
|
-
observations: trackObservations,
|
|
664
|
-
duration_ms: 0,
|
|
665
|
-
});
|
|
666
|
-
continue;
|
|
667
|
-
}
|
|
668
|
-
const trackStart = Date.now();
|
|
669
|
-
// Run compliance storyboards for this track
|
|
670
|
-
const storyboardResults = await (0, storyboard_tracks_1.runTrackStoryboards)(agentUrl, track, profile.tools, {
|
|
671
|
-
...effectiveOptions,
|
|
672
|
-
agentTools: profile.tools,
|
|
673
|
-
});
|
|
674
|
-
let trackResult;
|
|
675
|
-
if (storyboardResults.length > 0) {
|
|
676
|
-
// Map storyboard results to TrackResult for backwards compat
|
|
677
|
-
trackResult = (0, storyboard_tracks_1.mapStoryboardResultsToTrackResult)(track, storyboardResults, profile);
|
|
678
|
-
}
|
|
679
|
-
else {
|
|
680
|
-
// No storyboards for this track — skip
|
|
681
|
-
trackResult = {
|
|
682
|
-
track,
|
|
683
|
-
status: 'skip',
|
|
684
|
-
label: def.label,
|
|
591
|
+
label: storyboard_tracks_1.TRACK_LABELS[track] || track,
|
|
685
592
|
scenarios: [],
|
|
686
593
|
skipped_scenarios: [],
|
|
687
594
|
observations: [],
|
|
688
595
|
duration_ms: 0,
|
|
689
|
-
};
|
|
596
|
+
});
|
|
690
597
|
}
|
|
691
|
-
// Collect observations from track results and agent profile
|
|
692
|
-
const observations = collectObservations(track, trackResult.scenarios, profile);
|
|
693
|
-
trackResult.observations = observations;
|
|
694
|
-
trackResult.duration_ms = Date.now() - trackStart;
|
|
695
|
-
allObservations.push(...observations);
|
|
696
|
-
trackResults.push(trackResult);
|
|
697
598
|
}
|
|
698
599
|
// Build platform coherence result if platform type was declared
|
|
699
600
|
let platformCoherence;
|
|
700
601
|
if (platformProfile) {
|
|
701
602
|
const findings = platformProfile.checkCoherence(profile);
|
|
702
|
-
const
|
|
703
|
-
|
|
603
|
+
const applicableTrackSet = new Set(trackResults.filter(t => t.status !== 'skip' && t.status !== 'expected').map(t => t.track));
|
|
604
|
+
const missingTracks = platformProfile.expected_tracks.filter(t => !applicableTrackSet.has(t) && t !== 'core');
|
|
704
605
|
for (const finding of findings) {
|
|
705
606
|
allObservations.push({
|
|
706
607
|
category: 'coherence',
|
|
@@ -720,18 +621,14 @@ async function complyImpl(agentUrl, options) {
|
|
|
720
621
|
};
|
|
721
622
|
}
|
|
722
623
|
const summary = buildSummary(trackResults);
|
|
723
|
-
// Partition tracks by disposition (issue #403)
|
|
724
624
|
const testedTracks = trackResults.filter(t => t.status === 'pass' || t.status === 'fail' || t.status === 'partial');
|
|
725
625
|
const skippedTracks = trackResults
|
|
726
626
|
.filter(t => t.status === 'skip')
|
|
727
|
-
.map(t => {
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
reason: required.length > 0 ? `Agent lacks required tools: ${required.join(', ')}` : 'Agent lacks required tools',
|
|
733
|
-
};
|
|
734
|
-
});
|
|
627
|
+
.map(t => ({
|
|
628
|
+
track: t.track,
|
|
629
|
+
label: t.label,
|
|
630
|
+
reason: 'Agent lacks required tools for applicable storyboards',
|
|
631
|
+
}));
|
|
735
632
|
const expectedTracks = trackResults
|
|
736
633
|
.filter(t => t.status === 'expected')
|
|
737
634
|
.map(t => ({
|
|
@@ -739,7 +636,6 @@ async function complyImpl(agentUrl, options) {
|
|
|
739
636
|
label: t.label,
|
|
740
637
|
reason: `Expected for ${platformCoherence?.label ?? 'declared platform type'}`,
|
|
741
638
|
}));
|
|
742
|
-
// Compute overall status (issue #401)
|
|
743
639
|
const overallStatus = computeOverallStatus(summary);
|
|
744
640
|
return {
|
|
745
641
|
agent_url: agentUrl,
|
|
@@ -767,6 +663,76 @@ async function complyImpl(agentUrl, options) {
|
|
|
767
663
|
}
|
|
768
664
|
}
|
|
769
665
|
}
|
|
666
|
+
/**
|
|
667
|
+
* Build result for an unreachable or auth-required agent.
|
|
668
|
+
*/
|
|
669
|
+
async function buildUnreachableResult(agentUrl, profile, errorMsg, start, effectiveOptions, signal) {
|
|
670
|
+
const err = errorMsg || 'Unknown error';
|
|
671
|
+
const observations = [];
|
|
672
|
+
const isExplicitAuthError = err.includes('401') ||
|
|
673
|
+
err.includes('Unauthorized') ||
|
|
674
|
+
err.includes('unauthorized') ||
|
|
675
|
+
err.includes('authentication') ||
|
|
676
|
+
err.includes('JWS') ||
|
|
677
|
+
err.includes('JWT') ||
|
|
678
|
+
err.includes('signature verification');
|
|
679
|
+
let isAuthError = isExplicitAuthError;
|
|
680
|
+
if (!isAuthError && err.includes('Failed to discover')) {
|
|
681
|
+
try {
|
|
682
|
+
const probe = await fetch(agentUrl, {
|
|
683
|
+
method: 'POST',
|
|
684
|
+
headers: { 'Content-Type': 'application/json' },
|
|
685
|
+
signal,
|
|
686
|
+
});
|
|
687
|
+
if (probe.status === 401 || probe.status === 403) {
|
|
688
|
+
isAuthError = true;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
catch {
|
|
692
|
+
// Network error — not an auth issue
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
const headline = isAuthError ? `Authentication required` : `Agent unreachable — ${err}`;
|
|
696
|
+
if (isAuthError) {
|
|
697
|
+
const { discoverOAuthMetadata } = await Promise.resolve().then(() => __importStar(require('../../auth/oauth/discovery')));
|
|
698
|
+
const oauthMeta = await discoverOAuthMetadata(agentUrl);
|
|
699
|
+
if (oauthMeta) {
|
|
700
|
+
observations.push({
|
|
701
|
+
category: 'auth',
|
|
702
|
+
severity: 'error',
|
|
703
|
+
message: `Agent requires OAuth (issuer: ${oauthMeta.issuer || 'unknown'}). Save credentials: adcp --save-auth <alias> ${agentUrl} --oauth`,
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
else {
|
|
707
|
+
observations.push({
|
|
708
|
+
category: 'auth',
|
|
709
|
+
severity: 'error',
|
|
710
|
+
message: 'Agent returned 401. Check your --auth token.',
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
return {
|
|
715
|
+
agent_url: agentUrl,
|
|
716
|
+
agent_profile: profile,
|
|
717
|
+
overall_status: (isAuthError ? 'auth_required' : 'unreachable'),
|
|
718
|
+
tracks: [],
|
|
719
|
+
tested_tracks: [],
|
|
720
|
+
skipped_tracks: [],
|
|
721
|
+
expected_tracks: [],
|
|
722
|
+
summary: {
|
|
723
|
+
tracks_passed: 0,
|
|
724
|
+
tracks_failed: 0,
|
|
725
|
+
tracks_skipped: 0,
|
|
726
|
+
tracks_partial: 0,
|
|
727
|
+
tracks_expected: 0,
|
|
728
|
+
headline,
|
|
729
|
+
},
|
|
730
|
+
observations,
|
|
731
|
+
tested_at: new Date().toISOString(),
|
|
732
|
+
total_duration_ms: Date.now() - start,
|
|
733
|
+
dry_run: effectiveOptions.dry_run !== false,
|
|
734
|
+
};
|
|
735
|
+
}
|
|
770
736
|
/**
|
|
771
737
|
* Compute overall status for a reachable agent.
|
|
772
738
|
* auth_required and unreachable are set directly in the early-exit path.
|