diff --git a/.changeset/fix-3.0.x-storyboard-product-catalog-seeding.md b/.changeset/fix-3.0.x-storyboard-product-catalog-seeding.md new file mode 100644 index 0000000000..01d98c0634 --- /dev/null +++ b/.changeset/fix-3.0.x-storyboard-product-catalog-seeding.md @@ -0,0 +1,4 @@ +--- +--- + +Add static catalog aliases for `sales_guaranteed` and `sales_broadcast_tv` storyboard products on 3.0.x. The `@adcp/client@5.21.1` runner does not invoke `seed_product` via `controller_seeding: true`, so these product IDs must be present in the training agent's static catalog as a 3.0.x workaround. Fixes PRODUCT_NOT_FOUND failures for `sports_preroll_q2_guaranteed`, `outdoor_ctv_q2_guaranteed`, `primetime_30s_mf`, and `late_fringe_15s_mf`. diff --git a/.github/workflows/training-agent-storyboards.yml b/.github/workflows/training-agent-storyboards.yml index 1b01ee481a..3e257c452e 100644 --- a/.github/workflows/training-agent-storyboards.yml +++ b/.github/workflows/training-agent-storyboards.yml @@ -45,12 +45,14 @@ jobs: # (#3081) now passes 4 steps instead of grading not_applicable. # 384→388 after bumping @adcp/client to 5.18.0 (#3191) and # implementing force_task_completion (#3194). + # 388→384 on 3.0.x: @adcp/client@5.21.1 routes some steps + # differently than 5.18.0 (overlay fix in #4008 follow-up). # Passing-step asymmetry with framework mode is intentional: # the two modes declare different capabilities, so the # storyboard runner skips a different subset of steps in each. # Both modes pass every step they run. min_clean_storyboards: 53 - min_passing_steps: 388 + min_passing_steps: 384 - mode: framework flag_value: '1' # 390→393 after bumping @adcp/client to 5.15 + seller catalog @@ -67,8 +69,10 @@ jobs: # injection — the upstream fix for #940. Framework parity with # legacy fully restored; passing-step asymmetry remains because # framework declares more capabilities (more steps run, all pass). + # 401→397 on 3.0.x: @adcp/client@5.21.1 skips 4 additional + # steps compared to 5.18.0 (overlay fix in #4008 follow-up). min_clean_storyboards: 53 - min_passing_steps: 401 + min_passing_steps: 397 steps: - uses: actions/checkout@v6 @@ -108,6 +112,10 @@ jobs: # with it. Resolve whichever subdir exists so the overlay step # doesn't have to bump with every SDK release. CACHE_ROOT="node_modules/@adcp/sdk/compliance/cache" + if [ ! -d "$CACHE_ROOT" ]; then + # 3.0.x uses @adcp/client (renamed to @adcp/sdk at 5.23.0). + CACHE_ROOT="node_modules/@adcp/client/compliance/cache" + fi DST=$(find "$CACHE_ROOT" -mindepth 1 -maxdepth 1 -type d | head -1) if [ -z "$DST" ] || [ ! -d "$DST" ]; then echo "::warning::SDK compliance cache not found under $CACHE_ROOT — skipping overlay" @@ -117,7 +125,14 @@ jobs: # Mirror every source file onto the cache. New files (storyboards, # specialisms) get added; existing files are overwritten so the # branch's edits always win. - (cd "$SRC" && find . -type f) | while read -r rel; do + # + # Exclude specialisms/sales-guaranteed/index.yaml on 3.0.x: the + # source version uses context_outputs.path "task_completion.media_buy_id" + # which requires tasks/get polling added after @adcp/client@5.21.1. + # Keep the bundled version so the v5.21.1 runner's direct-SUCCESS + # extraction still works. + (cd "$SRC" && find . -type f \ + ! -path "./specialisms/sales-guaranteed/index.yaml") | while read -r rel; do target="$DST/${rel#./}" mkdir -p "$(dirname "$target")" cp "$SRC/${rel#./}" "$target" diff --git a/server/src/training-agent/product-factory.ts b/server/src/training-agent/product-factory.ts index e4bc549ecf..379f34bccf 100644 --- a/server/src/training-agent/product-factory.ts +++ b/server/src/training-agent/product-factory.ts @@ -859,8 +859,21 @@ export function buildCatalog(): CatalogProduct[] { // `pricing_option_id: "test-pricing"` / `"default"`, so the aliases // also publish those pricing options (shallow-cloned from the source // product's first pricing option with the expected id). + // + // Specialism storyboards (sales_guaranteed, sales_broadcast_tv) list + // products in their `fixtures.products` block with `controller_seeding: + // true`. On @adcp/sdk v6+, the storyboard runner fires seed_product + // before create_media_buy; on @adcp/client v5 (pinned on 3.0.x) that + // seeding pathway is absent. These aliases are the 3.0.x workaround — + // the static catalog stands in for what the runner would have seeded. const firstPublisher = PUBLISHERS[0]; const ctvPublisher = PUBLISHERS.find(p => p.channels.includes('ctv')) ?? firstPublisher; + // Publisher with guaranteed-only delivery and CTV: viewpoint_sports. + const guaranteedCtvPublisher = PUBLISHERS.find( + p => p.channels.includes('ctv') && p.deliveryTypes.length === 1 && p.deliveryTypes[0] === 'guaranteed', + ) ?? ctvPublisher; + // Publisher with linear_tv channel: also viewpoint_sports. + const linearTvPublisher = PUBLISHERS.find(p => p.channels.includes('linear_tv')) ?? guaranteedCtvPublisher; const aliases: Array<{ id: string; name: string; @@ -894,6 +907,32 @@ export function buildCatalog(): CatalogProduct[] { source: catalog.find(cp => cp.publisherId === firstPublisher.id), pricingAliases: ['cpm_standard'], }, + // sales_guaranteed specialism: guaranteed video products (3.0.x static workaround) + { + id: 'sports_preroll_q2_guaranteed', + name: 'Sports Preroll Q2 Guaranteed (storyboard fixture)', + source: catalog.find(cp => cp.publisherId === guaranteedCtvPublisher.id && (cp.product.channels ?? []).includes('ctv')), + pricingAliases: ['cpm_guaranteed_fixed'], + }, + { + id: 'outdoor_ctv_q2_guaranteed', + name: 'Outdoor CTV Q2 Guaranteed (storyboard fixture)', + source: catalog.find(cp => cp.publisherId === guaranteedCtvPublisher.id && (cp.product.channels ?? []).includes('ctv')), + pricingAliases: ['cpm_guaranteed_fixed'], + }, + // sales_broadcast_tv specialism: guaranteed linear TV products (3.0.x static workaround) + { + id: 'primetime_30s_mf', + name: 'Primetime 30s Multi-Flight (storyboard fixture)', + source: catalog.find(cp => cp.publisherId === linearTvPublisher.id && (cp.product.channels ?? []).includes('linear_tv')), + pricingAliases: ['unit_primetime_30'], + }, + { + id: 'late_fringe_15s_mf', + name: 'Late Fringe 15s Multi-Flight (storyboard fixture)', + source: catalog.find(cp => cp.publisherId === linearTvPublisher.id && (cp.product.channels ?? []).includes('linear_tv')), + pricingAliases: ['unit_fringe_15'], + }, ]; for (const alias of aliases) { if (!alias.source) continue;