Skip to content

fix(training-agent): event_source validation + cost_per_acquisition for performance_buy_flow#4654

Merged
bokelley merged 1 commit into
mainfrom
bokelley/4642-training-agent-performance-buy-flow
May 17, 2026
Merged

fix(training-agent): event_source validation + cost_per_acquisition for performance_buy_flow#4654
bokelley merged 1 commit into
mainfrom
bokelley/4642-training-agent-performance-buy-flow

Conversation

@bokelley
Copy link
Copy Markdown
Contributor

Summary

Close the two gaps that prevent the media_buy_seller/performance_buy_flow storyboard scenario (#4642) from passing against the training agent. The training agent already advertises media_buy.conversion_tracking, so the scenario runs against it (not not_applicable) — without these fixes the new rejection_unbound_event_source and attributed_delivery_reporting phases fail.

  • handleCreateMediaBuy validates optimization_goals[].event_sources[].event_source_id against the session's registered sources. Phantom ids reject with INVALID_REQUEST and error.field set to the literal JSONPath-lite shape packages[i].optimization_goals[j].event_sources[k].event_source_id the scenario asserts on. Silent acceptance would let a seller advertise conversion_tracking without being able to actually optimize against the event source the buyer thinks is bound — a façade.
  • get_media_buy_delivery totals now emit conversions and cost_per_acquisition = spend / conversions when simulate_delivery injected both. Sellers that don't compute conversion metrics simply omit the fields (no conversions injected → no fields emitted).

Lookup uses a new exported helper findEventSourceInSession(sessionKey, id) (session-local lookup with global fallback) in catalog-event-handlers.ts — same fallback semantics that log_event already relies on so the test-controller seeding path stays consistent.

Test plan

  • npx vitest run server/tests/unit/training-agent.test.ts — 372 tests pass, including three new assertions covering the rejection path, the success path against a registered source, and CPA emission/omission in delivery reporting.
  • npx vitest run server/tests/unit/comply-test-controller.test.ts — 42 tests pass; delivery simulation totals stay backwards-compatible for non-conversion buys.
  • npx vitest run server/tests/unit/training-agent (all 12 training-agent unit suites) — 503 tests pass.
  • npm run build — full schema build, compliance build, tarball build all clean.
  • Pre-commit hook (test:unit + typecheck + dynamic-imports) passed on commit.

Refs

What is NOT in this PR

🤖 Generated with Claude Code

…or performance_buy_flow

Close the two gaps that prevent the `media_buy_seller/performance_buy_flow`
storyboard scenario (#4642) from passing against the training agent.

- `handleCreateMediaBuy` validates `optimization_goals[].event_sources[].event_source_id`
  against the session's registered sources (via `findEventSourceInSession`,
  which falls back to a global scan to match `log_event` lookup semantics).
  Phantom ids are rejected with INVALID_REQUEST and `error.field` set to the
  literal JSONPath-lite shape (`packages[0].optimization_goals[0].event_sources[0].event_source_id`)
  the scenario asserts on. Silent acceptance would let a seller advertise
  `conversion_tracking` without being able to actually optimize against the
  event source the buyer thinks is bound.
- `get_media_buy_delivery` totals now carry `conversions` and
  `cost_per_acquisition = spend / conversions` when `simulate_delivery`
  injected both — matching the delivery contract for performance buys.
  Sellers that don't compute conversion metrics simply omit the fields
  (no conversions injected => no fields emitted).

Unit tests assert the rejection path with the literal JSONPath, the
success path against a registered source, and CPA emission / omission in
delivery reporting.

Refs #4569 (capability gating), #4637, #4642 (scenario PR).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bokelley bokelley merged commit 7db573c into main May 17, 2026
19 checks passed
@bokelley bokelley deleted the bokelley/4642-training-agent-performance-buy-flow branch May 17, 2026 14:30
bokelley added a commit that referenced this pull request May 17, 2026
…ed scenarios (#4637) (#4664)

* feat(compliance): audience_buy_flow + event_dedup_flow scenarios + TA audience validation (#4637)

Two new capability-gated scenarios in the contract pattern (#4637), added to
sales-non-guaranteed.requires_scenarios:

- media_buy_seller/audience_buy_flow — gated on media_buy.audience_targeting
  presence. Certifies sync_audiences → bound audience_id in targeting →
  unbound id rejected → delivery against an audience-targeted buy. Sibling
  to performance_buy_flow on the audience side; the unbound-id rejection
  is the discriminating assertion. The literal error.field path on
  packages[0].targeting_overlay.audience_include[0] mirrors the
  event_source_id contract from #4642.

- media_buy_seller/event_dedup_flow — gated on
  media_buy.conversion_tracking.multi_source_event_dedup equals true.
  Certifies that the same event_id from two registered event sources
  attributes to one conversion, not two. Sellers without
  multi_source_event_dedup grade not_applicable — the bit gates the
  scenario; the cumulative-count assertion (1, not 2) is the contract.
  The training agent does not declare this bit, so it grades
  not_applicable here; no training-agent fix is needed for this scenario.

Training-agent fix: create_media_buy now rejects
targeting_overlay.audience_include / audience_exclude entries whose
audience_id was never registered via sync_audiences, with INVALID_REQUEST
and error.field set to the literal JSONPath-lite path. Mirrors the
event_source_id validation pattern from #4654. sync_audiences itself is
now wired through the training agent (legacy /mcp HANDLER_MAP and v6
/sales/mcp via AudiencePlatform) so adopters can run the audience scenario
against the reference implementation. Four unit tests cover the four
contract paths (accept/reject × include/exclude).

Three sibling product-level scenarios (reach, clicks, completed_views)
remain blocked on #4651 product-level capability gating RFC.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(training-agent): register sync_audiences in /sales tenant catalog (PR 4664 CI fix)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley added a commit that referenced this pull request May 19, 2026
…ompleted_views scenarios (#4766)

* feat(training-agent): metric-mode capability + validation + derived delivery for clicks/reach/completed_views

Declares seller-level `media_buy.supported_optimization_metrics` (honest
union across catalog products), validates `reach_unit` / `view_duration_seconds`
against product capabilities on create_media_buy, and emits `cost_per_click`
plus goal-gated `reach + frequency` / `completed_views + completion_rate`
on get_media_buy_delivery. Flips three capability-gated storyboards from
`not_applicable` to applicable: clicks_buy_flow, reach_buy_flow,
completed_views_buy_flow. Same forcing-function shape as #4654 and #4664.

Manual rollup pending adcp-client#1818 (SDK seller-level field exposure).

Refs: #4637, #4642, #4654, #4664, #4722.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(training-agent): add completed_views delivery emission + cost_per_click negative tests (PR 4766 review)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant