fix(examples): multi_platform_seller passes storyboards (drops continue-on-error)#508
Merged
Merged
Conversation
…storyboard gate now blocking The two mock platforms behind PlatformRouter (MockGuaranteedPlatform, MockNonGuaranteedPlatform) were silently failing the AdCP storyboard suite since PR #490 — masked by ``continue-on-error: true`` on the CI job. Pre-fix counts: tenant-a 7 passed / 8 failed / 44 skipped (cascade from create failures), tenant-b 12 passed / 6 failed / 41 skipped. Wire-contract alignments: * ``create_media_buy``: read ``packages[].product_id`` (singular, per ``schemas/3.0.6/media-buy/package-request.json``) instead of the non-existent ``packages[].products`` array. ``budget`` is a flat number, not a nested ``{total: ...}``. * ``sync_creatives``: response items use ``{creative_id, action: "created", status: "approved"}`` per ``schemas/3.0.6/creative/sync-creatives-response.json``. The old ``approval_status`` key wasn't a valid field. * ``get_media_buys``: implemented on both mocks (was missing, causing tenant-b's "'media_buys' is a required property"). Echoes ``targeting_overlay`` persisted at create / update time so the ``inventory_list_targeting`` storyboard can verify round-trip. * ``update_media_buy``: validates ``packages[].package_id`` against the buy's persisted packages and raises ``PACKAGE_NOT_FOUND`` (recovery=correctable) on unknown ids; persists ``targeting_overlay`` + ``measurement_terms`` patches for round-trip. * Both mocks now check buyer-proposed ``measurement_terms`` and raise ``TERMS_REJECTED`` when ``max_variance_percent < 5`` or ``measurement_window`` is outside ``c3``/``c7`` — mirrors the v3 reference seller's policy in ``examples/seller_agent.py``. * MockNonGuaranteedPlatform now starts buys in ``pending_creatives`` and advances to ``active`` via ``pending_start`` only after creatives sync, so the ``pending_creatives_to_start`` storyboard passes. The state machine doesn't permit a direct pending_creatives → active jump. * ``MEDIA_BUY_NOT_FOUND`` recovery changed from ``terminal`` to ``correctable`` to match spec semantics — the buyer can retry with a fresh id. Storyboards skipped by capability gate (correct behavior, no change): ``proposal_finalize`` is gated by ``media_buy.supports_proposals``. Neither mock declares it because neither implements the proposal lifecycle. The gate stays as-is until ProposalManager v1.5 ships. CI: drops ``continue-on-error: true`` from the ``storyboard-multi-platform-seller`` job. Future drift in the mocks or the spec now blocks merge. Artifact upload still runs on ``if: always()`` so failures remain debuggable. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 tasks
Closed
3 tasks
5 tasks
bokelley
added a commit
that referenced
this pull request
May 4, 2026
…ummary (closes #510) (#521) Both MockGuaranteedPlatform and MockNonGuaranteedPlatform were missing list_creatives entirely. The storyboard step ``creative_fate_after_cancellation/list_creatives_before_cancel`` fails for both tenants on the post-#508 artifact because the wire contract requires query_summary on every list_creatives response. Fix: each mock now persists creatives at sync_creatives time into an in-memory library keyed by creative_id, and list_creatives returns them with the required ``query_summary`` (total_matching, returned) and ``pagination`` (has_more=False, total_count) blocks. Per-item Creative shape projects to the schemas/3.0.6/creative/list-creatives-response.json contract: {creative_id, name, format_id, status: "approved", created_date, updated_date}. Auto-approval mirrors the existing sync_creatives policy on both mocks. Pagination is intentionally trivial (returns the full library) — the storyboard catalog is small and modeling cursor-based paging adds noise without illustrating new platform-shape concerns. Validated: ListCreativesResponse.model_validate accepts the output on both mocks; full pytest suite stays green (3760 passed). Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
8 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The two mock platforms behind PlatformRouter (
MockGuaranteedPlatform,MockNonGuaranteedPlatform) have been silently failing the AdCP storyboard suite since PR #490 — masked bycontinue-on-error: trueon the CI job. This PR aligns the mocks with the v3.0.6 wire contract and drops the masking so future drift blocks merge.Pre-fix counts (from
.storyboard-baseline/, last main artifact):MockGuaranteedPlatform)MockNonGuaranteedPlatform)The high skip counts are cascades — once
create_media_buyfailed, every downstream step in the same scenario was skipped. Fixing the create path unblocks dozens more.Failure types and fixes
INVALID_REQUEST[packages[].products]: package.products is emptycreate_media_buyreadspackages[].product_id(singular, perschemas/3.0.6/media-buy/package-request.json) and resolves against the in-memory catalog.budgetis a flat number, not nested.'media_buys' is a required propertyonget_media_buysget_media_buyson both mocks (was missing). Echoes back persistedtargeting_overlayso theinventory_list_targetinground-trip passes.sync_creativesresponseoneOfvalidation failed{creative_id, action: "created", status: "approved"}perschemas/3.0.6/creative/sync-creatives-response.json. Oldapproval_statuskey wasn't a valid field.pending_creatives_to_start: expectedpending_creatives, gotactivepending_creativesand advancespending_creatives → pending_start → activeafter creatives sync. (The state machine doesn't permit a direct jump.)TERMS_REJECTEDnot raised on aggressivemeasurement_termsmeasurement_terms.billing_measurementand raiseTERMS_REJECTEDwhenmax_variance_percent < 5ormeasurement_windowis outsidec3/c7— mirrorsexamples/seller_agent.py.update_unknown_package: succeeded silently (actual: null)update_media_buynow validatespackages[].package_idagainst the buy's persisted packages and raisesPACKAGE_NOT_FOUND(recovery=correctable). Also persiststargeting_overlay/measurement_termspatches.MEDIA_BUY_NOT_FOUNDrecovery semanticsterminaltocorrectable— the buyer can retry with a fresh id.Skipped (correctly) —
proposal_finalizeNot in
summary.storyboards_executedfor either tenant. Gated by themedia_buy.supports_proposalscapability flag, which neither mock declares because neither implements the proposal lifecycle. The gate stays as-is — keeping the mocks honest about what they support, not faking storyboard pass. Upstream JS team will revisit when ProposalManager v1.5 ships.CI gate
Drops
continue-on-error: truefrom thestoryboard-multi-platform-sellerjob. Future regressions in the mocks or the spec wire shape now block merge. Artifact upload still runs onif: always()so failures remain debuggable.Test plan
ruff check src/ examples/multi_platform_seller/— cleanmypy src/adcp/— clean (777 source files)pytest tests/ -x -q— 3760 passed, 32 skipped, 1 xfailedPRODUCT_NOT_FOUND,TERMS_REJECTED,PACKAGE_NOT_FOUND,MEDIA_BUY_NOT_FOUND) raises the right code; happy path lifecycle (pending_creatives → pending_start → activeafter sync) advances correctly;targeting_overlayround-trips throughget_media_buys.Notes / upstream observations
AdcpErrorto a text-only payload (code[field]: message) instead of populatingstructuredContent.adcp_error.code. This is documented as a known limitation insrc/adcp/server/translate.py(# Bridge, not endpointblock) — the storyboard runner's/adcp_error/codeJSON-pointer check therefore reportsactual: mcp_errorfor some error-code assertions even when the platform raises the rightAdcpError. Out of scope for this PR; tracked by the FastMCP_make_error_resultmigration.🤖 Generated with Claude Code