@adcp/client 4.14.0 → 4.16.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.js +2 -0
- package/dist/lib/adapters/governance-adapter.d.ts +2 -2
- package/dist/lib/adapters/governance-adapter.d.ts.map +1 -1
- package/dist/lib/adapters/governance-adapter.js +1 -3
- package/dist/lib/adapters/governance-adapter.js.map +1 -1
- package/dist/lib/adapters/si-session-manager.d.ts.map +1 -1
- package/dist/lib/adapters/si-session-manager.js +8 -3
- package/dist/lib/adapters/si-session-manager.js.map +1 -1
- package/dist/lib/agents/index.generated.d.ts +9 -1
- package/dist/lib/agents/index.generated.d.ts.map +1 -1
- package/dist/lib/agents/index.generated.js +12 -0
- package/dist/lib/agents/index.generated.js.map +1 -1
- package/dist/lib/core/ADCPMultiAgentClient.d.ts.map +1 -1
- package/dist/lib/core/ADCPMultiAgentClient.js +10 -3
- package/dist/lib/core/ADCPMultiAgentClient.js.map +1 -1
- package/dist/lib/core/AgentClient.d.ts.map +1 -1
- package/dist/lib/core/AsyncHandler.d.ts.map +1 -1
- package/dist/lib/core/AsyncHandler.js +1 -1
- package/dist/lib/core/AsyncHandler.js.map +1 -1
- package/dist/lib/core/GovernanceMiddleware.d.ts +2 -10
- package/dist/lib/core/GovernanceMiddleware.d.ts.map +1 -1
- package/dist/lib/core/GovernanceMiddleware.js +8 -51
- package/dist/lib/core/GovernanceMiddleware.js.map +1 -1
- package/dist/lib/core/GovernanceTypes.d.ts +4 -4
- package/dist/lib/core/GovernanceTypes.d.ts.map +1 -1
- package/dist/lib/core/GovernanceTypes.js +1 -0
- package/dist/lib/core/GovernanceTypes.js.map +1 -1
- package/dist/lib/core/SingleAgentClient.d.ts +1 -1
- package/dist/lib/core/SingleAgentClient.d.ts.map +1 -1
- package/dist/lib/core/SingleAgentClient.js +7 -4
- package/dist/lib/core/SingleAgentClient.js.map +1 -1
- package/dist/lib/core/TaskExecutor.d.ts +4 -0
- package/dist/lib/core/TaskExecutor.d.ts.map +1 -1
- package/dist/lib/core/TaskExecutor.js +61 -18
- package/dist/lib/core/TaskExecutor.js.map +1 -1
- package/dist/lib/index.d.ts +5 -3
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +12 -6
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/protocols/index.d.ts +1 -0
- package/dist/lib/protocols/index.d.ts.map +1 -1
- package/dist/lib/protocols/index.js +14 -4
- package/dist/lib/protocols/index.js.map +1 -1
- package/dist/lib/protocols/mcp-tasks.d.ts +55 -0
- package/dist/lib/protocols/mcp-tasks.d.ts.map +1 -0
- package/dist/lib/protocols/mcp-tasks.js +334 -0
- package/dist/lib/protocols/mcp-tasks.js.map +1 -0
- package/dist/lib/protocols/mcp.d.ts +9 -0
- package/dist/lib/protocols/mcp.d.ts.map +1 -1
- package/dist/lib/protocols/mcp.js +4 -0
- package/dist/lib/protocols/mcp.js.map +1 -1
- package/dist/lib/registry/types.generated.d.ts +9 -9
- package/dist/lib/registry/types.generated.d.ts.map +1 -1
- package/dist/lib/registry/types.generated.js +1 -1
- package/dist/lib/server/index.d.ts +2 -0
- package/dist/lib/server/index.d.ts.map +1 -1
- package/dist/lib/server/index.js +7 -1
- package/dist/lib/server/index.js.map +1 -1
- package/dist/lib/server/tasks.d.ts +86 -0
- package/dist/lib/server/tasks.d.ts.map +1 -0
- package/dist/lib/server/tasks.js +110 -0
- package/dist/lib/server/tasks.js.map +1 -0
- package/dist/lib/testing/agent-tester.d.ts +1 -1
- package/dist/lib/testing/agent-tester.d.ts.map +1 -1
- package/dist/lib/testing/agent-tester.js +52 -2
- package/dist/lib/testing/agent-tester.js.map +1 -1
- package/dist/lib/testing/client.d.ts +18 -0
- package/dist/lib/testing/client.d.ts.map +1 -1
- package/dist/lib/testing/client.js +39 -1
- package/dist/lib/testing/client.js.map +1 -1
- package/dist/lib/testing/compliance/comply.d.ts +4 -0
- package/dist/lib/testing/compliance/comply.d.ts.map +1 -1
- package/dist/lib/testing/compliance/comply.js +406 -173
- package/dist/lib/testing/compliance/comply.js.map +1 -1
- package/dist/lib/testing/compliance/types.d.ts +7 -1
- package/dist/lib/testing/compliance/types.d.ts.map +1 -1
- package/dist/lib/testing/index.d.ts +3 -1
- package/dist/lib/testing/index.d.ts.map +1 -1
- package/dist/lib/testing/index.js +13 -2
- package/dist/lib/testing/index.js.map +1 -1
- package/dist/lib/testing/orchestrator.d.ts +4 -0
- package/dist/lib/testing/orchestrator.d.ts.map +1 -1
- package/dist/lib/testing/orchestrator.js +19 -2
- package/dist/lib/testing/orchestrator.js.map +1 -1
- package/dist/lib/testing/scenarios/capabilities.js +2 -2
- package/dist/lib/testing/scenarios/capabilities.js.map +1 -1
- package/dist/lib/testing/scenarios/creative.js +4 -4
- package/dist/lib/testing/scenarios/creative.js.map +1 -1
- package/dist/lib/testing/scenarios/deterministic.d.ts +37 -0
- package/dist/lib/testing/scenarios/deterministic.d.ts.map +1 -0
- package/dist/lib/testing/scenarios/deterministic.js +705 -0
- package/dist/lib/testing/scenarios/deterministic.js.map +1 -0
- package/dist/lib/testing/scenarios/discovery.js +2 -2
- package/dist/lib/testing/scenarios/discovery.js.map +1 -1
- package/dist/lib/testing/scenarios/edge-cases.d.ts.map +1 -1
- package/dist/lib/testing/scenarios/edge-cases.js +18 -25
- package/dist/lib/testing/scenarios/edge-cases.js.map +1 -1
- package/dist/lib/testing/scenarios/error-compliance.d.ts.map +1 -1
- package/dist/lib/testing/scenarios/error-compliance.js +9 -13
- package/dist/lib/testing/scenarios/error-compliance.js.map +1 -1
- package/dist/lib/testing/scenarios/governance.d.ts +15 -0
- package/dist/lib/testing/scenarios/governance.d.ts.map +1 -1
- package/dist/lib/testing/scenarios/governance.js +386 -49
- package/dist/lib/testing/scenarios/governance.js.map +1 -1
- package/dist/lib/testing/scenarios/health.js +2 -2
- package/dist/lib/testing/scenarios/health.js.map +1 -1
- package/dist/lib/testing/scenarios/index.d.ts +2 -1
- package/dist/lib/testing/scenarios/index.d.ts.map +1 -1
- package/dist/lib/testing/scenarios/index.js +12 -1
- package/dist/lib/testing/scenarios/index.js.map +1 -1
- package/dist/lib/testing/scenarios/media-buy.d.ts.map +1 -1
- package/dist/lib/testing/scenarios/media-buy.js +258 -31
- package/dist/lib/testing/scenarios/media-buy.js.map +1 -1
- package/dist/lib/testing/scenarios/schema-compliance.js +2 -2
- package/dist/lib/testing/scenarios/schema-compliance.js.map +1 -1
- package/dist/lib/testing/scenarios/signals.d.ts.map +1 -1
- package/dist/lib/testing/scenarios/signals.js +35 -2
- package/dist/lib/testing/scenarios/signals.js.map +1 -1
- package/dist/lib/testing/scenarios/sponsored-intelligence.d.ts.map +1 -1
- package/dist/lib/testing/scenarios/sponsored-intelligence.js +8 -7
- package/dist/lib/testing/scenarios/sponsored-intelligence.js.map +1 -1
- package/dist/lib/testing/stubs/governance-agent-stub.d.ts +72 -0
- package/dist/lib/testing/stubs/governance-agent-stub.d.ts.map +1 -0
- package/dist/lib/testing/stubs/governance-agent-stub.js +295 -0
- package/dist/lib/testing/stubs/governance-agent-stub.js.map +1 -0
- package/dist/lib/testing/stubs/index.d.ts +3 -0
- package/dist/lib/testing/stubs/index.d.ts.map +1 -0
- package/dist/lib/testing/stubs/index.js +6 -0
- package/dist/lib/testing/stubs/index.js.map +1 -0
- package/dist/lib/testing/test-controller.d.ts +46 -0
- package/dist/lib/testing/test-controller.d.ts.map +1 -0
- package/dist/lib/testing/test-controller.js +143 -0
- package/dist/lib/testing/test-controller.js.map +1 -0
- package/dist/lib/testing/types.d.ts +8 -1
- package/dist/lib/testing/types.d.ts.map +1 -1
- package/dist/lib/types/core.generated.d.ts +562 -97
- package/dist/lib/types/core.generated.d.ts.map +1 -1
- package/dist/lib/types/core.generated.js +1 -1
- package/dist/lib/types/error-codes.d.ts +4 -4
- package/dist/lib/types/error-codes.d.ts.map +1 -1
- package/dist/lib/types/error-codes.js +26 -2
- package/dist/lib/types/error-codes.js.map +1 -1
- package/dist/lib/types/schemas.generated.d.ts +4625 -8682
- package/dist/lib/types/schemas.generated.d.ts.map +1 -1
- package/dist/lib/types/schemas.generated.js +711 -403
- package/dist/lib/types/schemas.generated.js.map +1 -1
- package/dist/lib/types/tools.generated.d.ts +1188 -405
- package/dist/lib/types/tools.generated.d.ts.map +1 -1
- package/dist/lib/utils/response-schemas.d.ts.map +1 -1
- package/dist/lib/utils/response-schemas.js +2 -0
- package/dist/lib/utils/response-schemas.js.map +1 -1
- package/dist/lib/utils/response-unwrapper.d.ts.map +1 -1
- package/dist/lib/utils/response-unwrapper.js +12 -0
- package/dist/lib/utils/response-unwrapper.js.map +1 -1
- package/dist/lib/utils/union-errors.d.ts +16 -0
- package/dist/lib/utils/union-errors.d.ts.map +1 -0
- package/dist/lib/utils/union-errors.js +34 -0
- package/dist/lib/utils/union-errors.js.map +1 -0
- package/dist/lib/version.d.ts +3 -3
- package/dist/lib/version.js +3 -3
- package/package.json +1 -1
|
@@ -47,13 +47,21 @@ const client_1 = require("../client");
|
|
|
47
47
|
const orchestrator_1 = require("../orchestrator");
|
|
48
48
|
const profiles_1 = require("./profiles");
|
|
49
49
|
const mcp_1 = require("../../protocols/mcp");
|
|
50
|
+
const test_controller_1 = require("../test-controller");
|
|
50
51
|
/**
|
|
51
52
|
* Maps each track to its constituent scenarios and a human-readable label.
|
|
52
53
|
*/
|
|
53
54
|
const TRACK_DEFINITIONS = {
|
|
54
55
|
core: {
|
|
55
56
|
label: 'Core Protocol',
|
|
56
|
-
scenarios: [
|
|
57
|
+
scenarios: [
|
|
58
|
+
'health_check',
|
|
59
|
+
'discovery',
|
|
60
|
+
'capability_discovery',
|
|
61
|
+
'schema_compliance',
|
|
62
|
+
'controller_validation',
|
|
63
|
+
'deterministic_account',
|
|
64
|
+
],
|
|
57
65
|
},
|
|
58
66
|
products: {
|
|
59
67
|
label: 'Product Discovery',
|
|
@@ -69,29 +77,41 @@ const TRACK_DEFINITIONS = {
|
|
|
69
77
|
'media_buy_lifecycle',
|
|
70
78
|
'terminal_state_enforcement',
|
|
71
79
|
'package_lifecycle',
|
|
80
|
+
'seller_governance_context',
|
|
81
|
+
'deterministic_media_buy',
|
|
82
|
+
'deterministic_budget',
|
|
72
83
|
],
|
|
73
84
|
},
|
|
74
85
|
creative: {
|
|
75
86
|
label: 'Creative Management',
|
|
76
|
-
scenarios: ['creative_sync', 'creative_flow'],
|
|
87
|
+
scenarios: ['creative_sync', 'creative_flow', 'deterministic_creative'],
|
|
77
88
|
},
|
|
78
89
|
reporting: {
|
|
79
90
|
label: 'Reporting',
|
|
80
91
|
// full_sales_flow covers get_media_buy_delivery — but we assess it as a
|
|
81
92
|
// separate track concern by checking if the agent has the tool
|
|
82
|
-
scenarios: ['full_sales_flow'],
|
|
93
|
+
scenarios: ['full_sales_flow', 'deterministic_delivery'],
|
|
83
94
|
},
|
|
84
95
|
governance: {
|
|
85
96
|
label: 'Governance',
|
|
86
97
|
scenarios: ['governance_property_lists', 'governance_content_standards', 'property_list_filters'],
|
|
87
98
|
},
|
|
99
|
+
campaign_governance: {
|
|
100
|
+
label: 'Campaign Governance',
|
|
101
|
+
scenarios: [
|
|
102
|
+
'campaign_governance',
|
|
103
|
+
'campaign_governance_denied',
|
|
104
|
+
'campaign_governance_conditions',
|
|
105
|
+
'campaign_governance_delivery',
|
|
106
|
+
],
|
|
107
|
+
},
|
|
88
108
|
signals: {
|
|
89
109
|
label: 'Signals',
|
|
90
110
|
scenarios: ['signals_flow'],
|
|
91
111
|
},
|
|
92
112
|
si: {
|
|
93
113
|
label: 'Sponsored Intelligence',
|
|
94
|
-
scenarios: ['si_session_lifecycle', 'si_availability', 'si_handoff'],
|
|
114
|
+
scenarios: ['si_session_lifecycle', 'si_availability', 'si_handoff', 'deterministic_session'],
|
|
95
115
|
},
|
|
96
116
|
audiences: {
|
|
97
117
|
label: 'Audience Management',
|
|
@@ -113,6 +133,7 @@ const TRACK_RELEVANCE = {
|
|
|
113
133
|
creative: ['sync_creatives', 'build_creative', 'list_creative_formats'],
|
|
114
134
|
reporting: ['get_media_buy_delivery'],
|
|
115
135
|
governance: ['create_property_list', 'list_content_standards'],
|
|
136
|
+
campaign_governance: ['sync_plans', 'check_governance'],
|
|
116
137
|
signals: ['get_signals'],
|
|
117
138
|
si: ['si_initiate_session'],
|
|
118
139
|
audiences: ['sync_audiences'],
|
|
@@ -125,6 +146,7 @@ const TRACK_ORDER = [
|
|
|
125
146
|
'creative',
|
|
126
147
|
'reporting',
|
|
127
148
|
'governance',
|
|
149
|
+
'campaign_governance',
|
|
128
150
|
'signals',
|
|
129
151
|
'si',
|
|
130
152
|
'audiences',
|
|
@@ -248,8 +270,11 @@ function collectObservations(track, results, profile) {
|
|
|
248
270
|
}
|
|
249
271
|
// Media buy track observations
|
|
250
272
|
if (track === 'media_buy') {
|
|
251
|
-
// Check for valid_actions support
|
|
273
|
+
// Check for valid_actions support (first match only)
|
|
274
|
+
let checkedValidActions = false;
|
|
252
275
|
for (const result of results) {
|
|
276
|
+
if (checkedValidActions)
|
|
277
|
+
break;
|
|
253
278
|
for (const step of result.steps ?? []) {
|
|
254
279
|
if (step.task === 'get_media_buys' && step.response_preview) {
|
|
255
280
|
try {
|
|
@@ -257,12 +282,135 @@ function collectObservations(track, results, profile) {
|
|
|
257
282
|
if (preview.valid_actions === undefined || preview.valid_actions === null) {
|
|
258
283
|
observations.push({
|
|
259
284
|
category: 'best_practice',
|
|
260
|
-
severity: '
|
|
285
|
+
severity: 'warning',
|
|
261
286
|
track,
|
|
262
287
|
message: 'Agent does not return valid_actions in get_media_buys response. ' +
|
|
263
|
-
'valid_actions
|
|
288
|
+
'Without valid_actions, buyer agents must hardcode the state machine to know what operations are permitted.',
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
// Check creative_deadline support
|
|
292
|
+
if (preview.has_creative_deadline === false) {
|
|
293
|
+
observations.push({
|
|
294
|
+
category: 'best_practice',
|
|
295
|
+
severity: 'suggestion',
|
|
296
|
+
track,
|
|
297
|
+
message: 'Agent does not return creative_deadline on media buys or packages. ' +
|
|
298
|
+
'Buyers need to know when creative uploads must be finalized to avoid rejected submissions.',
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
// Check history entry shape when present
|
|
302
|
+
if (preview.history_entries && preview.history_entries > 0 && preview.history_valid === false) {
|
|
303
|
+
observations.push({
|
|
304
|
+
category: 'best_practice',
|
|
305
|
+
severity: 'warning',
|
|
306
|
+
track,
|
|
307
|
+
message: 'Agent returns history entries but some lack required fields (timestamp, action). ' +
|
|
308
|
+
'History entries must include at least timestamp and action to be useful for audit.',
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
// Check dry_run/sandbox confirmation
|
|
312
|
+
if (preview.sandbox === undefined || preview.sandbox === null) {
|
|
313
|
+
observations.push({
|
|
314
|
+
category: 'best_practice',
|
|
315
|
+
severity: 'suggestion',
|
|
316
|
+
track,
|
|
317
|
+
message: 'Agent does not confirm sandbox mode in get_media_buys response. ' +
|
|
318
|
+
'Include sandbox: true so buyers can verify the agent honored dry_run mode.',
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
checkedValidActions = true;
|
|
322
|
+
}
|
|
323
|
+
catch {
|
|
324
|
+
// not always JSON
|
|
325
|
+
}
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
// Check for confirmed_at and revision in create_media_buy responses (first match only)
|
|
331
|
+
let checkedCreateLifecycle = false;
|
|
332
|
+
for (const result of results) {
|
|
333
|
+
if (checkedCreateLifecycle)
|
|
334
|
+
break;
|
|
335
|
+
for (const step of result.steps ?? []) {
|
|
336
|
+
if (step.task === 'create_media_buy' && step.response_preview) {
|
|
337
|
+
try {
|
|
338
|
+
const preview = JSON.parse(step.response_preview);
|
|
339
|
+
if (preview.confirmed_at === undefined || preview.confirmed_at === null) {
|
|
340
|
+
observations.push({
|
|
341
|
+
category: 'best_practice',
|
|
342
|
+
severity: 'warning',
|
|
343
|
+
track,
|
|
344
|
+
message: 'Agent does not return confirmed_at in create_media_buy response. ' +
|
|
345
|
+
'A successful response constitutes order confirmation — confirmed_at provides an auditable timestamp for dispute resolution.',
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
if (preview.revision === undefined || preview.revision === null) {
|
|
349
|
+
observations.push({
|
|
350
|
+
category: 'best_practice',
|
|
351
|
+
severity: 'suggestion',
|
|
352
|
+
track,
|
|
353
|
+
message: 'Agent does not return revision in create_media_buy response. ' +
|
|
354
|
+
'Revision numbers enable optimistic concurrency for safe concurrent updates.',
|
|
264
355
|
});
|
|
265
356
|
}
|
|
357
|
+
checkedCreateLifecycle = true;
|
|
358
|
+
}
|
|
359
|
+
catch {
|
|
360
|
+
// not always JSON
|
|
361
|
+
}
|
|
362
|
+
break;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
// Check for history support in get_media_buys responses (first match only)
|
|
367
|
+
let checkedHistory = false;
|
|
368
|
+
for (const result of results) {
|
|
369
|
+
if (checkedHistory)
|
|
370
|
+
break;
|
|
371
|
+
for (const step of result.steps ?? []) {
|
|
372
|
+
if (step.task === 'get_media_buys' && step.response_preview) {
|
|
373
|
+
try {
|
|
374
|
+
const preview = JSON.parse(step.response_preview);
|
|
375
|
+
if (preview.history_entries !== undefined && preview.history_entries === 0) {
|
|
376
|
+
observations.push({
|
|
377
|
+
category: 'best_practice',
|
|
378
|
+
severity: 'suggestion',
|
|
379
|
+
track,
|
|
380
|
+
message: 'Agent does not return revision history when include_history is requested. ' +
|
|
381
|
+
'History enables audit trails and helps buyers understand what changed.',
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
checkedHistory = true;
|
|
385
|
+
}
|
|
386
|
+
catch {
|
|
387
|
+
// not always JSON
|
|
388
|
+
}
|
|
389
|
+
break;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
// Check canceled_by validation on canceled media buys (first match only)
|
|
394
|
+
let checkedCancellation = false;
|
|
395
|
+
for (const result of results) {
|
|
396
|
+
if (checkedCancellation)
|
|
397
|
+
break;
|
|
398
|
+
for (const step of result.steps ?? []) {
|
|
399
|
+
if (step.task === 'update_media_buy' && step.response_preview) {
|
|
400
|
+
try {
|
|
401
|
+
const preview = JSON.parse(step.response_preview);
|
|
402
|
+
if (preview.status === 'canceled') {
|
|
403
|
+
if (!preview.canceled_by) {
|
|
404
|
+
observations.push({
|
|
405
|
+
category: 'completeness',
|
|
406
|
+
severity: 'warning',
|
|
407
|
+
track,
|
|
408
|
+
message: 'Agent transitions to canceled status but does not include canceled_by field. ' +
|
|
409
|
+
'Buyers need to distinguish buyer-initiated from seller-initiated cancellations.',
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
checkedCancellation = true;
|
|
413
|
+
}
|
|
266
414
|
}
|
|
267
415
|
catch {
|
|
268
416
|
// not always JSON
|
|
@@ -322,6 +470,34 @@ function collectObservations(track, results, profile) {
|
|
|
322
470
|
}
|
|
323
471
|
}
|
|
324
472
|
}
|
|
473
|
+
// Campaign governance track observations
|
|
474
|
+
if (track === 'campaign_governance') {
|
|
475
|
+
let anyCheckMissingContext = false;
|
|
476
|
+
for (const result of results) {
|
|
477
|
+
for (const step of result.steps ?? []) {
|
|
478
|
+
if (step.task === 'check_governance' && step.passed && step.response_preview) {
|
|
479
|
+
try {
|
|
480
|
+
const preview = JSON.parse(step.response_preview);
|
|
481
|
+
if (!preview.governance_context || preview.governance_context === '(absent)') {
|
|
482
|
+
anyCheckMissingContext = true;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
catch {
|
|
486
|
+
// not always JSON
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
if (anyCheckMissingContext) {
|
|
492
|
+
observations.push({
|
|
493
|
+
category: 'best_practice',
|
|
494
|
+
severity: 'warning',
|
|
495
|
+
track,
|
|
496
|
+
message: 'Governance agent did not return governance_context on check_governance response. ' +
|
|
497
|
+
'Without it, sellers cannot maintain governance continuity across the media buy lifecycle.',
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
}
|
|
325
501
|
// Check for slow responses
|
|
326
502
|
for (const result of results) {
|
|
327
503
|
for (const step of result.steps ?? []) {
|
|
@@ -352,196 +528,253 @@ async function comply(agentUrl, options = {}) {
|
|
|
352
528
|
}
|
|
353
529
|
async function complyImpl(agentUrl, options) {
|
|
354
530
|
const start = Date.now();
|
|
355
|
-
const { tracks: trackFilter, platform_type, ...testOptions } = options;
|
|
531
|
+
const { tracks: trackFilter, platform_type, timeout_ms, signal: externalSignal, ...testOptions } = options;
|
|
356
532
|
const platformProfile = platform_type ? (0, profiles_1.getPlatformProfile)(platform_type) : undefined;
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
533
|
+
// Validate timeout_ms
|
|
534
|
+
if (timeout_ms !== undefined) {
|
|
535
|
+
if (typeof timeout_ms !== 'number' || !Number.isFinite(timeout_ms) || timeout_ms <= 0) {
|
|
536
|
+
throw new TypeError(`timeout_ms must be a positive finite number, got: ${timeout_ms}`);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
// Build a combined AbortSignal from timeout_ms and/or external signal
|
|
540
|
+
const needsAbort = timeout_ms !== undefined || externalSignal !== undefined;
|
|
541
|
+
const abortController = needsAbort ? new AbortController() : undefined;
|
|
542
|
+
let timeoutId;
|
|
543
|
+
const onExternalAbort = externalSignal ? () => abortController.abort(externalSignal.reason) : undefined;
|
|
544
|
+
if (timeout_ms !== undefined && abortController) {
|
|
545
|
+
timeoutId = setTimeout(() => abortController.abort(new Error(`comply() timed out after ${timeout_ms}ms`)), timeout_ms);
|
|
546
|
+
}
|
|
547
|
+
if (externalSignal && abortController) {
|
|
548
|
+
if (externalSignal.aborted) {
|
|
549
|
+
abortController.abort(externalSignal.reason);
|
|
550
|
+
}
|
|
551
|
+
else {
|
|
552
|
+
externalSignal.addEventListener('abort', onExternalAbort, { once: true });
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
const signal = abortController?.signal;
|
|
556
|
+
try {
|
|
557
|
+
const effectiveOptions = {
|
|
558
|
+
...testOptions,
|
|
559
|
+
dry_run: testOptions.dry_run !== false,
|
|
560
|
+
test_session_id: testOptions.test_session_id || `comply-${Date.now()}`,
|
|
561
|
+
};
|
|
562
|
+
// Check for abort before starting
|
|
563
|
+
signal?.throwIfAborted();
|
|
564
|
+
// Discover agent capabilities once and share across all scenarios
|
|
565
|
+
const client = (0, client_1.createTestClient)(agentUrl, effectiveOptions.protocol ?? 'mcp', effectiveOptions);
|
|
566
|
+
const { profile, step: profileStep } = await (0, client_1.discoverAgentProfile)(client);
|
|
567
|
+
effectiveOptions._client = client;
|
|
568
|
+
effectiveOptions._profile = profile;
|
|
569
|
+
// Detect test controller for deterministic mode
|
|
570
|
+
let controllerDetection = { detected: false };
|
|
571
|
+
if (profileStep.passed && (0, test_controller_1.hasTestController)(profile)) {
|
|
572
|
+
controllerDetection = await (0, test_controller_1.detectController)(client, profile, effectiveOptions);
|
|
573
|
+
if (controllerDetection.detected) {
|
|
574
|
+
effectiveOptions._controllerCapabilities = controllerDetection;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
if (!profileStep.passed) {
|
|
578
|
+
const errorMsg = profileStep.error || 'Unknown error';
|
|
579
|
+
const observations = [];
|
|
580
|
+
// Check for auth errors — either explicit 401/Unauthorized or MCP SDK's generic
|
|
581
|
+
// "Failed to discover" which often wraps a 401
|
|
582
|
+
const isExplicitAuthError = errorMsg.includes('401') ||
|
|
583
|
+
errorMsg.includes('Unauthorized') ||
|
|
584
|
+
errorMsg.includes('unauthorized') ||
|
|
585
|
+
errorMsg.includes('authentication') ||
|
|
586
|
+
errorMsg.includes('JWS') ||
|
|
587
|
+
errorMsg.includes('JWT') ||
|
|
588
|
+
errorMsg.includes('signature verification');
|
|
589
|
+
// When MCP SDK wraps the error, probe the endpoint directly
|
|
590
|
+
let isAuthError = isExplicitAuthError;
|
|
591
|
+
if (!isAuthError && errorMsg.includes('Failed to discover')) {
|
|
592
|
+
try {
|
|
593
|
+
const probe = await fetch(agentUrl, {
|
|
594
|
+
method: 'POST',
|
|
595
|
+
headers: { 'Content-Type': 'application/json' },
|
|
596
|
+
signal,
|
|
597
|
+
});
|
|
598
|
+
if (probe.status === 401 || probe.status === 403) {
|
|
599
|
+
isAuthError = true;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
catch {
|
|
603
|
+
// Network error — not an auth issue
|
|
384
604
|
}
|
|
385
605
|
}
|
|
386
|
-
|
|
387
|
-
|
|
606
|
+
const headline = isAuthError ? `Authentication required` : `Agent unreachable — ${errorMsg}`;
|
|
607
|
+
if (isAuthError) {
|
|
608
|
+
// Check if agent supports OAuth
|
|
609
|
+
const { discoverOAuthMetadata } = await Promise.resolve().then(() => __importStar(require('../../auth/oauth/discovery')));
|
|
610
|
+
const oauthMeta = await discoverOAuthMetadata(agentUrl);
|
|
611
|
+
if (oauthMeta) {
|
|
612
|
+
observations.push({
|
|
613
|
+
category: 'auth',
|
|
614
|
+
severity: 'error',
|
|
615
|
+
message: `Agent requires OAuth (issuer: ${oauthMeta.issuer || 'unknown'}). Save credentials: adcp --save-auth <alias> ${agentUrl} --oauth`,
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
else {
|
|
619
|
+
observations.push({
|
|
620
|
+
category: 'auth',
|
|
621
|
+
severity: 'error',
|
|
622
|
+
message: 'Agent returned 401. Check your --auth token.',
|
|
623
|
+
});
|
|
624
|
+
}
|
|
388
625
|
}
|
|
626
|
+
return {
|
|
627
|
+
agent_url: agentUrl,
|
|
628
|
+
agent_profile: profile,
|
|
629
|
+
tracks: [],
|
|
630
|
+
summary: {
|
|
631
|
+
tracks_passed: 0,
|
|
632
|
+
tracks_failed: 0,
|
|
633
|
+
tracks_skipped: 0,
|
|
634
|
+
tracks_partial: 0,
|
|
635
|
+
tracks_expected: 0,
|
|
636
|
+
headline,
|
|
637
|
+
},
|
|
638
|
+
observations,
|
|
639
|
+
tested_at: new Date().toISOString(),
|
|
640
|
+
total_duration_ms: Date.now() - start,
|
|
641
|
+
dry_run: effectiveOptions.dry_run !== false,
|
|
642
|
+
};
|
|
389
643
|
}
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
644
|
+
const tracksToRun = trackFilter ?? TRACK_ORDER;
|
|
645
|
+
const trackResults = [];
|
|
646
|
+
const allObservations = [];
|
|
647
|
+
for (const track of tracksToRun) {
|
|
648
|
+
// Check for abort between tracks
|
|
649
|
+
signal?.throwIfAborted();
|
|
650
|
+
const def = TRACK_DEFINITIONS[track];
|
|
651
|
+
if (!def)
|
|
652
|
+
continue;
|
|
653
|
+
if (!isTrackApplicable(track, profile.tools)) {
|
|
654
|
+
const isExpected = track !== 'core' && (platformProfile?.expected_tracks.includes(track) ?? false);
|
|
655
|
+
trackResults.push({
|
|
656
|
+
track,
|
|
657
|
+
status: isExpected ? 'expected' : 'skip',
|
|
658
|
+
label: def.label,
|
|
659
|
+
scenarios: [],
|
|
660
|
+
skipped_scenarios: def.scenarios,
|
|
661
|
+
observations: [],
|
|
662
|
+
duration_ms: 0,
|
|
400
663
|
});
|
|
664
|
+
continue;
|
|
401
665
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
666
|
+
const trackStart = Date.now();
|
|
667
|
+
const applicable = (0, orchestrator_1.getApplicableScenarios)(profile.tools, def.scenarios);
|
|
668
|
+
const skipped = def.scenarios.filter(s => !applicable.includes(s));
|
|
669
|
+
// Track is relevant (agent has some related tools) but no scenarios match
|
|
670
|
+
// the specific tool combinations. Report as pass with an observation.
|
|
671
|
+
if (applicable.length === 0) {
|
|
672
|
+
const relevantTools = TRACK_RELEVANCE[track].filter(t => profile.tools.includes(t));
|
|
673
|
+
const observations = [
|
|
674
|
+
{
|
|
675
|
+
category: 'completeness',
|
|
676
|
+
severity: 'info',
|
|
677
|
+
track,
|
|
678
|
+
message: `Agent has ${relevantTools.join(', ')} but no test scenarios cover this tool combination. ` +
|
|
679
|
+
`Compliance tests exist for: ${def.scenarios.join(', ')}.`,
|
|
680
|
+
evidence: { tools_present: relevantTools, scenarios_available: def.scenarios },
|
|
681
|
+
},
|
|
682
|
+
];
|
|
683
|
+
allObservations.push(...observations);
|
|
684
|
+
trackResults.push({
|
|
685
|
+
track,
|
|
686
|
+
status: 'pass',
|
|
687
|
+
label: def.label,
|
|
688
|
+
scenarios: [],
|
|
689
|
+
skipped_scenarios: skipped,
|
|
690
|
+
observations,
|
|
691
|
+
duration_ms: Date.now() - trackStart,
|
|
407
692
|
});
|
|
693
|
+
continue;
|
|
408
694
|
}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
total_duration_ms: Date.now() - start,
|
|
425
|
-
dry_run: effectiveOptions.dry_run !== false,
|
|
426
|
-
};
|
|
427
|
-
}
|
|
428
|
-
const tracksToRun = trackFilter ?? TRACK_ORDER;
|
|
429
|
-
const trackResults = [];
|
|
430
|
-
const allObservations = [];
|
|
431
|
-
for (const track of tracksToRun) {
|
|
432
|
-
const def = TRACK_DEFINITIONS[track];
|
|
433
|
-
if (!def)
|
|
434
|
-
continue;
|
|
435
|
-
if (!isTrackApplicable(track, profile.tools)) {
|
|
436
|
-
const isExpected = track !== 'core' && (platformProfile?.expected_tracks.includes(track) ?? false);
|
|
437
|
-
trackResults.push({
|
|
438
|
-
track,
|
|
439
|
-
status: isExpected ? 'expected' : 'skip',
|
|
440
|
-
label: def.label,
|
|
441
|
-
scenarios: [],
|
|
442
|
-
skipped_scenarios: def.scenarios,
|
|
443
|
-
observations: [],
|
|
444
|
-
duration_ms: 0,
|
|
445
|
-
});
|
|
446
|
-
continue;
|
|
447
|
-
}
|
|
448
|
-
const trackStart = Date.now();
|
|
449
|
-
const applicable = (0, orchestrator_1.getApplicableScenarios)(profile.tools, def.scenarios);
|
|
450
|
-
const skipped = def.scenarios.filter(s => !applicable.includes(s));
|
|
451
|
-
// Track is relevant (agent has some related tools) but no scenarios match
|
|
452
|
-
// the specific tool combinations. Report as pass with an observation.
|
|
453
|
-
if (applicable.length === 0) {
|
|
454
|
-
const relevantTools = TRACK_RELEVANCE[track].filter(t => profile.tools.includes(t));
|
|
455
|
-
const observations = [
|
|
456
|
-
{
|
|
457
|
-
category: 'completeness',
|
|
695
|
+
// Run each applicable scenario for this track
|
|
696
|
+
const results = [];
|
|
697
|
+
for (const scenario of applicable) {
|
|
698
|
+
// Check for abort between scenarios
|
|
699
|
+
signal?.throwIfAborted();
|
|
700
|
+
const result = await (0, agent_tester_1.testAgent)(agentUrl, scenario, effectiveOptions);
|
|
701
|
+
results.push(result);
|
|
702
|
+
}
|
|
703
|
+
const observations = collectObservations(track, results, profile);
|
|
704
|
+
// Detect auth-only failures when running without auth
|
|
705
|
+
const hasAuth = !!effectiveOptions.auth;
|
|
706
|
+
const authSkippedScenarios = !hasAuth ? results.filter(r => isAuthOnlyFailure(r)).map(r => r.scenario) : [];
|
|
707
|
+
if (authSkippedScenarios.length > 0) {
|
|
708
|
+
observations.push({
|
|
709
|
+
category: 'auth',
|
|
458
710
|
severity: 'info',
|
|
459
711
|
track,
|
|
460
|
-
message:
|
|
461
|
-
`
|
|
462
|
-
evidence: {
|
|
463
|
-
}
|
|
464
|
-
|
|
712
|
+
message: `${authSkippedScenarios.length} scenario(s) require authentication: ${authSkippedScenarios.join(', ')}. ` +
|
|
713
|
+
`Re-run with --auth to test.`,
|
|
714
|
+
evidence: { scenarios: authSkippedScenarios },
|
|
715
|
+
});
|
|
716
|
+
}
|
|
465
717
|
allObservations.push(...observations);
|
|
718
|
+
const status = computeTrackStatus(results, skipped.length, hasAuth);
|
|
719
|
+
const hasDeterministicScenario = applicable.some(s => s.startsWith('deterministic_') || s === 'controller_validation');
|
|
720
|
+
const mode = hasDeterministicScenario ? 'deterministic' : 'observational';
|
|
466
721
|
trackResults.push({
|
|
467
722
|
track,
|
|
468
|
-
status
|
|
723
|
+
status,
|
|
469
724
|
label: def.label,
|
|
470
|
-
scenarios:
|
|
725
|
+
scenarios: results,
|
|
471
726
|
skipped_scenarios: skipped,
|
|
472
727
|
observations,
|
|
473
728
|
duration_ms: Date.now() - trackStart,
|
|
729
|
+
mode,
|
|
474
730
|
});
|
|
475
|
-
continue;
|
|
476
|
-
}
|
|
477
|
-
// Run each applicable scenario for this track
|
|
478
|
-
const results = [];
|
|
479
|
-
for (const scenario of applicable) {
|
|
480
|
-
const result = await (0, agent_tester_1.testAgent)(agentUrl, scenario, effectiveOptions);
|
|
481
|
-
results.push(result);
|
|
482
731
|
}
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
observations
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
732
|
+
// Build platform coherence result if platform type was declared
|
|
733
|
+
let platformCoherence;
|
|
734
|
+
if (platformProfile) {
|
|
735
|
+
const findings = platformProfile.checkCoherence(profile);
|
|
736
|
+
const missingTracks = platformProfile.expected_tracks.filter(t => !isTrackApplicable(t, profile.tools) && t !== 'core');
|
|
737
|
+
// Add coherence findings as observations
|
|
738
|
+
for (const finding of findings) {
|
|
739
|
+
allObservations.push({
|
|
740
|
+
category: 'coherence',
|
|
741
|
+
severity: finding.severity,
|
|
742
|
+
message: `${finding.expected} — ${finding.actual}. ${finding.guidance}`,
|
|
743
|
+
evidence: { platform_type: platformProfile.type },
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
platformCoherence = {
|
|
747
|
+
platform_type: platformProfile.type,
|
|
748
|
+
label: platformProfile.label,
|
|
749
|
+
expected_tracks: platformProfile.expected_tracks,
|
|
750
|
+
missing_tracks: missingTracks,
|
|
751
|
+
findings,
|
|
752
|
+
coherent: findings.filter(f => f.severity === 'error' || f.severity === 'warning').length === 0 &&
|
|
753
|
+
missingTracks.length === 0,
|
|
754
|
+
};
|
|
496
755
|
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
756
|
+
const summary = buildSummary(trackResults);
|
|
757
|
+
return {
|
|
758
|
+
agent_url: agentUrl,
|
|
759
|
+
agent_profile: profile,
|
|
760
|
+
tracks: trackResults,
|
|
761
|
+
summary,
|
|
762
|
+
observations: allObservations,
|
|
763
|
+
platform_coherence: platformCoherence,
|
|
764
|
+
controller_detected: controllerDetection.detected,
|
|
765
|
+
controller_scenarios: controllerDetection.detected ? controllerDetection.scenarios : undefined,
|
|
766
|
+
tested_at: new Date().toISOString(),
|
|
767
|
+
total_duration_ms: Date.now() - start,
|
|
768
|
+
dry_run: effectiveOptions.dry_run !== false,
|
|
769
|
+
};
|
|
508
770
|
}
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
// Add coherence findings as observations
|
|
515
|
-
for (const finding of findings) {
|
|
516
|
-
allObservations.push({
|
|
517
|
-
category: 'coherence',
|
|
518
|
-
severity: finding.severity,
|
|
519
|
-
message: `${finding.expected} — ${finding.actual}. ${finding.guidance}`,
|
|
520
|
-
evidence: { platform_type: platformProfile.type },
|
|
521
|
-
});
|
|
771
|
+
finally {
|
|
772
|
+
if (timeoutId !== undefined)
|
|
773
|
+
clearTimeout(timeoutId);
|
|
774
|
+
if (onExternalAbort && externalSignal) {
|
|
775
|
+
externalSignal.removeEventListener('abort', onExternalAbort);
|
|
522
776
|
}
|
|
523
|
-
platformCoherence = {
|
|
524
|
-
platform_type: platformProfile.type,
|
|
525
|
-
label: platformProfile.label,
|
|
526
|
-
expected_tracks: platformProfile.expected_tracks,
|
|
527
|
-
missing_tracks: missingTracks,
|
|
528
|
-
findings,
|
|
529
|
-
coherent: findings.filter(f => f.severity === 'error' || f.severity === 'warning').length === 0 &&
|
|
530
|
-
missingTracks.length === 0,
|
|
531
|
-
};
|
|
532
777
|
}
|
|
533
|
-
const summary = buildSummary(trackResults);
|
|
534
|
-
return {
|
|
535
|
-
agent_url: agentUrl,
|
|
536
|
-
agent_profile: profile,
|
|
537
|
-
tracks: trackResults,
|
|
538
|
-
summary,
|
|
539
|
-
observations: allObservations,
|
|
540
|
-
platform_coherence: platformCoherence,
|
|
541
|
-
tested_at: new Date().toISOString(),
|
|
542
|
-
total_duration_ms: Date.now() - start,
|
|
543
|
-
dry_run: effectiveOptions.dry_run !== false,
|
|
544
|
-
};
|
|
545
778
|
}
|
|
546
779
|
function buildSummary(tracks) {
|
|
547
780
|
const passed = tracks.filter(t => t.status === 'pass').length;
|