Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/gate-creative-library-storyboard.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"adcontextprotocol": patch
---

Gate the `creative_fate_after_cancellation` compliance storyboard on sellers advertising `creative.has_creative_library: true`. Sellers without declared creative-library support now exclude this scenario from runnable coverage, which can correct compliance badge and completeness-score denominators.
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
id: media_buy_seller/creative_fate_after_cancellation
version: "1.0.0"
version: "1.0.1"
title: "Creative lifecycle is decoupled from media buy lifecycle"
category: media_buy_seller
summary: "Validates that canceling a media buy releases package-creative assignments but leaves the underlying creatives in the library with their review state intact, and that buyers can reuse released creatives on a new buy."
track: media_buy

requires_capability:
path: creative.has_creative_library
equals: true

required_tools:
- get_products
- create_media_buy
Expand Down
63 changes: 63 additions & 0 deletions tests/sdk-runner-capability-gates.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,69 @@ test('inventory-list storyboards skip sellers that declare property-list support
}
});

test('creative fate storyboard requires advertised creative library support', async () => {
const storyboardPath = path.join(
__dirname,
'..',
'static',
'compliance',
'source',
'protocols',
'media-buy',
'scenarios',
'creative_fate_after_cancellation.yaml'
);
const storyboard = YAML.parse(fs.readFileSync(storyboardPath, 'utf8'));
const tools = ['get_adcp_capabilities', ...storyboard.required_tools];

assert.deepEqual(storyboard.requires_capability, {
path: 'creative.has_creative_library',
equals: true,
});

const unsupportedProfiles = [
{
rawCapabilities: { creative: { has_creative_library: false } },
errorPattern: /creative\.has_creative_library/,
},
{
rawCapabilities: {},
errorPattern: /agent did not declare support/,
},
];

for (const { rawCapabilities, errorPattern } of unsupportedProfiles) {
const result = await runStoryboard('https://agent.example/mcp', storyboard, {
_profile: { tools, raw_capabilities: rawCapabilities },
agentTools: tools,
});

assert.equal(result.overall_passed, true);
assert.equal(result.failed_count, 0);
assert.equal(result.skipped_count, 1);
assert.equal(result.phases.length, 1);
assert.equal(result.phases[0].phase_id, 'capability_unsupported');
assert.equal(result.phases[0].steps[0].skip_reason, 'capability_unsupported');
assert.match(result.phases[0].steps[0].error, errorPattern);
}

const supported = await runStoryboard(
'https://agent.example/mcp',
{ ...storyboard, prerequisites: undefined, phases: [] },
{
_profile: {
tools,
raw_capabilities: { creative: { has_creative_library: true } },
},
agentTools: tools,
}
);

assert.equal(supported.overall_passed, true);
assert.equal(supported.phases[0].phase_id, 'no_phases');
assert.equal(supported.phases[0].steps[0].skip_reason, 'no_phases');
});

test('inventory-list no-match requires canonical rejection and fails accepted buys', async () => {
const storyboardPath = path.join(
__dirname,
Expand Down
Loading