@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
|
@@ -16,10 +16,13 @@ exports.testCampaignGovernance = testCampaignGovernance;
|
|
|
16
16
|
exports.testCampaignGovernanceDenied = testCampaignGovernanceDenied;
|
|
17
17
|
exports.testCampaignGovernanceConditions = testCampaignGovernanceConditions;
|
|
18
18
|
exports.testCampaignGovernanceDelivery = testCampaignGovernanceDelivery;
|
|
19
|
+
exports.testSellerGovernanceContext = testSellerGovernanceContext;
|
|
19
20
|
exports.hasGovernanceTools = hasGovernanceTools;
|
|
20
21
|
exports.hasCampaignGovernanceTools = hasCampaignGovernanceTools;
|
|
21
22
|
const client_1 = require("../client");
|
|
22
23
|
const capabilities_1 = require("../../utils/capabilities");
|
|
24
|
+
const stubs_1 = require("../stubs");
|
|
25
|
+
const mcp_1 = require("../../protocols/mcp");
|
|
23
26
|
const GovernanceMiddleware_1 = require("../../core/GovernanceMiddleware");
|
|
24
27
|
// Property list tools
|
|
25
28
|
const PROPERTY_LIST_TOOLS = [
|
|
@@ -46,9 +49,9 @@ const CONTENT_STANDARDS_TOOLS = [
|
|
|
46
49
|
*/
|
|
47
50
|
async function testGovernancePropertyLists(agentUrl, options) {
|
|
48
51
|
const steps = [];
|
|
49
|
-
const client = (0, client_1.
|
|
52
|
+
const client = (0, client_1.getOrCreateClient)(agentUrl, options);
|
|
50
53
|
// Discover agent profile
|
|
51
|
-
const { profile, step: profileStep } = await (0, client_1.
|
|
54
|
+
const { profile, step: profileStep } = await (0, client_1.getOrDiscoverProfile)(client, options);
|
|
52
55
|
steps.push(profileStep);
|
|
53
56
|
if (!profileStep.passed) {
|
|
54
57
|
return { steps, profile };
|
|
@@ -250,9 +253,9 @@ async function testGovernancePropertyLists(agentUrl, options) {
|
|
|
250
253
|
*/
|
|
251
254
|
async function testGovernanceContentStandards(agentUrl, options) {
|
|
252
255
|
const steps = [];
|
|
253
|
-
const client = (0, client_1.
|
|
256
|
+
const client = (0, client_1.getOrCreateClient)(agentUrl, options);
|
|
254
257
|
// Discover agent profile
|
|
255
|
-
const { profile, step: profileStep } = await (0, client_1.
|
|
258
|
+
const { profile, step: profileStep } = await (0, client_1.getOrDiscoverProfile)(client, options);
|
|
256
259
|
steps.push(profileStep);
|
|
257
260
|
if (!profileStep.passed) {
|
|
258
261
|
return { steps, profile };
|
|
@@ -445,8 +448,8 @@ async function testGovernanceContentStandards(agentUrl, options) {
|
|
|
445
448
|
*/
|
|
446
449
|
async function testPropertyListFilters(agentUrl, options) {
|
|
447
450
|
const steps = [];
|
|
448
|
-
const client = (0, client_1.
|
|
449
|
-
const { profile, step: profileStep } = await (0, client_1.
|
|
451
|
+
const client = (0, client_1.getOrCreateClient)(agentUrl, options);
|
|
452
|
+
const { profile, step: profileStep } = await (0, client_1.getOrDiscoverProfile)(client, options);
|
|
450
453
|
steps.push(profileStep);
|
|
451
454
|
if (!profileStep.passed) {
|
|
452
455
|
return { steps, profile };
|
|
@@ -628,9 +631,9 @@ const CAMPAIGN_GOVERNANCE_TOOLS = [
|
|
|
628
631
|
*/
|
|
629
632
|
async function testCampaignGovernance(agentUrl, options) {
|
|
630
633
|
const steps = [];
|
|
631
|
-
const client = (0, client_1.
|
|
634
|
+
const client = (0, client_1.getOrCreateClient)(agentUrl, options);
|
|
632
635
|
// Discover agent profile
|
|
633
|
-
const { profile, step: profileStep } = await (0, client_1.
|
|
636
|
+
const { profile, step: profileStep } = await (0, client_1.getOrDiscoverProfile)(client, options);
|
|
634
637
|
steps.push(profileStep);
|
|
635
638
|
if (!profileStep.passed) {
|
|
636
639
|
return { steps, profile };
|
|
@@ -649,7 +652,7 @@ async function testCampaignGovernance(agentUrl, options) {
|
|
|
649
652
|
}
|
|
650
653
|
profile.supports_governance = true;
|
|
651
654
|
const testPlanId = `test-plan-${Date.now()}`;
|
|
652
|
-
const
|
|
655
|
+
const testMediaBuyId = `test-mb-${Date.now()}`;
|
|
653
656
|
const callerUrl = 'https://test-orchestrator.example.com';
|
|
654
657
|
const flightStart = new Date();
|
|
655
658
|
const flightEnd = new Date(flightStart.getTime() + 30 * 24 * 60 * 60 * 1000); // 30 days
|
|
@@ -712,15 +715,14 @@ async function testCampaignGovernance(agentUrl, options) {
|
|
|
712
715
|
}
|
|
713
716
|
// Step 2: check_governance (proposed, expecting approved)
|
|
714
717
|
let checkId;
|
|
718
|
+
let governanceContext;
|
|
715
719
|
if (profile.tools.includes('check_governance')) {
|
|
716
720
|
const { result, step } = await (0, client_1.runStep)('Check governance (proposed buy)', 'check_governance', async () => client.executeTask('check_governance', {
|
|
717
721
|
plan_id: testPlanId,
|
|
718
|
-
buyer_campaign_ref: testCampaignRef,
|
|
719
722
|
binding: 'proposed',
|
|
720
723
|
caller: callerUrl,
|
|
721
724
|
tool: 'create_media_buy',
|
|
722
725
|
payload: {
|
|
723
|
-
buyer_ref: `e2e-test-${Date.now()}`,
|
|
724
726
|
channel: 'display',
|
|
725
727
|
budget: { total: 1000, currency: 'USD' },
|
|
726
728
|
flight: {
|
|
@@ -733,6 +735,7 @@ async function testCampaignGovernance(agentUrl, options) {
|
|
|
733
735
|
if (result?.success && result?.data) {
|
|
734
736
|
const data = result.data;
|
|
735
737
|
checkId = data.check_id;
|
|
738
|
+
governanceContext = data.governance_context;
|
|
736
739
|
const status = data.status;
|
|
737
740
|
step.details = `Governance check: status=${status}, binding=${data.binding}, mode=${data.mode || 'unknown'}`;
|
|
738
741
|
step.response_preview = JSON.stringify({
|
|
@@ -743,12 +746,25 @@ async function testCampaignGovernance(agentUrl, options) {
|
|
|
743
746
|
explanation: data.explanation,
|
|
744
747
|
findings_count: data.findings?.length || 0,
|
|
745
748
|
expires_at: data.expires_at,
|
|
749
|
+
governance_context: data.governance_context ? '(present)' : '(absent)',
|
|
746
750
|
}, null, 2);
|
|
747
751
|
// Any status is valid — we're testing the protocol, not the policy
|
|
748
752
|
if (!['approved', 'denied', 'conditions', 'escalated'].includes(status)) {
|
|
749
753
|
step.passed = false;
|
|
750
754
|
step.error = `Unexpected governance status: ${status}`;
|
|
751
755
|
}
|
|
756
|
+
// Validate governance_context format if present
|
|
757
|
+
if (governanceContext !== undefined) {
|
|
758
|
+
if (typeof governanceContext !== 'string') {
|
|
759
|
+
step.warnings = [...(step.warnings || []), 'governance_context is not a string'];
|
|
760
|
+
}
|
|
761
|
+
else if (governanceContext.length > 4096) {
|
|
762
|
+
step.warnings = [
|
|
763
|
+
...(step.warnings || []),
|
|
764
|
+
`governance_context exceeds 4096 chars (${governanceContext.length})`,
|
|
765
|
+
];
|
|
766
|
+
}
|
|
767
|
+
}
|
|
752
768
|
}
|
|
753
769
|
else if (result && !result.success) {
|
|
754
770
|
step.passed = false;
|
|
@@ -756,27 +772,25 @@ async function testCampaignGovernance(agentUrl, options) {
|
|
|
756
772
|
}
|
|
757
773
|
steps.push(step);
|
|
758
774
|
}
|
|
759
|
-
// Step 3: report_plan_outcome (completed)
|
|
775
|
+
// Step 3: report_plan_outcome (completed) — thread governance_context
|
|
760
776
|
if (profile.tools.includes('report_plan_outcome') && checkId) {
|
|
761
|
-
const
|
|
777
|
+
const outcomeRequest = {
|
|
762
778
|
plan_id: testPlanId,
|
|
763
779
|
check_id: checkId,
|
|
764
|
-
buyer_campaign_ref: testCampaignRef,
|
|
765
780
|
outcome: 'completed',
|
|
781
|
+
governance_context: governanceContext || '',
|
|
766
782
|
seller_response: {
|
|
767
|
-
media_buy_id:
|
|
768
|
-
buyer_ref: `e2e-test-${Date.now()}`,
|
|
783
|
+
media_buy_id: testMediaBuyId,
|
|
769
784
|
packages: [
|
|
770
785
|
{
|
|
771
|
-
|
|
772
|
-
name: 'E2E Test Package',
|
|
773
|
-
budget: { total: 1000, currency: 'USD' },
|
|
786
|
+
budget: 1000,
|
|
774
787
|
},
|
|
775
788
|
],
|
|
776
789
|
},
|
|
777
|
-
}
|
|
790
|
+
};
|
|
791
|
+
const { result, step } = await (0, client_1.runStep)('Report plan outcome (completed)', 'report_plan_outcome', async () => client.executeTask('report_plan_outcome', outcomeRequest));
|
|
778
792
|
if (result?.success && result?.data) {
|
|
779
|
-
step.details =
|
|
793
|
+
step.details = `Outcome reported with governance_context=${governanceContext ? 'present' : 'absent'}`;
|
|
780
794
|
step.response_preview = JSON.stringify(result.data, null, 2);
|
|
781
795
|
}
|
|
782
796
|
else if (result && !result.success) {
|
|
@@ -785,11 +799,11 @@ async function testCampaignGovernance(agentUrl, options) {
|
|
|
785
799
|
}
|
|
786
800
|
steps.push(step);
|
|
787
801
|
}
|
|
788
|
-
// Step 4: get_plan_audit_logs
|
|
802
|
+
// Step 4: get_plan_audit_logs — query by media_buy_id
|
|
789
803
|
if (profile.tools.includes('get_plan_audit_logs')) {
|
|
790
804
|
const { result, step } = await (0, client_1.runStep)('Get plan audit logs', 'get_plan_audit_logs', async () => client.executeTask('get_plan_audit_logs', {
|
|
791
805
|
plan_ids: [testPlanId],
|
|
792
|
-
|
|
806
|
+
media_buy_id: testMediaBuyId,
|
|
793
807
|
include_entries: true,
|
|
794
808
|
}));
|
|
795
809
|
if (result?.success && result?.data) {
|
|
@@ -818,8 +832,8 @@ async function testCampaignGovernance(agentUrl, options) {
|
|
|
818
832
|
*/
|
|
819
833
|
async function testCampaignGovernanceDenied(agentUrl, options) {
|
|
820
834
|
const steps = [];
|
|
821
|
-
const client = (0, client_1.
|
|
822
|
-
const { profile, step: profileStep } = await (0, client_1.
|
|
835
|
+
const client = (0, client_1.getOrCreateClient)(agentUrl, options);
|
|
836
|
+
const { profile, step: profileStep } = await (0, client_1.getOrDiscoverProfile)(client, options);
|
|
823
837
|
steps.push(profileStep);
|
|
824
838
|
if (!profileStep.passed) {
|
|
825
839
|
return { steps, profile };
|
|
@@ -835,7 +849,6 @@ async function testCampaignGovernanceDenied(agentUrl, options) {
|
|
|
835
849
|
}
|
|
836
850
|
profile.supports_governance = true;
|
|
837
851
|
const testPlanId = `test-denied-plan-${Date.now()}`;
|
|
838
|
-
const testCampaignRef = `test-denied-campaign-${Date.now()}`;
|
|
839
852
|
const callerUrl = 'https://test-orchestrator.example.com';
|
|
840
853
|
const flightStart = new Date();
|
|
841
854
|
const flightEnd = new Date(flightStart.getTime() + 7 * 24 * 60 * 60 * 1000);
|
|
@@ -870,12 +883,10 @@ async function testCampaignGovernanceDenied(agentUrl, options) {
|
|
|
870
883
|
// Check governance with over-budget request
|
|
871
884
|
const { result: overBudgetResult, step: overBudgetStep } = await (0, client_1.runStep)('Check governance (over-budget, expecting denial or conditions)', 'check_governance', async () => client.executeTask('check_governance', {
|
|
872
885
|
plan_id: testPlanId,
|
|
873
|
-
buyer_campaign_ref: testCampaignRef,
|
|
874
886
|
binding: 'proposed',
|
|
875
887
|
caller: callerUrl,
|
|
876
888
|
tool: 'create_media_buy',
|
|
877
889
|
payload: {
|
|
878
|
-
buyer_ref: `e2e-overbudget-${Date.now()}`,
|
|
879
890
|
channel: 'display',
|
|
880
891
|
budget: { total: 50000, currency: 'USD' },
|
|
881
892
|
flight: {
|
|
@@ -885,8 +896,10 @@ async function testCampaignGovernanceDenied(agentUrl, options) {
|
|
|
885
896
|
countries: ['US'],
|
|
886
897
|
},
|
|
887
898
|
}));
|
|
899
|
+
let overBudgetGovernanceContext;
|
|
888
900
|
if (overBudgetResult?.success && overBudgetResult?.data) {
|
|
889
901
|
const data = overBudgetResult.data;
|
|
902
|
+
overBudgetGovernanceContext = data.governance_context;
|
|
890
903
|
overBudgetStep.details = `Over-budget check: status=${data.status}, explanation: ${data.explanation}`;
|
|
891
904
|
overBudgetStep.response_preview = JSON.stringify({
|
|
892
905
|
status: data.status,
|
|
@@ -897,6 +910,7 @@ async function testCampaignGovernanceDenied(agentUrl, options) {
|
|
|
897
910
|
explanation: f.explanation,
|
|
898
911
|
})),
|
|
899
912
|
conditions: data.conditions,
|
|
913
|
+
governance_context: data.governance_context ? '(present)' : '(absent)',
|
|
900
914
|
}, null, 2);
|
|
901
915
|
if (data.status === 'approved' && data.mode !== 'advisory' && data.mode !== 'audit') {
|
|
902
916
|
overBudgetStep.passed = false;
|
|
@@ -917,12 +931,10 @@ async function testCampaignGovernanceDenied(agentUrl, options) {
|
|
|
917
931
|
// Check governance with unauthorized market
|
|
918
932
|
const { result: geoResult, step: geoStep } = await (0, client_1.runStep)('Check governance (unauthorized market, expecting denial or conditions)', 'check_governance', async () => client.executeTask('check_governance', {
|
|
919
933
|
plan_id: testPlanId,
|
|
920
|
-
buyer_campaign_ref: testCampaignRef,
|
|
921
934
|
binding: 'proposed',
|
|
922
935
|
caller: callerUrl,
|
|
923
936
|
tool: 'create_media_buy',
|
|
924
937
|
payload: {
|
|
925
|
-
buyer_ref: `e2e-badgeo-${Date.now()}`,
|
|
926
938
|
channel: 'display',
|
|
927
939
|
budget: { total: 100, currency: 'USD' },
|
|
928
940
|
flight: {
|
|
@@ -954,13 +966,13 @@ async function testCampaignGovernanceDenied(agentUrl, options) {
|
|
|
954
966
|
geoStep.error = geoResult.error || 'check_governance failed';
|
|
955
967
|
}
|
|
956
968
|
steps.push(geoStep);
|
|
957
|
-
// Report failed outcome
|
|
969
|
+
// Report failed outcome — thread governance_context
|
|
958
970
|
if (profile.tools.includes('report_plan_outcome') && overBudgetResult?.data?.check_id) {
|
|
959
971
|
const { step: outcomeStep } = await (0, client_1.runStep)('Report failed outcome for denied check', 'report_plan_outcome', async () => client.executeTask('report_plan_outcome', {
|
|
960
972
|
plan_id: testPlanId,
|
|
961
973
|
check_id: overBudgetResult.data.check_id,
|
|
962
|
-
buyer_campaign_ref: testCampaignRef,
|
|
963
974
|
outcome: 'failed',
|
|
975
|
+
governance_context: overBudgetGovernanceContext || '',
|
|
964
976
|
error: {
|
|
965
977
|
code: 'governance_denied',
|
|
966
978
|
message: 'Action blocked by governance check',
|
|
@@ -978,8 +990,8 @@ async function testCampaignGovernanceDenied(agentUrl, options) {
|
|
|
978
990
|
*/
|
|
979
991
|
async function testCampaignGovernanceConditions(agentUrl, options) {
|
|
980
992
|
const steps = [];
|
|
981
|
-
const client = (0, client_1.
|
|
982
|
-
const { profile, step: profileStep } = await (0, client_1.
|
|
993
|
+
const client = (0, client_1.getOrCreateClient)(agentUrl, options);
|
|
994
|
+
const { profile, step: profileStep } = await (0, client_1.getOrDiscoverProfile)(client, options);
|
|
983
995
|
steps.push(profileStep);
|
|
984
996
|
if (!profileStep.passed) {
|
|
985
997
|
return { steps, profile };
|
|
@@ -995,7 +1007,6 @@ async function testCampaignGovernanceConditions(agentUrl, options) {
|
|
|
995
1007
|
}
|
|
996
1008
|
profile.supports_governance = true;
|
|
997
1009
|
const testPlanId = `test-conditions-plan-${Date.now()}`;
|
|
998
|
-
const testCampaignRef = `test-conditions-campaign-${Date.now()}`;
|
|
999
1010
|
const callerUrl = 'https://test-orchestrator.example.com';
|
|
1000
1011
|
const flightStart = new Date();
|
|
1001
1012
|
const flightEnd = new Date(flightStart.getTime() + 14 * 24 * 60 * 60 * 1000);
|
|
@@ -1035,12 +1046,10 @@ async function testCampaignGovernanceConditions(agentUrl, options) {
|
|
|
1035
1046
|
// First check: send budget that might trigger conditions
|
|
1036
1047
|
const { result: checkResult, step: checkStep } = await (0, client_1.runStep)('Check governance (may trigger conditions)', 'check_governance', async () => client.executeTask('check_governance', {
|
|
1037
1048
|
plan_id: testPlanId,
|
|
1038
|
-
buyer_campaign_ref: testCampaignRef,
|
|
1039
1049
|
binding: 'proposed',
|
|
1040
1050
|
caller: callerUrl,
|
|
1041
1051
|
tool: 'create_media_buy',
|
|
1042
1052
|
payload: {
|
|
1043
|
-
buyer_ref: `e2e-conditions-${Date.now()}`,
|
|
1044
1053
|
channel: 'display',
|
|
1045
1054
|
budget: { total: 4000, currency: 'USD' },
|
|
1046
1055
|
flight: {
|
|
@@ -1052,6 +1061,7 @@ async function testCampaignGovernanceConditions(agentUrl, options) {
|
|
|
1052
1061
|
}));
|
|
1053
1062
|
if (checkResult?.success && checkResult?.data) {
|
|
1054
1063
|
const data = checkResult.data;
|
|
1064
|
+
const initialGovernanceContext = data.governance_context;
|
|
1055
1065
|
checkStep.details = `Initial check: status=${data.status}`;
|
|
1056
1066
|
checkStep.response_preview = JSON.stringify({
|
|
1057
1067
|
check_id: data.check_id,
|
|
@@ -1062,8 +1072,9 @@ async function testCampaignGovernanceConditions(agentUrl, options) {
|
|
|
1062
1072
|
category_id: f.category_id,
|
|
1063
1073
|
explanation: f.explanation,
|
|
1064
1074
|
})),
|
|
1075
|
+
governance_context: initialGovernanceContext ? '(present)' : '(absent)',
|
|
1065
1076
|
}, null, 2);
|
|
1066
|
-
// If we got conditions, apply them and re-check
|
|
1077
|
+
// If we got conditions, apply them and re-check with governance_context round-trip
|
|
1067
1078
|
if (data.status === 'conditions' && data.conditions?.length > 0) {
|
|
1068
1079
|
const conditions = data.conditions;
|
|
1069
1080
|
const appliedConditions = conditions
|
|
@@ -1072,7 +1083,6 @@ async function testCampaignGovernanceConditions(agentUrl, options) {
|
|
|
1072
1083
|
checkStep.details += `. Conditions received: ${conditions.length} (${appliedConditions.length} machine-actionable)`;
|
|
1073
1084
|
// Build adjusted payload by applying conditions
|
|
1074
1085
|
const adjustedPayload = {
|
|
1075
|
-
buyer_ref: `e2e-conditions-adjusted-${Date.now()}`,
|
|
1076
1086
|
channel: 'display',
|
|
1077
1087
|
budget: { total: 4000, currency: 'USD' },
|
|
1078
1088
|
flight: {
|
|
@@ -1086,14 +1096,14 @@ async function testCampaignGovernanceConditions(agentUrl, options) {
|
|
|
1086
1096
|
(0, GovernanceMiddleware_1.setAtPath)(adjustedPayload, condition.field, condition.required_value);
|
|
1087
1097
|
}
|
|
1088
1098
|
}
|
|
1089
|
-
// Re-check with adjusted parameters
|
|
1099
|
+
// Re-check with adjusted parameters — thread governance_context from initial check
|
|
1090
1100
|
const { result: recheckResult, step: recheckStep } = await (0, client_1.runStep)('Re-check governance (after applying conditions)', 'check_governance', async () => client.executeTask('check_governance', {
|
|
1091
1101
|
plan_id: testPlanId,
|
|
1092
|
-
buyer_campaign_ref: testCampaignRef,
|
|
1093
1102
|
binding: 'proposed',
|
|
1094
1103
|
caller: callerUrl,
|
|
1095
1104
|
tool: 'create_media_buy',
|
|
1096
1105
|
payload: adjustedPayload,
|
|
1106
|
+
governance_context: initialGovernanceContext,
|
|
1097
1107
|
}));
|
|
1098
1108
|
if (recheckResult?.success && recheckResult?.data) {
|
|
1099
1109
|
const recheckData = recheckResult.data;
|
|
@@ -1102,6 +1112,7 @@ async function testCampaignGovernanceConditions(agentUrl, options) {
|
|
|
1102
1112
|
check_id: recheckData.check_id,
|
|
1103
1113
|
status: recheckData.status,
|
|
1104
1114
|
explanation: recheckData.explanation,
|
|
1115
|
+
governance_context: recheckData.governance_context ? '(present)' : '(absent)',
|
|
1105
1116
|
}, null, 2);
|
|
1106
1117
|
}
|
|
1107
1118
|
else if (recheckResult && !recheckResult.success) {
|
|
@@ -1126,8 +1137,8 @@ async function testCampaignGovernanceConditions(agentUrl, options) {
|
|
|
1126
1137
|
*/
|
|
1127
1138
|
async function testCampaignGovernanceDelivery(agentUrl, options) {
|
|
1128
1139
|
const steps = [];
|
|
1129
|
-
const client = (0, client_1.
|
|
1130
|
-
const { profile, step: profileStep } = await (0, client_1.
|
|
1140
|
+
const client = (0, client_1.getOrCreateClient)(agentUrl, options);
|
|
1141
|
+
const { profile, step: profileStep } = await (0, client_1.getOrDiscoverProfile)(client, options);
|
|
1131
1142
|
steps.push(profileStep);
|
|
1132
1143
|
if (!profileStep.passed) {
|
|
1133
1144
|
return { steps, profile };
|
|
@@ -1143,7 +1154,7 @@ async function testCampaignGovernanceDelivery(agentUrl, options) {
|
|
|
1143
1154
|
}
|
|
1144
1155
|
profile.supports_governance = true;
|
|
1145
1156
|
const testPlanId = `test-delivery-plan-${Date.now()}`;
|
|
1146
|
-
const
|
|
1157
|
+
const testMediaBuyId = `test-mb-${Date.now()}`;
|
|
1147
1158
|
const callerUrl = 'https://test-seller.example.com';
|
|
1148
1159
|
const flightStart = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
|
|
1149
1160
|
const flightEnd = new Date(Date.now() + 23 * 24 * 60 * 60 * 1000);
|
|
@@ -1178,10 +1189,9 @@ async function testCampaignGovernanceDelivery(agentUrl, options) {
|
|
|
1178
1189
|
const reportingEnd = new Date();
|
|
1179
1190
|
const { result: deliveryResult, step: deliveryStep } = await (0, client_1.runStep)('Check governance (delivery phase with metrics)', 'check_governance', async () => client.executeTask('check_governance', {
|
|
1180
1191
|
plan_id: testPlanId,
|
|
1181
|
-
buyer_campaign_ref: testCampaignRef,
|
|
1182
1192
|
binding: 'committed',
|
|
1183
1193
|
caller: callerUrl,
|
|
1184
|
-
media_buy_id:
|
|
1194
|
+
media_buy_id: testMediaBuyId,
|
|
1185
1195
|
phase: 'delivery',
|
|
1186
1196
|
planned_delivery: {
|
|
1187
1197
|
total_budget: 3000,
|
|
@@ -1203,8 +1213,10 @@ async function testCampaignGovernanceDelivery(agentUrl, options) {
|
|
|
1203
1213
|
pacing: 'on_track',
|
|
1204
1214
|
},
|
|
1205
1215
|
}));
|
|
1216
|
+
let deliveryGovernanceContext;
|
|
1206
1217
|
if (deliveryResult?.success && deliveryResult?.data) {
|
|
1207
1218
|
const data = deliveryResult.data;
|
|
1219
|
+
deliveryGovernanceContext = data.governance_context;
|
|
1208
1220
|
deliveryStep.details = `Delivery check: status=${data.status}, next_check=${data.next_check || 'not specified'}`;
|
|
1209
1221
|
deliveryStep.response_preview = JSON.stringify({
|
|
1210
1222
|
check_id: data.check_id,
|
|
@@ -1217,6 +1229,7 @@ async function testCampaignGovernanceDelivery(agentUrl, options) {
|
|
|
1217
1229
|
explanation: f.explanation,
|
|
1218
1230
|
})),
|
|
1219
1231
|
next_check: data.next_check,
|
|
1232
|
+
governance_context: data.governance_context ? '(present)' : '(absent)',
|
|
1220
1233
|
}, null, 2);
|
|
1221
1234
|
}
|
|
1222
1235
|
else if (deliveryResult && !deliveryResult.success) {
|
|
@@ -1224,14 +1237,14 @@ async function testCampaignGovernanceDelivery(agentUrl, options) {
|
|
|
1224
1237
|
deliveryStep.error = deliveryResult.error || 'Delivery-phase check_governance failed';
|
|
1225
1238
|
}
|
|
1226
1239
|
steps.push(deliveryStep);
|
|
1227
|
-
// Delivery-phase check with drift (overspend)
|
|
1240
|
+
// Delivery-phase check with drift (overspend) — thread governance_context from first check
|
|
1228
1241
|
const { result: driftResult, step: driftStep } = await (0, client_1.runStep)('Check governance (delivery phase with overspend drift)', 'check_governance', async () => client.executeTask('check_governance', {
|
|
1229
1242
|
plan_id: testPlanId,
|
|
1230
|
-
buyer_campaign_ref: testCampaignRef,
|
|
1231
1243
|
binding: 'committed',
|
|
1232
1244
|
caller: callerUrl,
|
|
1233
|
-
media_buy_id:
|
|
1245
|
+
media_buy_id: testMediaBuyId,
|
|
1234
1246
|
phase: 'delivery',
|
|
1247
|
+
governance_context: deliveryGovernanceContext,
|
|
1235
1248
|
planned_delivery: {
|
|
1236
1249
|
total_budget: 3000,
|
|
1237
1250
|
currency: 'USD',
|
|
@@ -1273,6 +1286,330 @@ async function testCampaignGovernanceDelivery(agentUrl, options) {
|
|
|
1273
1286
|
steps.push(driftStep);
|
|
1274
1287
|
return { steps, profile };
|
|
1275
1288
|
}
|
|
1289
|
+
/**
|
|
1290
|
+
* Test: Seller Governance Context Round-Trip
|
|
1291
|
+
*
|
|
1292
|
+
* Two-tier test:
|
|
1293
|
+
* 1. (Active) If seller supports register_governance: starts a stub governance
|
|
1294
|
+
* agent, registers it with the seller, creates a media buy, and verifies
|
|
1295
|
+
* the seller called check_governance on the stub with the correct
|
|
1296
|
+
* governance_context.
|
|
1297
|
+
* 2. (Passive) Verifies the seller persists governance_context from
|
|
1298
|
+
* create_media_buy and returns it on get_media_buys.
|
|
1299
|
+
*/
|
|
1300
|
+
async function testSellerGovernanceContext(agentUrl, options) {
|
|
1301
|
+
const steps = [];
|
|
1302
|
+
const client = (0, client_1.getOrCreateClient)(agentUrl, options);
|
|
1303
|
+
const { profile, step: profileStep } = await (0, client_1.getOrDiscoverProfile)(client, options);
|
|
1304
|
+
steps.push(profileStep);
|
|
1305
|
+
if (!profileStep.passed) {
|
|
1306
|
+
return { steps, profile };
|
|
1307
|
+
}
|
|
1308
|
+
// This scenario requires create_media_buy + get_media_buys (seller tools)
|
|
1309
|
+
if (!profile.tools.includes('create_media_buy') || !profile.tools.includes('get_media_buys')) {
|
|
1310
|
+
steps.push({
|
|
1311
|
+
step: 'Seller governance_context support check',
|
|
1312
|
+
passed: false,
|
|
1313
|
+
duration_ms: 0,
|
|
1314
|
+
error: 'Agent requires create_media_buy + get_media_buys for governance_context persistence testing',
|
|
1315
|
+
});
|
|
1316
|
+
return { steps, profile };
|
|
1317
|
+
}
|
|
1318
|
+
// Need get_products to create a valid media buy
|
|
1319
|
+
if (!profile.tools.includes('get_products')) {
|
|
1320
|
+
steps.push({
|
|
1321
|
+
step: 'Seller governance_context support check',
|
|
1322
|
+
passed: false,
|
|
1323
|
+
duration_ms: 0,
|
|
1324
|
+
error: 'Agent requires get_products for governance_context persistence testing',
|
|
1325
|
+
});
|
|
1326
|
+
return { steps, profile };
|
|
1327
|
+
}
|
|
1328
|
+
// Step 1: Get products
|
|
1329
|
+
const { result: productsResult, step: productsStep } = await (0, client_1.runStep)('Fetch products for governance context test', 'get_products', async () => client.executeTask('get_products', {
|
|
1330
|
+
buying_mode: 'brief',
|
|
1331
|
+
brief: options.brief || 'display advertising',
|
|
1332
|
+
brand: options.brand || { domain: 'test.example.com' },
|
|
1333
|
+
}));
|
|
1334
|
+
steps.push(productsStep);
|
|
1335
|
+
const products = productsResult?.data?.products;
|
|
1336
|
+
if (!products?.length) {
|
|
1337
|
+
return { steps, profile };
|
|
1338
|
+
}
|
|
1339
|
+
// Pick first product with pricing
|
|
1340
|
+
const product = products[0];
|
|
1341
|
+
const pricingOptions = product.pricing_options;
|
|
1342
|
+
const pricing = pricingOptions?.[0];
|
|
1343
|
+
if (!pricing) {
|
|
1344
|
+
steps.push({
|
|
1345
|
+
step: 'Select product for governance context test',
|
|
1346
|
+
passed: false,
|
|
1347
|
+
duration_ms: 0,
|
|
1348
|
+
error: 'No products with pricing options available',
|
|
1349
|
+
});
|
|
1350
|
+
return { steps, profile };
|
|
1351
|
+
}
|
|
1352
|
+
// Step 2: Resolve an account to use for the test
|
|
1353
|
+
const account = await resolveTestAccount(client, profile, options);
|
|
1354
|
+
// Step 3: If seller supports register_governance, start stub and register it
|
|
1355
|
+
let stub = null;
|
|
1356
|
+
let stubUrl = null;
|
|
1357
|
+
let governanceContext = null;
|
|
1358
|
+
const planId = `plan-comply-gc-${Date.now()}`;
|
|
1359
|
+
// register_governance is from AdCP PR #1644 — merged but schemas not yet deployed.
|
|
1360
|
+
// Once deployed and synced, sellers that implement it will get the active stub test.
|
|
1361
|
+
const hasRegisterGovernance = profile.tools.includes('register_governance');
|
|
1362
|
+
if (hasRegisterGovernance) {
|
|
1363
|
+
stub = new stubs_1.GovernanceAgentStub();
|
|
1364
|
+
try {
|
|
1365
|
+
const info = await stub.startHttps();
|
|
1366
|
+
stubUrl = info.url;
|
|
1367
|
+
}
|
|
1368
|
+
catch {
|
|
1369
|
+
// HTTPS generation failed (no openssl?) — fall back to HTTP
|
|
1370
|
+
try {
|
|
1371
|
+
const info = await stub.start();
|
|
1372
|
+
stubUrl = info.url;
|
|
1373
|
+
}
|
|
1374
|
+
catch (err) {
|
|
1375
|
+
steps.push({
|
|
1376
|
+
step: 'Start governance agent stub',
|
|
1377
|
+
passed: false,
|
|
1378
|
+
duration_ms: 0,
|
|
1379
|
+
error: `Failed to start governance agent stub: ${err.message}`,
|
|
1380
|
+
});
|
|
1381
|
+
return { steps, profile };
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
steps.push({
|
|
1385
|
+
step: 'Start governance agent stub',
|
|
1386
|
+
passed: true,
|
|
1387
|
+
duration_ms: 0,
|
|
1388
|
+
details: `Stub running at ${stubUrl}`,
|
|
1389
|
+
});
|
|
1390
|
+
// Register the stub with the seller
|
|
1391
|
+
const { result: registerResult, step: registerStep } = await (0, client_1.runStep)('Register governance agent with seller', 'register_governance', async () => client.executeTask('register_governance', {
|
|
1392
|
+
accounts: [
|
|
1393
|
+
{
|
|
1394
|
+
account,
|
|
1395
|
+
governance_agents: [
|
|
1396
|
+
{
|
|
1397
|
+
url: stubUrl,
|
|
1398
|
+
authentication: {
|
|
1399
|
+
schemes: ['Bearer'],
|
|
1400
|
+
credentials: stub.authToken,
|
|
1401
|
+
},
|
|
1402
|
+
},
|
|
1403
|
+
],
|
|
1404
|
+
},
|
|
1405
|
+
],
|
|
1406
|
+
}));
|
|
1407
|
+
steps.push(registerStep);
|
|
1408
|
+
if (!registerResult?.success) {
|
|
1409
|
+
// Registration failed — fall through to passive test
|
|
1410
|
+
steps.push({
|
|
1411
|
+
step: 'Governance agent registration',
|
|
1412
|
+
passed: true,
|
|
1413
|
+
duration_ms: 0,
|
|
1414
|
+
details: 'register_governance failed — falling back to passive governance_context persistence test',
|
|
1415
|
+
warnings: ['Cannot verify seller calls governance agent — register_governance returned an error'],
|
|
1416
|
+
});
|
|
1417
|
+
}
|
|
1418
|
+
else {
|
|
1419
|
+
// Get governance_context from the stub (simulate buyer's proposed check)
|
|
1420
|
+
try {
|
|
1421
|
+
const checkResult = await (0, mcp_1.callMCPTool)(stubUrl, 'check_governance', {
|
|
1422
|
+
plan_id: planId,
|
|
1423
|
+
binding: 'proposed',
|
|
1424
|
+
caller: 'buyer',
|
|
1425
|
+
tool: 'create_media_buy',
|
|
1426
|
+
payload: {},
|
|
1427
|
+
});
|
|
1428
|
+
const parsed = JSON.parse(checkResult.content[0].text);
|
|
1429
|
+
governanceContext = parsed.governance_context;
|
|
1430
|
+
steps.push({
|
|
1431
|
+
step: 'Obtain governance_context from stub (proposed check)',
|
|
1432
|
+
passed: true,
|
|
1433
|
+
duration_ms: 0,
|
|
1434
|
+
details: `governance_context received (${governanceContext?.length ?? 0} chars)`,
|
|
1435
|
+
});
|
|
1436
|
+
}
|
|
1437
|
+
catch (err) {
|
|
1438
|
+
steps.push({
|
|
1439
|
+
step: 'Obtain governance_context from stub (proposed check)',
|
|
1440
|
+
passed: false,
|
|
1441
|
+
duration_ms: 0,
|
|
1442
|
+
error: `Failed to call check_governance on stub: ${err.message}`,
|
|
1443
|
+
});
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
else {
|
|
1448
|
+
steps.push({
|
|
1449
|
+
step: 'Check register_governance support',
|
|
1450
|
+
passed: true,
|
|
1451
|
+
duration_ms: 0,
|
|
1452
|
+
details: 'Seller does not support register_governance — running passive governance_context persistence test only',
|
|
1453
|
+
warnings: ['Cannot verify seller calls governance agent — register_governance not supported'],
|
|
1454
|
+
});
|
|
1455
|
+
}
|
|
1456
|
+
// Step 4: Create media buy WITH governance_context
|
|
1457
|
+
const testGovernanceContext = governanceContext || `test-gc-comply-${Date.now()}`;
|
|
1458
|
+
const flightStart = new Date();
|
|
1459
|
+
const flightEnd = new Date(flightStart.getTime() + 30 * 24 * 60 * 60 * 1000);
|
|
1460
|
+
const { result: createResult, step: createStep } = await (0, client_1.runStep)('Create media buy with governance_context', 'create_media_buy', async () => client.executeTask('create_media_buy', {
|
|
1461
|
+
account,
|
|
1462
|
+
brand: options.brand || { domain: 'test.example.com' },
|
|
1463
|
+
start_time: flightStart.toISOString(),
|
|
1464
|
+
end_time: flightEnd.toISOString(),
|
|
1465
|
+
plan_id: governanceContext ? planId : undefined,
|
|
1466
|
+
packages: [
|
|
1467
|
+
{
|
|
1468
|
+
product_id: product.product_id,
|
|
1469
|
+
pricing_option_id: pricing.pricing_option_id,
|
|
1470
|
+
budget: pricing.min_spend_per_package || 500,
|
|
1471
|
+
bid_price: (pricing.floor_price || pricing.fixed_price || 10) + 1,
|
|
1472
|
+
name: 'Governance context test package',
|
|
1473
|
+
},
|
|
1474
|
+
],
|
|
1475
|
+
governance_context: testGovernanceContext,
|
|
1476
|
+
}));
|
|
1477
|
+
if (!createResult?.success || !createResult?.data) {
|
|
1478
|
+
createStep.passed = false;
|
|
1479
|
+
createStep.error = createResult?.error || 'create_media_buy failed';
|
|
1480
|
+
steps.push(createStep);
|
|
1481
|
+
await stopStub(stub);
|
|
1482
|
+
return { steps, profile };
|
|
1483
|
+
}
|
|
1484
|
+
const mediaBuyId = createResult.data.media_buy_id;
|
|
1485
|
+
createStep.details = `Created media buy ${mediaBuyId} with governance_context`;
|
|
1486
|
+
steps.push(createStep);
|
|
1487
|
+
// Step 5: If stub is active, verify seller called check_governance(committed)
|
|
1488
|
+
if (stub && governanceContext && stubUrl) {
|
|
1489
|
+
// Poll the stub's call log for the committed check (100ms interval, 5s timeout)
|
|
1490
|
+
const pollStart = Date.now();
|
|
1491
|
+
const pollTimeout = 5000;
|
|
1492
|
+
const pollInterval = 100;
|
|
1493
|
+
while (Date.now() - pollStart < pollTimeout) {
|
|
1494
|
+
const calls = stub.getCallsForTool('check_governance');
|
|
1495
|
+
if (calls.some(c => c.params.binding === 'committed'))
|
|
1496
|
+
break;
|
|
1497
|
+
await new Promise(r => setTimeout(r, pollInterval));
|
|
1498
|
+
}
|
|
1499
|
+
const committedCalls = stub.getCallsForTool('check_governance').filter(c => c.params.binding === 'committed');
|
|
1500
|
+
const { step: callbackStep } = await (0, client_1.runStep)('Verify seller called check_governance(committed) on governance agent', 'check_governance (callback)', async () => {
|
|
1501
|
+
if (committedCalls.length === 0) {
|
|
1502
|
+
throw new Error('Seller did not call check_governance(committed) on the registered governance agent. ' +
|
|
1503
|
+
'Sellers MUST call check_governance with binding="committed" before executing a media buy.');
|
|
1504
|
+
}
|
|
1505
|
+
const callWithContext = committedCalls.find(c => c.params.governance_context === governanceContext);
|
|
1506
|
+
if (!callWithContext) {
|
|
1507
|
+
throw new Error(`Seller called check_governance(committed) but with wrong governance_context. ` +
|
|
1508
|
+
`Expected "${governanceContext}", got: ${committedCalls.map(c => JSON.stringify(c.params.governance_context)).join(', ')}`);
|
|
1509
|
+
}
|
|
1510
|
+
});
|
|
1511
|
+
if (committedCalls.length > 0) {
|
|
1512
|
+
callbackStep.response_preview = JSON.stringify(committedCalls[0].params, null, 2);
|
|
1513
|
+
}
|
|
1514
|
+
steps.push(callbackStep);
|
|
1515
|
+
}
|
|
1516
|
+
// Step 6: Retrieve the media buy and check for governance_context persistence
|
|
1517
|
+
const { result: getResult, step: getStep } = await (0, client_1.runStep)('Verify governance_context persisted on get_media_buys', 'get_media_buys', async () => client.executeTask('get_media_buys', {
|
|
1518
|
+
media_buy_ids: [mediaBuyId],
|
|
1519
|
+
}));
|
|
1520
|
+
if (getResult?.success && getResult?.data) {
|
|
1521
|
+
const buys = getResult.data.media_buys || [];
|
|
1522
|
+
const buy = buys.find((b) => b.media_buy_id === mediaBuyId) ||
|
|
1523
|
+
(buys.length === 1 ? buys[0] : undefined);
|
|
1524
|
+
if (buy) {
|
|
1525
|
+
const returnedGC = buy.governance_context;
|
|
1526
|
+
if (returnedGC === testGovernanceContext) {
|
|
1527
|
+
getStep.details = 'governance_context persisted and returned correctly';
|
|
1528
|
+
}
|
|
1529
|
+
else if (returnedGC) {
|
|
1530
|
+
getStep.passed = false;
|
|
1531
|
+
getStep.error = `governance_context returned but value changed: expected "${testGovernanceContext}", got "${returnedGC}"`;
|
|
1532
|
+
}
|
|
1533
|
+
else {
|
|
1534
|
+
getStep.passed = false;
|
|
1535
|
+
getStep.error =
|
|
1536
|
+
'Seller did not return governance_context on get_media_buys. ' +
|
|
1537
|
+
'Sellers MUST persist governance_context from create_media_buy and include it on all subsequent responses.';
|
|
1538
|
+
}
|
|
1539
|
+
getStep.response_preview = JSON.stringify({
|
|
1540
|
+
media_buy_id: buy.media_buy_id,
|
|
1541
|
+
governance_context: returnedGC ? `(present, ${returnedGC.length} chars)` : '(absent)',
|
|
1542
|
+
status: buy.status,
|
|
1543
|
+
}, null, 2);
|
|
1544
|
+
}
|
|
1545
|
+
else if (buys.length === 0) {
|
|
1546
|
+
getStep.details =
|
|
1547
|
+
'get_media_buys returned 0 buys — agent may be stateless (test agent). Cannot verify governance_context persistence.';
|
|
1548
|
+
getStep.warnings = [
|
|
1549
|
+
'governance_context persistence could not be verified — agent does not persist media buys across requests',
|
|
1550
|
+
];
|
|
1551
|
+
}
|
|
1552
|
+
else {
|
|
1553
|
+
getStep.passed = false;
|
|
1554
|
+
getStep.error = `Media buy ${mediaBuyId} not found among ${buys.length} returned buys`;
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
else if (getResult && !getResult.success) {
|
|
1558
|
+
getStep.passed = false;
|
|
1559
|
+
getStep.error = getResult.error || 'get_media_buys failed';
|
|
1560
|
+
}
|
|
1561
|
+
steps.push(getStep);
|
|
1562
|
+
await stopStub(stub);
|
|
1563
|
+
return { steps, profile };
|
|
1564
|
+
}
|
|
1565
|
+
/**
|
|
1566
|
+
* Resolve a test account from the seller. Tries list_accounts first,
|
|
1567
|
+
* then sync_accounts, then falls back to a static account reference.
|
|
1568
|
+
*/
|
|
1569
|
+
async function resolveTestAccount(client, profile, options) {
|
|
1570
|
+
if (profile.tools.includes('list_accounts')) {
|
|
1571
|
+
try {
|
|
1572
|
+
const result = (await client.executeTask('list_accounts', {}));
|
|
1573
|
+
const accounts = result?.data?.accounts;
|
|
1574
|
+
if (accounts?.length && accounts[0]) {
|
|
1575
|
+
return { account_id: accounts[0].account_id };
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
catch {
|
|
1579
|
+
// Fall through
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
if (profile.tools.includes('sync_accounts')) {
|
|
1583
|
+
try {
|
|
1584
|
+
const result = (await client.executeTask('sync_accounts', {
|
|
1585
|
+
accounts: [
|
|
1586
|
+
{
|
|
1587
|
+
brand: options.brand || { domain: 'test.example.com' },
|
|
1588
|
+
operator: 'comply-test',
|
|
1589
|
+
billing: 'operator',
|
|
1590
|
+
sandbox: true,
|
|
1591
|
+
},
|
|
1592
|
+
],
|
|
1593
|
+
}));
|
|
1594
|
+
const accounts = result?.data?.accounts;
|
|
1595
|
+
if (accounts?.length && accounts[0] && accounts[0].account_id) {
|
|
1596
|
+
return { account_id: accounts[0].account_id };
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
catch {
|
|
1600
|
+
// Fall through
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
return { account_id: 'test-gc-acct' };
|
|
1604
|
+
}
|
|
1605
|
+
async function stopStub(stub) {
|
|
1606
|
+
if (stub) {
|
|
1607
|
+
try {
|
|
1608
|
+
await stub.stop();
|
|
1609
|
+
}
|
|
1610
|
+
catch { }
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1276
1613
|
/**
|
|
1277
1614
|
* Check if agent has any governance protocol tools
|
|
1278
1615
|
*/
|