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