@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
|
@@ -64,7 +64,6 @@ function buildCreateMediaBuyRequest(product, pricingOption, options, extras = {}
|
|
|
64
64
|
const isAuction = !('fixed_price' in pricingOption) &&
|
|
65
65
|
(pricingOption.floor_price !== undefined || pricingOption.price_guidance !== undefined);
|
|
66
66
|
const packageRequest = {
|
|
67
|
-
buyer_ref: `pkg-test-${Date.now()}`,
|
|
68
67
|
product_id: product.product_id,
|
|
69
68
|
budget,
|
|
70
69
|
pricing_option_id: pricingOption.pricing_option_id,
|
|
@@ -82,7 +81,6 @@ function buildCreateMediaBuyRequest(product, pricingOption, options, extras = {}
|
|
|
82
81
|
packageRequest.creative_ids = extras.creative_ids;
|
|
83
82
|
}
|
|
84
83
|
return {
|
|
85
|
-
buyer_ref: `e2e-test-${Date.now()}`,
|
|
86
84
|
account: (0, client_1.resolveAccount)(options),
|
|
87
85
|
brand: (0, client_1.resolveBrand)(options),
|
|
88
86
|
start_time: startTime.toISOString(),
|
|
@@ -148,7 +146,7 @@ function buildSyncCreativeFromManifest(manifest, fallbackFormatId) {
|
|
|
148
146
|
*/
|
|
149
147
|
async function testCreateMediaBuy(agentUrl, options) {
|
|
150
148
|
const steps = [];
|
|
151
|
-
const client = (0, client_1.
|
|
149
|
+
const client = (0, client_1.getOrCreateClient)(agentUrl, options);
|
|
152
150
|
// First run discovery
|
|
153
151
|
const { steps: discoverySteps, profile } = await (0, discovery_1.testDiscovery)(agentUrl, options);
|
|
154
152
|
steps.push(...discoverySteps);
|
|
@@ -218,9 +216,15 @@ async function testCreateMediaBuy(agentUrl, options) {
|
|
|
218
216
|
const packages = (mediaBuy.packages || nested?.packages);
|
|
219
217
|
createStep.details = `Created media buy: ${mediaBuyId}, status: ${status}`;
|
|
220
218
|
createStep.created_id = mediaBuyId;
|
|
219
|
+
const confirmedAt = (mediaBuy.confirmed_at ?? nested?.confirmed_at);
|
|
220
|
+
const revision = (mediaBuy.revision ?? nested?.revision);
|
|
221
|
+
const validActions = (mediaBuy.valid_actions ?? nested?.valid_actions);
|
|
221
222
|
createStep.response_preview = JSON.stringify({
|
|
222
223
|
media_buy_id: mediaBuyId,
|
|
223
224
|
status,
|
|
225
|
+
confirmed_at: confirmedAt,
|
|
226
|
+
revision,
|
|
227
|
+
valid_actions: validActions,
|
|
224
228
|
packages_count: packages?.length,
|
|
225
229
|
pricing_model: pricingOption.pricing_model,
|
|
226
230
|
product_name: product.name,
|
|
@@ -239,7 +243,7 @@ async function testCreateMediaBuy(agentUrl, options) {
|
|
|
239
243
|
*/
|
|
240
244
|
async function testFullSalesFlow(agentUrl, options) {
|
|
241
245
|
const steps = [];
|
|
242
|
-
const client = (0, client_1.
|
|
246
|
+
const client = (0, client_1.getOrCreateClient)(agentUrl, options);
|
|
243
247
|
// Run create media buy flow first
|
|
244
248
|
const { steps: createSteps, profile, mediaBuyId } = await testCreateMediaBuy(agentUrl, options);
|
|
245
249
|
steps.push(...createSteps);
|
|
@@ -346,9 +350,9 @@ async function testFullSalesFlow(agentUrl, options) {
|
|
|
346
350
|
*/
|
|
347
351
|
async function testCreativeSync(agentUrl, options) {
|
|
348
352
|
const steps = [];
|
|
349
|
-
const client = (0, client_1.
|
|
353
|
+
const client = (0, client_1.getOrCreateClient)(agentUrl, options);
|
|
350
354
|
// Discover profile
|
|
351
|
-
const { profile, step: profileStep } = await (0, client_1.
|
|
355
|
+
const { profile, step: profileStep } = await (0, client_1.getOrDiscoverProfile)(client, options);
|
|
352
356
|
steps.push(profileStep);
|
|
353
357
|
if (!profile.tools.includes('sync_creatives')) {
|
|
354
358
|
steps.push({
|
|
@@ -462,7 +466,7 @@ async function testCreativeSync(agentUrl, options) {
|
|
|
462
466
|
*/
|
|
463
467
|
async function testCreativeInline(agentUrl, options) {
|
|
464
468
|
const steps = [];
|
|
465
|
-
const client = (0, client_1.
|
|
469
|
+
const client = (0, client_1.getOrCreateClient)(agentUrl, options);
|
|
466
470
|
// Discovery first
|
|
467
471
|
const { steps: discoverySteps, profile } = await (0, discovery_1.testDiscovery)(agentUrl, options);
|
|
468
472
|
steps.push(...discoverySteps);
|
|
@@ -607,7 +611,7 @@ async function testCreativeInline(agentUrl, options) {
|
|
|
607
611
|
*/
|
|
608
612
|
async function testCreativeReference(agentUrl, options) {
|
|
609
613
|
const steps = [];
|
|
610
|
-
const client = (0, client_1.
|
|
614
|
+
const client = (0, client_1.getOrCreateClient)(agentUrl, options);
|
|
611
615
|
const { steps: discoverySteps, profile } = await (0, discovery_1.testDiscovery)(agentUrl, options);
|
|
612
616
|
steps.push(...discoverySteps);
|
|
613
617
|
if (!profile?.tools.includes('build_creative') || !profile.tools.includes('sync_creatives')) {
|
|
@@ -797,9 +801,9 @@ async function resolveAccountForAudiences(options, tools, listAccounts) {
|
|
|
797
801
|
*/
|
|
798
802
|
async function testSyncAudiences(agentUrl, options) {
|
|
799
803
|
const steps = [];
|
|
800
|
-
const client = (0, client_1.
|
|
804
|
+
const client = (0, client_1.getOrCreateClient)(agentUrl, options);
|
|
801
805
|
// Discover agent profile
|
|
802
|
-
const { profile, step: profileStep } = await (0, client_1.
|
|
806
|
+
const { profile, step: profileStep } = await (0, client_1.getOrDiscoverProfile)(client, options);
|
|
803
807
|
steps.push(profileStep);
|
|
804
808
|
if (!profileStep.passed) {
|
|
805
809
|
return { steps, profile };
|
|
@@ -874,7 +878,19 @@ async function testSyncAudiences(agentUrl, options) {
|
|
|
874
878
|
action: testAudience?.action,
|
|
875
879
|
status: testAudience?.status,
|
|
876
880
|
uploaded_count: testAudience?.uploaded_count,
|
|
881
|
+
matched_count: testAudience?.matched_count,
|
|
882
|
+
effective_match_rate: testAudience?.effective_match_rate,
|
|
883
|
+
match_breakdown: testAudience?.match_breakdown,
|
|
877
884
|
}, null, 2);
|
|
885
|
+
// Advisory: report match breakdown availability
|
|
886
|
+
if (testAudience?.status === 'ready') {
|
|
887
|
+
if (testAudience.match_breakdown) {
|
|
888
|
+
createStep.details += `, match_breakdown: ${testAudience.match_breakdown.length} ID type(s)`;
|
|
889
|
+
}
|
|
890
|
+
if (testAudience.effective_match_rate != null) {
|
|
891
|
+
createStep.details += `, effective_match_rate: ${(testAudience.effective_match_rate * 100).toFixed(1)}%`;
|
|
892
|
+
}
|
|
893
|
+
}
|
|
878
894
|
}
|
|
879
895
|
else if (createResult && !createResult.success) {
|
|
880
896
|
createStep.passed = false;
|
|
@@ -928,13 +944,15 @@ function extractStatus(data) {
|
|
|
928
944
|
*/
|
|
929
945
|
async function testMediaBuyLifecycle(agentUrl, options) {
|
|
930
946
|
const steps = [];
|
|
931
|
-
const client = (0, client_1.
|
|
947
|
+
const client = (0, client_1.getOrCreateClient)(agentUrl, options);
|
|
932
948
|
// Create a media buy to work with
|
|
933
949
|
const { steps: createSteps, profile, mediaBuyId } = await testCreateMediaBuy(agentUrl, options);
|
|
934
950
|
steps.push(...createSteps);
|
|
935
951
|
if (!mediaBuyId || !profile?.tools.includes('update_media_buy')) {
|
|
936
952
|
return { steps, profile };
|
|
937
953
|
}
|
|
954
|
+
// Track revisions across steps for monotonicity check
|
|
955
|
+
const revisions = [];
|
|
938
956
|
// Step 1: Pause the media buy
|
|
939
957
|
const { result: pauseResult, step: pauseStep } = await (0, client_1.runStep)('Pause media buy', 'update_media_buy', async () => client.updateMediaBuy({
|
|
940
958
|
media_buy_id: mediaBuyId,
|
|
@@ -944,8 +962,11 @@ async function testMediaBuyLifecycle(agentUrl, options) {
|
|
|
944
962
|
if (pauseResult?.success && pauseResult?.data) {
|
|
945
963
|
const data = pauseResult.data;
|
|
946
964
|
const status = extractStatus(data);
|
|
965
|
+
const pauseRevision = data.revision;
|
|
966
|
+
if (pauseRevision !== undefined)
|
|
967
|
+
revisions.push({ step: 'pause', revision: pauseRevision });
|
|
947
968
|
pauseStep.details = `Paused media buy, status: ${status}`;
|
|
948
|
-
pauseStep.response_preview = JSON.stringify({ media_buy_id: mediaBuyId, status }, null, 2);
|
|
969
|
+
pauseStep.response_preview = JSON.stringify({ media_buy_id: mediaBuyId, status, revision: pauseRevision }, null, 2);
|
|
949
970
|
if (status && status !== 'paused') {
|
|
950
971
|
pauseStep.warnings = [`Expected status 'paused', got '${status}'`];
|
|
951
972
|
}
|
|
@@ -964,8 +985,11 @@ async function testMediaBuyLifecycle(agentUrl, options) {
|
|
|
964
985
|
if (resumeResult?.success && resumeResult?.data) {
|
|
965
986
|
const data = resumeResult.data;
|
|
966
987
|
const status = extractStatus(data);
|
|
988
|
+
const resumeRevision = data.revision;
|
|
989
|
+
if (resumeRevision !== undefined)
|
|
990
|
+
revisions.push({ step: 'resume', revision: resumeRevision });
|
|
967
991
|
resumeStep.details = `Resumed media buy, status: ${status}`;
|
|
968
|
-
resumeStep.response_preview = JSON.stringify({ media_buy_id: mediaBuyId, status }, null, 2);
|
|
992
|
+
resumeStep.response_preview = JSON.stringify({ media_buy_id: mediaBuyId, status, revision: resumeRevision }, null, 2);
|
|
969
993
|
if (status && status !== 'active' && status !== 'pending_activation') {
|
|
970
994
|
resumeStep.warnings = [`Expected status 'active' or 'pending_activation', got '${status}'`];
|
|
971
995
|
}
|
|
@@ -975,10 +999,55 @@ async function testMediaBuyLifecycle(agentUrl, options) {
|
|
|
975
999
|
resumeStep.error = resumeResult.error || 'Resume operation failed';
|
|
976
1000
|
}
|
|
977
1001
|
steps.push(resumeStep);
|
|
978
|
-
// Step
|
|
1002
|
+
// Step 2b: Budget update — verify substantive field mutation
|
|
1003
|
+
// Find a package to update budget on
|
|
1004
|
+
let budgetPackageId;
|
|
1005
|
+
let originalBudget;
|
|
1006
|
+
if (profile.tools.includes('get_media_buys')) {
|
|
1007
|
+
const { result: fetchResult } = await (0, client_1.runStep)('Fetch packages for budget test', 'get_media_buys', async () => client.executeTask('get_media_buys', { media_buy_ids: [mediaBuyId] }));
|
|
1008
|
+
if (fetchResult?.success && fetchResult?.data) {
|
|
1009
|
+
const mbs = (fetchResult.data.media_buys || []);
|
|
1010
|
+
const mb = mbs.find((item) => item.media_buy_id === mediaBuyId) || mbs[0];
|
|
1011
|
+
const pkgs = (mb?.packages || []);
|
|
1012
|
+
if (pkgs[0]) {
|
|
1013
|
+
budgetPackageId = pkgs[0].package_id;
|
|
1014
|
+
originalBudget = pkgs[0].budget;
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
if (budgetPackageId && originalBudget !== undefined) {
|
|
1019
|
+
const newBudget = Math.round(originalBudget * 1.2 * 100) / 100; // 20% increase
|
|
1020
|
+
const { result: budgetResult, step: budgetStep } = await (0, client_1.runStep)('Update package budget', 'update_media_buy', async () => client.updateMediaBuy({
|
|
1021
|
+
media_buy_id: mediaBuyId,
|
|
1022
|
+
packages: [{ package_id: budgetPackageId, budget: newBudget }],
|
|
1023
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- intentional: test request bypasses strict typing
|
|
1024
|
+
}));
|
|
1025
|
+
if (budgetResult?.success && budgetResult?.data) {
|
|
1026
|
+
const data = budgetResult.data;
|
|
1027
|
+
const budgetRevision = data.revision;
|
|
1028
|
+
if (budgetRevision !== undefined)
|
|
1029
|
+
revisions.push({ step: 'budget_update', revision: budgetRevision });
|
|
1030
|
+
budgetStep.details = `Updated budget from $${originalBudget} to $${newBudget}`;
|
|
1031
|
+
budgetStep.response_preview = JSON.stringify({ media_buy_id: mediaBuyId, package_id: budgetPackageId, new_budget: newBudget, revision: budgetRevision }, null, 2);
|
|
1032
|
+
}
|
|
1033
|
+
else if (budgetResult && !budgetResult.success) {
|
|
1034
|
+
const error = budgetResult.error || '';
|
|
1035
|
+
if (error.includes('BUDGET_EXCEEDED') || error.includes('budget_exceeded')) {
|
|
1036
|
+
budgetStep.passed = true;
|
|
1037
|
+
budgetStep.details = `Agent rejected budget increase with BUDGET_EXCEEDED — acceptable`;
|
|
1038
|
+
}
|
|
1039
|
+
else {
|
|
1040
|
+
budgetStep.passed = false;
|
|
1041
|
+
budgetStep.error = budgetResult.error || 'Budget update failed';
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
steps.push(budgetStep);
|
|
1045
|
+
}
|
|
1046
|
+
// Step 3: Get status and check valid_actions, confirmed_at, revision, history (if get_media_buys available)
|
|
979
1047
|
if (profile.tools.includes('get_media_buys')) {
|
|
980
1048
|
const { result: statusResult, step: statusStep } = await (0, client_1.runStep)('Get media buy status and valid_actions', 'get_media_buys', async () => client.executeTask('get_media_buys', {
|
|
981
1049
|
media_buy_ids: [mediaBuyId],
|
|
1050
|
+
include_history: 10,
|
|
982
1051
|
}));
|
|
983
1052
|
if (statusResult?.success && statusResult?.data) {
|
|
984
1053
|
const mediaBuys = (statusResult.data.media_buys || []);
|
|
@@ -989,11 +1058,30 @@ async function testMediaBuyLifecycle(agentUrl, options) {
|
|
|
989
1058
|
}
|
|
990
1059
|
else {
|
|
991
1060
|
const validActions = mediaBuy.valid_actions;
|
|
992
|
-
|
|
1061
|
+
const mbRevision = mediaBuy.revision;
|
|
1062
|
+
const mbConfirmedAt = mediaBuy.confirmed_at;
|
|
1063
|
+
const history = mediaBuy.history;
|
|
1064
|
+
const packages = (mediaBuy.packages || []);
|
|
1065
|
+
const hasCreativeDeadline = packages.some(p => p.creative_deadline) || !!mediaBuy.creative_deadline;
|
|
1066
|
+
// Validate history entry shape if present
|
|
1067
|
+
let historyValid = true;
|
|
1068
|
+
if (history?.length) {
|
|
1069
|
+
const missingTimestamp = history.some(h => !h.timestamp);
|
|
1070
|
+
const missingAction = history.some(h => !h.action);
|
|
1071
|
+
if (missingTimestamp || missingAction)
|
|
1072
|
+
historyValid = false;
|
|
1073
|
+
}
|
|
1074
|
+
statusStep.details = `Status: ${mediaBuy.status}, valid_actions: ${validActions ? validActions.join(', ') : 'not provided'}, revision: ${mbRevision ?? 'not provided'}`;
|
|
993
1075
|
statusStep.response_preview = JSON.stringify({
|
|
994
1076
|
media_buy_id: mediaBuy.media_buy_id,
|
|
995
1077
|
status: mediaBuy.status,
|
|
1078
|
+
confirmed_at: mbConfirmedAt,
|
|
1079
|
+
revision: mbRevision,
|
|
996
1080
|
valid_actions: validActions,
|
|
1081
|
+
history_entries: history?.length ?? 0,
|
|
1082
|
+
history_valid: historyValid,
|
|
1083
|
+
has_creative_deadline: hasCreativeDeadline,
|
|
1084
|
+
sandbox: mediaBuy.sandbox,
|
|
997
1085
|
}, null, 2);
|
|
998
1086
|
}
|
|
999
1087
|
}
|
|
@@ -1003,6 +1091,62 @@ async function testMediaBuyLifecycle(agentUrl, options) {
|
|
|
1003
1091
|
}
|
|
1004
1092
|
steps.push(statusStep);
|
|
1005
1093
|
}
|
|
1094
|
+
// Step 3b: Revision concurrency check — use actual stale revision from an earlier step
|
|
1095
|
+
const lastRevision = revisions.length > 0 ? revisions[revisions.length - 1].revision : undefined;
|
|
1096
|
+
const staleRevision = revisions.length >= 2 ? revisions[0].revision : -1;
|
|
1097
|
+
const { result: conflictResult, step: conflictStep } = await (0, client_1.runStep)('Update with stale revision (expect CONFLICT)', 'update_media_buy', async () => client.updateMediaBuy({
|
|
1098
|
+
media_buy_id: mediaBuyId,
|
|
1099
|
+
revision: staleRevision,
|
|
1100
|
+
end_time: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
|
|
1101
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- intentional: test request bypasses strict typing
|
|
1102
|
+
}));
|
|
1103
|
+
if (conflictResult && !conflictResult.success) {
|
|
1104
|
+
const error = conflictResult.error || '';
|
|
1105
|
+
if (error.includes('CONFLICT') || error.includes('conflict') || error.includes('revision')) {
|
|
1106
|
+
conflictStep.passed = true;
|
|
1107
|
+
conflictStep.details = `Correctly rejected stale revision ${staleRevision} with CONFLICT (current: ${lastRevision ?? 'unknown'})`;
|
|
1108
|
+
}
|
|
1109
|
+
else {
|
|
1110
|
+
// Agent rejected for another reason — still acceptable, revision may not be supported
|
|
1111
|
+
conflictStep.passed = true;
|
|
1112
|
+
conflictStep.details = `Agent rejected update: ${error}`;
|
|
1113
|
+
conflictStep.warnings = [
|
|
1114
|
+
'Agent did not return CONFLICT for stale revision — revision concurrency may not be supported',
|
|
1115
|
+
];
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
else if (conflictResult?.success) {
|
|
1119
|
+
// Agent accepted stale revision — revision concurrency not enforced
|
|
1120
|
+
conflictStep.passed = true;
|
|
1121
|
+
conflictStep.details = `Agent accepted stale revision ${staleRevision} — optimistic concurrency not enforced`;
|
|
1122
|
+
conflictStep.warnings = ['Agent does not enforce optimistic concurrency via revision numbers'];
|
|
1123
|
+
}
|
|
1124
|
+
else if (!conflictResult) {
|
|
1125
|
+
conflictStep.passed = false;
|
|
1126
|
+
conflictStep.error = 'No response from update_media_buy';
|
|
1127
|
+
}
|
|
1128
|
+
steps.push(conflictStep);
|
|
1129
|
+
// Step 3c: Check revision monotonicity
|
|
1130
|
+
if (revisions.length >= 2) {
|
|
1131
|
+
const monotonicStep = {
|
|
1132
|
+
step: 'Revision monotonicity check',
|
|
1133
|
+
task: 'update_media_buy',
|
|
1134
|
+
passed: true,
|
|
1135
|
+
duration_ms: 0,
|
|
1136
|
+
};
|
|
1137
|
+
const isMonotonic = revisions.every((r, i) => i === 0 || r.revision > revisions[i - 1].revision);
|
|
1138
|
+
if (isMonotonic) {
|
|
1139
|
+
monotonicStep.details = `Revisions are monotonically increasing: ${revisions.map(r => `${r.step}=${r.revision}`).join(' → ')}`;
|
|
1140
|
+
}
|
|
1141
|
+
else {
|
|
1142
|
+
monotonicStep.passed = true; // advisory, not a hard fail
|
|
1143
|
+
monotonicStep.details = `Revisions: ${revisions.map(r => `${r.step}=${r.revision}`).join(' → ')}`;
|
|
1144
|
+
monotonicStep.warnings = [
|
|
1145
|
+
`Revision numbers are not monotonically increasing — buyers depend on this for concurrency safety`,
|
|
1146
|
+
];
|
|
1147
|
+
}
|
|
1148
|
+
steps.push(monotonicStep);
|
|
1149
|
+
}
|
|
1006
1150
|
// Step 4: Cancel the media buy
|
|
1007
1151
|
const { result: cancelResult, step: cancelStep } = await (0, client_1.runStep)('Cancel media buy', 'update_media_buy', async () => client.updateMediaBuy({
|
|
1008
1152
|
media_buy_id: mediaBuyId,
|
|
@@ -1013,8 +1157,13 @@ async function testMediaBuyLifecycle(agentUrl, options) {
|
|
|
1013
1157
|
if (cancelResult?.success && cancelResult?.data) {
|
|
1014
1158
|
const data = cancelResult.data;
|
|
1015
1159
|
const status = extractStatus(data);
|
|
1160
|
+
const cancelRevision = data.revision;
|
|
1016
1161
|
cancelStep.details = `Canceled media buy, status: ${status}`;
|
|
1017
|
-
|
|
1162
|
+
const canceledBy = data.canceled_by;
|
|
1163
|
+
const canceledAt = data.canceled_at;
|
|
1164
|
+
if (cancelRevision !== undefined)
|
|
1165
|
+
revisions.push({ step: 'cancel', revision: cancelRevision });
|
|
1166
|
+
cancelStep.response_preview = JSON.stringify({ media_buy_id: mediaBuyId, status, revision: cancelRevision, canceled_by: canceledBy, canceled_at: canceledAt }, null, 2);
|
|
1018
1167
|
if (status && status !== 'canceled') {
|
|
1019
1168
|
cancelStep.warnings = [`Expected status 'canceled', got '${status}'`];
|
|
1020
1169
|
}
|
|
@@ -1040,7 +1189,7 @@ async function testMediaBuyLifecycle(agentUrl, options) {
|
|
|
1040
1189
|
*/
|
|
1041
1190
|
async function testTerminalStateEnforcement(agentUrl, options) {
|
|
1042
1191
|
const steps = [];
|
|
1043
|
-
const client = (0, client_1.
|
|
1192
|
+
const client = (0, client_1.getOrCreateClient)(agentUrl, options);
|
|
1044
1193
|
// Create and cancel a media buy
|
|
1045
1194
|
const { steps: createSteps, profile, mediaBuyId } = await testCreateMediaBuy(agentUrl, options);
|
|
1046
1195
|
steps.push(...createSteps);
|
|
@@ -1061,10 +1210,57 @@ async function testTerminalStateEnforcement(agentUrl, options) {
|
|
|
1061
1210
|
else if (cancelResult && !cancelResult.success) {
|
|
1062
1211
|
const error = cancelResult.error || '';
|
|
1063
1212
|
if (error.includes('NOT_CANCELLABLE') || error.includes('not_cancellable')) {
|
|
1064
|
-
// Agent doesn't support cancellation —
|
|
1213
|
+
// Agent doesn't support cancellation — try to find a completed buy instead
|
|
1065
1214
|
cancelStep.passed = true;
|
|
1066
|
-
cancelStep.details = 'Agent does not support cancellation —
|
|
1215
|
+
cancelStep.details = 'Agent does not support cancellation — will check for completed media buys instead';
|
|
1067
1216
|
steps.push(cancelStep);
|
|
1217
|
+
// Look for a completed media buy to test terminal state enforcement against
|
|
1218
|
+
if (profile.tools.includes('get_media_buys')) {
|
|
1219
|
+
const { result: completedResult, step: completedStep } = await (0, client_1.runStep)('Find completed media buy for terminal state test', 'get_media_buys', async () => client.executeTask('get_media_buys', {
|
|
1220
|
+
status_filter: ['completed'],
|
|
1221
|
+
pagination: { max_results: 1 },
|
|
1222
|
+
}));
|
|
1223
|
+
if (completedResult?.success && completedResult?.data) {
|
|
1224
|
+
const completedBuys = (completedResult.data.media_buys || []);
|
|
1225
|
+
if (completedBuys.length > 0) {
|
|
1226
|
+
const completedId = completedBuys[0].media_buy_id;
|
|
1227
|
+
completedStep.details = `Found completed media buy: ${completedId}`;
|
|
1228
|
+
steps.push(completedStep);
|
|
1229
|
+
// Try to update the completed media buy
|
|
1230
|
+
const { result: updateCompletedResult, step: updateCompletedStep } = await (0, client_1.runStep)('Update completed media buy (expect rejection)', 'update_media_buy', async () => client.updateMediaBuy({
|
|
1231
|
+
media_buy_id: completedId,
|
|
1232
|
+
paused: true,
|
|
1233
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- intentional: test request bypasses strict typing
|
|
1234
|
+
}));
|
|
1235
|
+
if (updateCompletedResult?.success) {
|
|
1236
|
+
updateCompletedStep.passed = false;
|
|
1237
|
+
updateCompletedStep.error =
|
|
1238
|
+
'Agent accepted update to completed media buy — should reject with INVALID_STATE';
|
|
1239
|
+
}
|
|
1240
|
+
else if (updateCompletedResult) {
|
|
1241
|
+
updateCompletedStep.passed = true;
|
|
1242
|
+
const err = updateCompletedResult.error || '';
|
|
1243
|
+
const hasCode = err.includes('INVALID_STATE') || err.includes('invalid_state');
|
|
1244
|
+
updateCompletedStep.details = hasCode
|
|
1245
|
+
? 'Correctly rejected update to completed media buy with INVALID_STATE'
|
|
1246
|
+
: `Correctly rejected update to completed media buy: ${err}`;
|
|
1247
|
+
if (!hasCode && err) {
|
|
1248
|
+
updateCompletedStep.warnings = ['Agent rejected the update but did not use INVALID_STATE error code'];
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
steps.push(updateCompletedStep);
|
|
1252
|
+
}
|
|
1253
|
+
else {
|
|
1254
|
+
completedStep.details = 'No completed media buys found — cannot test terminal state enforcement';
|
|
1255
|
+
steps.push(completedStep);
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
else {
|
|
1259
|
+
completedStep.passed = false;
|
|
1260
|
+
completedStep.error = completedResult?.error || 'Failed to query for completed media buys';
|
|
1261
|
+
steps.push(completedStep);
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1068
1264
|
return { steps, profile };
|
|
1069
1265
|
}
|
|
1070
1266
|
cancelStep.passed = false;
|
|
@@ -1072,29 +1268,29 @@ async function testTerminalStateEnforcement(agentUrl, options) {
|
|
|
1072
1268
|
}
|
|
1073
1269
|
steps.push(cancelStep);
|
|
1074
1270
|
// Try to pause the canceled media buy — should be rejected
|
|
1075
|
-
const { result:
|
|
1271
|
+
const { result: pauseTerminalResult, step: pauseTerminalStep } = await (0, client_1.runStep)('Update canceled media buy (expect rejection)', 'update_media_buy', async () => client.updateMediaBuy({
|
|
1076
1272
|
media_buy_id: mediaBuyId,
|
|
1077
1273
|
paused: true,
|
|
1078
1274
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- intentional: test request bypasses strict typing
|
|
1079
1275
|
}));
|
|
1080
|
-
if (
|
|
1081
|
-
|
|
1082
|
-
|
|
1276
|
+
if (pauseTerminalResult?.success) {
|
|
1277
|
+
pauseTerminalStep.passed = false;
|
|
1278
|
+
pauseTerminalStep.error = 'Agent accepted update to canceled media buy — should reject with INVALID_STATE';
|
|
1083
1279
|
}
|
|
1084
|
-
else if (
|
|
1280
|
+
else if (pauseTerminalResult) {
|
|
1085
1281
|
// Agent returned { success: false } — correct behavior
|
|
1086
|
-
|
|
1087
|
-
const error =
|
|
1282
|
+
pauseTerminalStep.passed = true;
|
|
1283
|
+
const error = pauseTerminalResult.error || '';
|
|
1088
1284
|
const hasExpectedCode = error.includes('INVALID_STATE') || error.includes('invalid_state');
|
|
1089
|
-
|
|
1285
|
+
pauseTerminalStep.details = hasExpectedCode
|
|
1090
1286
|
? 'Correctly rejected with INVALID_STATE'
|
|
1091
1287
|
: `Correctly rejected update to canceled media buy: ${error}`;
|
|
1092
1288
|
if (!hasExpectedCode && error) {
|
|
1093
|
-
|
|
1289
|
+
pauseTerminalStep.warnings = ['Agent rejected the update but did not use INVALID_STATE error code'];
|
|
1094
1290
|
}
|
|
1095
1291
|
}
|
|
1096
|
-
// else:
|
|
1097
|
-
steps.push(
|
|
1292
|
+
// else: result is undefined (exception thrown) — runStep already set passed=false and error
|
|
1293
|
+
steps.push(pauseTerminalStep);
|
|
1098
1294
|
// Try to cancel again — should also be rejected (or idempotent)
|
|
1099
1295
|
const { result: reCancelResult, step: reCancelStep } = await (0, client_1.runStep)('Cancel already-canceled media buy (expect rejection)', 'update_media_buy', async () => client.updateMediaBuy({
|
|
1100
1296
|
media_buy_id: mediaBuyId,
|
|
@@ -1112,6 +1308,37 @@ async function testTerminalStateEnforcement(agentUrl, options) {
|
|
|
1112
1308
|
reCancelStep.details = `Correctly rejected re-cancellation: ${error}`;
|
|
1113
1309
|
}
|
|
1114
1310
|
steps.push(reCancelStep);
|
|
1311
|
+
// Also check completed terminal state if get_media_buys is available
|
|
1312
|
+
if (profile.tools.includes('get_media_buys')) {
|
|
1313
|
+
const { result: completedResult } = await (0, client_1.runStep)('Find completed media buy', 'get_media_buys', async () => client.executeTask('get_media_buys', {
|
|
1314
|
+
status_filter: ['completed'],
|
|
1315
|
+
pagination: { max_results: 1 },
|
|
1316
|
+
}));
|
|
1317
|
+
if (completedResult?.success && completedResult?.data) {
|
|
1318
|
+
const completedBuys = (completedResult.data.media_buys || []);
|
|
1319
|
+
if (completedBuys.length > 0) {
|
|
1320
|
+
const completedId = completedBuys[0].media_buy_id;
|
|
1321
|
+
const { result: updateCompletedResult, step: updateCompletedStep } = await (0, client_1.runStep)('Update completed media buy (expect rejection)', 'update_media_buy', async () => client.updateMediaBuy({
|
|
1322
|
+
media_buy_id: completedId,
|
|
1323
|
+
paused: true,
|
|
1324
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- intentional: test request bypasses strict typing
|
|
1325
|
+
}));
|
|
1326
|
+
if (updateCompletedResult?.success) {
|
|
1327
|
+
updateCompletedStep.passed = false;
|
|
1328
|
+
updateCompletedStep.error = 'Agent accepted update to completed media buy — should reject with INVALID_STATE';
|
|
1329
|
+
}
|
|
1330
|
+
else if (updateCompletedResult) {
|
|
1331
|
+
updateCompletedStep.passed = true;
|
|
1332
|
+
const err = updateCompletedResult.error || '';
|
|
1333
|
+
const hasCode = err.includes('INVALID_STATE') || err.includes('invalid_state');
|
|
1334
|
+
updateCompletedStep.details = hasCode
|
|
1335
|
+
? 'Correctly rejected update to completed media buy with INVALID_STATE'
|
|
1336
|
+
: `Correctly rejected update to completed media buy: ${err}`;
|
|
1337
|
+
}
|
|
1338
|
+
steps.push(updateCompletedStep);
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1115
1342
|
return { steps, profile };
|
|
1116
1343
|
}
|
|
1117
1344
|
/**
|
|
@@ -1120,7 +1347,7 @@ async function testTerminalStateEnforcement(agentUrl, options) {
|
|
|
1120
1347
|
*/
|
|
1121
1348
|
async function testPackageLifecycle(agentUrl, options) {
|
|
1122
1349
|
const steps = [];
|
|
1123
|
-
const client = (0, client_1.
|
|
1350
|
+
const client = (0, client_1.getOrCreateClient)(agentUrl, options);
|
|
1124
1351
|
// Create a media buy
|
|
1125
1352
|
const { steps: createSteps, profile, mediaBuyId } = await testCreateMediaBuy(agentUrl, options);
|
|
1126
1353
|
steps.push(...createSteps);
|