Skip to content

feat(media-buy): add shared budgets and bidding policy - #6088

Open
bokelley wants to merge 6 commits into
mainfrom
flexible-package-budgets
Open

feat(media-buy): add shared budgets and bidding policy#6088
bokelley wants to merge 6 commits into
mainfrom
flexible-package-budgets

Conversation

@bokelley

@bokelley bokelley commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Closes #6106

Summary

  • add a discriminated BudgetAllocation model with backward-compatible fixed allocation and seller-optimized shared budgets
  • add a reusable BiddingPolicy at media-buy and package scope for manual bids, auction ceilings, average-cost controls, and ROAS controls
  • define aggregate/package pacing, complete-block bidding inheritance, authored-scope-preserving readback, atomic placement validation, update clearing, and deterministic legacy migration
  • add focused schema tests and capability-gated conformance scenarios for shared budgets and bidding-policy placement

Motivation

AdCP 3.1 requires explicit package budgets and splits buyer price intent across package bid_price, pricing-option max_bid, and monetary optimization_goals targets. Platforms that optimize one campaign budget and strategy across multiple ad sets or line items cannot express that behavior without vendor extensions.

This gives 3.2 one portable placement story: objective functions stay in optimization_goals; budget allocation, pacing, and bidding execution policy live at the scope where they are authored.

Budget semantics

  • total_budget is the hard aggregate lifetime cap
  • package budget remains a hard cap, never a current allocation
  • min_spend_target is soft
  • media-buy pacing governs aggregate spend; package pacing is subordinate
  • allocation goals operate across packages; package goals operate within a package
  • continuous seller allocation is delivery behavior and does not mutate revision/history
  • seller-optimized proposals use minimum/maximum percentages rather than exact allocation percentages
  • fixed/default add_packages updates keep package budgets required; uncapped additions require explicit seller-optimized allocation context

Bidding semantics

  • media-buy bidding is a complete inherited default; package bidding is a complete override, never a field merge
  • bid_amount is a manual auction bid; max_bid is the only hard per-auction ceiling
  • cost_per distinguishes average cap from target; roas distinguishes floor from target
  • cost_per resolves against the primary optimization goal, while bid_amount/max_bid resolve against the selected pricing option's auction unit
  • shared-budget campaign policy is authoritative where the provider requires campaign placement; unsupported overrides fail atomically with BIDDING_PLACEMENT_CONFLICT
  • canonical and legacy policy cannot coexist on one effective package; mixed input fails with AMBIGUOUS_BIDDING_POLICY
  • pricing-option max_bid, package bid_price, and monetary goal targets are deprecated with deterministic normalization rules

Validation

  • schema and compliance builds
  • 654-schema structural/reference/example validation
  • 138 composed-schema tests
  • documentation JSON validation: 284/284 annotated examples
  • storyboard sample request/response lint
  • oneOf audit, immutable-release, platform-agnostic, diff, and changeset checks
  • dynamic-import/state-change lint and TypeScript typecheck
  • pre-merge aggregate unit run exercised 4,640 tests; two unrelated suite-order failures passed in isolation (23/23 and 5/5)

Notes

Daily/hourly caps remain out of scope; the new budget is a lifetime media-buy total. The bidding block represents numeric portfolio policy but not shared provider portfolio identity or lifecycle across media buys; that requires a future provider-neutral resource/reference.

@bokelley
bokelley marked this pull request as ready for review July 30, 2026 10:20
Comment thread docs/media-buy/task-reference/get_media_buys.mdx
Comment thread static/schemas/source/media-buy/package-request.json

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ladon verdict: Escalate to human review

Escalate — gated schema paths require human/CODEOWNERS approval.

This PR modifies published protocol source under static/schemas/source/** (a deterministic hard approval gate) and review_decision is REVIEW_REQUIRED, not APPROVED. Per decision rule row 2, the outcome must be escalate until a real human/CODEOWNERS approval lifts the gate.

No blocking (critical/high) findings were reported. The reviewer classified the change as Normative (non-breaking): additive BudgetAllocation discriminated model, aggregate budget/pacing, package caps, a new capability flag, and a new update_budget_allocation action, with wire changes that are additive or relaxations, a correct minor changeset, and a properly discriminated oneOf. Two Medium coherence/validation gaps remain (see below) but do not on their own change the outcome — the gated-paths gate is the controlling trigger.

Gated files requiring human/CODEOWNERS review include: static/schemas/source/core/budget-allocation.json (added) and the modified media-buy.json, package.json, proposal.json, planned-delivery.json, product-allocation.json, optimization-goal.json, media-buy-features.json, index.json, plus the media-buy request/response schemas.

Medium findings for the human reviewer to weigh:

  • docs/media-buy/task-reference/get_media_buys.mdx:84 — packages[].pacing added to response but undocumented in the package table (schema↔docs coherence gap).
  • static/schemas/source/media-buy/package-request.json:47 — budget no longer required on new_packages[] for fixed-allocation buys.

Once a CODEOWNERS/human approval sets review_decision to APPROVED, this can fall through to the normal table (no blocking findings present).

Medium findings

  • docs/media-buy/task-reference/get_media_buys.mdx:84 — packages[].pacing added to get-media-buys-response but undocumented in get_media_buys package table
  • static/schemas/source/media-buy/package-request.json:47 — budget no longer required on new_packages[] for fixed-allocation buys

Why human review

  • Touches gated protocol source under static/schemas/source/** (e.g. budget-allocation.json, media-buy.json, proposal.json, package.json) — hard approval gate; review_decision is REVIEW_REQUIRED, not APPROVED. Human/CODEOWNERS review required.
  • Two medium schema↔docs coherence/validation gaps to verify: get_media_buys.mdx:84 (packages[].pacing undocumented) and package-request.json:47 (budget no longer required on new_packages[] for fixed allocation).
  • This PR touches a path under a hard, non-overridable approval gate (static/schemas/source/core/budget-allocation.json (added) matches static/schemas/source/**; static/schemas/source/core/media-buy-features.json (modified) matches static/schemas/source/**; static/schemas/source/core/media-buy.json (modified) matches static/schemas/source/**; static/schemas/source/core/optimization-goal.json (modified) matches static/schemas/source/**; static/schemas/source/core/package.json (modified) matches static/schemas/source/**; static/schemas/source/core/planned-delivery.json (modified) matches static/schemas/source/**; static/schemas/source/core/product-allocation.json (modified) matches static/schemas/source/**; static/schemas/source/core/proposal.json (modified) matches static/schemas/source/**; static/schemas/source/enums/media-buy-valid-action.json (modified) matches static/schemas/source/**; static/schemas/source/index.json (modified) matches static/schemas/source/**; static/schemas/source/media-buy/create-media-buy-request.json (modified) matches static/schemas/source/**; static/schemas/source/media-buy/create-media-buy-response.json (modified) matches static/schemas/source/**; static/schemas/source/media-buy/get-media-buy-delivery-response.json (modified) matches static/schemas/source/**; static/schemas/source/media-buy/get-media-buys-response.json (modified) matches static/schemas/source/**; static/sc

…(truncated)…

@aao-secretariat aao-secretariat Bot added the ladon/needs-human-review Ladon has escalated this PR for human review. label Jul 30, 2026
@bokelley bokelley changed the title feat(media-buy): add seller-optimized package budgets feat(media-buy): add shared budgets and bidding policy Jul 30, 2026
@aao-secretariat

Copy link
Copy Markdown
Contributor

Ladon cannot review this PR until merge conflicts are resolved.

Comment thread static/schemas/source/media-buy/package-update.json Outdated

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ladon verdict: Escalate to human review

Escalate — gated protocol source under a hard approval gate, review_decision is not APPROVED.

This PR modifies static/schemas/source/** (gated paths), and review_decision is unknown/not APPROVED. Per decision-table row 2, the hard approval gate is not satisfied and the outcome must be escalate — Ladon cannot auto-approve gated protocol source until a human/CODEOWNERS approval is recorded.

Iteration credit: Both prior medium findings are resolved this pass (no packages[].pacing in get-media-buys-response.json; budget still required in package-request.json). The BiddingPolicy work is coherent and well-tested — mutual exclusion via not.anyOf, deprecated-not-removed bid_price/max_bid, type-const-discriminated execution oneOf, matching docs across create/get/update, new error codes, and composed/AJV coverage. This is a net improvement over the prior run.

Medium findings (surfaced, non-blocking):

  • static/schemas/source/media-buy/package-update.json:202 — bidding-conflict guards reject an explicit bidding:null clear, wrongly rejecting a legit legacy-migration update. Worth fixing but does not itself block.

Escalation reasons:

  • Gated paths under static/schemas/source/** (e.g. bidding-policy.json, budget-allocation.json, media-buy.json, proposal.json) touched; review_decision is not APPROVED. Human/CODEOWNERS review required.
  • New protocol fields, new error codes (BIDDING_PLACEMENT_CONFLICT, AMBIGUOUS_BIDDING_POLICY), and new enum values are additive/normative surface changes on the published protocol; ratification is a human act.

Medium findings

  • static/schemas/source/media-buy/package-update.json:202 — package-update bidding guards reject an explicit bidding:null clear

Why human review

  • Touches gated protocol source under static/schemas/source/** (bidding-policy.json, budget-allocation.json, media-buy.json, proposal.json, package.json, and others) — hard, non-overridable approval gate; review_decision is not APPROVED, so Ladon cannot auto-approve. Human/CODEOWNERS review required.
  • Adds new protocol fields, new error codes, and new enum values to the published AdCP surface — normative/additive spec changes whose ratification is a human act.
  • One medium to verify during human review: static/schemas/source/media-buy/package-update.json:202 — bidding-conflict guards reject an explicit bidding:null clear, blocking a legit legacy-migration update.
  • This PR touches a path under a hard, non-overridable approval gate (static/schemas/source/core/bidding-policy.json (added) matches static/schemas/source/**; static/schemas/source/core/budget-allocation.json (added) matches static/schemas/source/**; static/schemas/source/core/media-buy-features.json (modified) matches static/schemas/source/**; static/schemas/source/core/media-buy.json (modified) matches static/schemas/source/**; static/schemas/source/core/optimization-goal.json (modified) matches static/schemas/source/**; static/schemas/source/core/package.json (modified) matches static/schemas/source/**; static/schemas/source/core/planned-delivery.json (modified) matches static/schemas/source/**; static/schemas/source/core/pricing-option.json (modified) matches static/schemas/source/**; static/schemas/source/core/product-allocation.json (modified) matches static/schemas/source/**; static/schemas/source/core/proposal.json (modified) matches static/schemas/source/**; static/schemas/source/enums/error-code.json (modified) matches static/schemas/source/**; static/schemas/source/enums/media-buy-valid-action.json (modified) matches static/schemas/source/**; static/schemas/source/index.json (modified) matches static/schemas/source/**; static/schemas/source/media-buy/create-media-buy-request.json (modified) matches static/schemas/source/**; static/schemas/source/media-buy/create-media-buy-response.json (modified) matches static/schemas/source/**; static/s

…(truncated)…

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ladon verdict: Escalate to human review

Escalate — gated protocol-source paths require human/CODEOWNERS approval that is not yet recorded.

This is a subsequent pass on the shared-budgets / bidding-policy PR. Both prior Medium findings are now resolved:

  • get_media_buys.mdx now documents packages[].pacing / min_spend_target / bidding (get_media_buys.mdx:674-681) matching get-media-buys-response.json.
  • The package-request budget-removal is re-guarded by the create-media-buy-request anyOf fixed branch (create-media-buy-request.json:2008-2099) and the update-media-buy-request new_packages conditional (update-media-buy-request.json:2510-2535), with rejection tests at composed-schema-validation.test.cjs:2841/2859/2875.

Reviewer reports no new blocking or Medium findings in the delta. The diff is mechanically clean. However, this PR modifies/adds files under static/schemas/source/** — a hard, non-overridable approval gate — and review_decision is not APPROVED (unknown/none). Per decision-table row 2, the gate is not satisfied and the outcome must be escalate. A human/CODEOWNERS approval must be recorded before this can merge. This also carries new protocol surface (new fields, new bidding-policy / budget-allocation schemas, new conformance scenarios), which under the constitution's breaking-class posture likewise requires human ratification rather than auto-approval.

Escalation reasons

  • Touches gated protocol source under static/schemas/source/** (e.g. bidding-policy.json, budget-allocation.json, media-buy.json, proposal.json, package.json) — hard approval gate; review_decision is not APPROVED. Human/CODEOWNERS review required.
  • Adds new normative protocol surface (new fields, new schemas, new conformance scenarios) — spec ratification is a human act, not auto-approvable.

Why human review

  • Touches gated protocol source under static/schemas/source/** (bidding-policy.json, budget-allocation.json, media-buy.json, proposal.json, package.json, and 20+ others) — hard approval gate; review_decision is not APPROVED (unknown/none). Human/CODEOWNERS review required before merge.
  • Adds new normative protocol surface (new bidding-policy and budget-allocation schemas, new fields, new capability-gated conformance scenarios) — spec ratification is a human act and is never auto-approved absent an APPROVED review.
  • This PR touches a path under a hard, non-overridable approval gate (static/schemas/source/core/bidding-policy.json (added) matches static/schemas/source/**; static/schemas/source/core/budget-allocation.json (added) matches static/schemas/source/**; static/schemas/source/core/media-buy-features.json (modified) matches static/schemas/source/**; static/schemas/source/core/media-buy.json (modified) matches static/schemas/source/**; static/schemas/source/core/optimization-goal.json (modified) matches static/schemas/source/**; static/schemas/source/core/package.json (modified) matches static/schemas/source/**; static/schemas/source/core/planned-delivery.json (modified) matches static/schemas/source/**; static/schemas/source/core/pricing-option.json (modified) matches static/schemas/source/**; static/schemas/source/core/product-allocation.json (modified) matches static/schemas/source/**; static/schemas/source/core/proposal.json (modified) matches static/schemas/source/**; static/schemas/source/enums/error-code.json (modified) matches static/schemas/source/**; static/schemas/source/enums/media-buy-valid-action.json (modified) matches static/schemas/source/**; static/schemas/source/index.json (modified) matches static/schemas/source/**; static/schemas/source/media-buy/create-media-buy-request.json (modified) matches static/schemas/source/**; static/schemas/source/media-buy/create-media-buy-response.json (modified) matches static/schemas/source/**; static/schemas/source/media-buy/get-media-buy-delivery-response.json (modified) matches static/schemas/source/**; static/schemas/s

…(truncated)…

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ladon verdict: Escalate to human review

Escalate — gated protocol source under hard approval gate; human/CODEOWNERS review required.

This PR adds new normative protocol surface to static/schemas/source/** (new bidding-policy.json and budget-allocation.json schemas, new fields, new error codes, and new capability-gated conformance scenarios). Under this repo's constitution, adding new normative protocol surface — new schemas, new fields, new enum/error codes — is a spec-ratification act that is never auto-approved.

Decision-table trigger (row 2): gated_paths: true AND review_decision is not APPROVED (unknown/none). This is a hard, deterministic, code-enforced gate — Ladon cannot auto-approve until a human/CODEOWNERS approval is recorded, regardless of how clean the diff is.

Reviewer findings: none. The delta since the prior pass only adds two well-formed disposition entries (AMBIGUOUS_BIDDING_POLICY, BIDDING_PLACEMENT_CONFLICT) to scripts/error-code-drift-dispositions.json — both held-for-next-minor / target_version 3.2, present in the source enum, satisfying the lint contract. No new critical/high/medium findings. The prior escalate stands purely on the unsatisfied gate, not on a fresh defect.

Escalation reasons

  • Touches gated protocol source under static/schemas/source/** (bidding-policy.json, budget-allocation.json, media-buy.json, proposal.json, package.json, pricing-options/*, and 20+ others) — hard approval gate; review_decision is not APPROVED. Human/CODEOWNERS review required before merge.
  • Adds new normative protocol surface (new schemas, new fields, new error codes, new conformance scenarios) — spec ratification is a human act and is never auto-approved absent an APPROVED review.

Once a human/CODEOWNERS APPROVED review is recorded, this clean diff can fall through to normal approval.

Why human review

  • Touches gated protocol source under static/schemas/source/** (bidding-policy.json, budget-allocation.json, media-buy.json, proposal.json, package.json, pricing-options/*, and 20+ others) — hard approval gate; review_decision is not APPROVED (unknown/none). Human/CODEOWNERS review required before merge.
  • Adds new normative protocol surface (new bidding-policy and budget-allocation schemas, new fields, new error codes, new capability-gated conformance scenarios) — spec ratification is a human act and is never auto-approved absent an APPROVED review.
  • This PR touches a path under a hard, non-overridable approval gate (static/schemas/source/core/bidding-policy.json (added) matches static/schemas/source/**; static/schemas/source/core/budget-allocation.json (added) matches static/schemas/source/**; static/schemas/source/core/media-buy-features.json (modified) matches static/schemas/source/**; static/schemas/source/core/media-buy.json (modified) matches static/schemas/source/**; static/schemas/source/core/optimization-goal.json (modified) matches static/schemas/source/**; static/schemas/source/core/package.json (modified) matches static/schemas/source/**; static/schemas/source/core/planned-delivery.json (modified) matches static/schemas/source/**; static/schemas/source/core/pricing-option.json (modified) matches static/schemas/source/**; static/schemas/source/core/product-allocation.json (modified) matches static/schemas/source/**; static/schemas/source/core/proposal.json (modified) matches static/schemas/source/**; static/schemas/source/enums/error-code.json (modified) matches static/schemas/source/**; static/schemas/source/enums/media-buy-valid-action.json (modified) matches static/schemas/source/**; static/schemas/source/index.json (modified) matches static/schemas/source/**; static/schemas/source/media-buy/create-media-buy-request.json (modified) matches static/schemas/source/**; static/schemas/source/media-buy/create-media-buy-response.json (modified) matches static/schemas/source/**; static/schemas/source/media-buy/get-media-buy-del

…(truncated)…

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ladon verdict: Escalate to human review

Escalate — hard approval gate on protocol source, and Breaking-class/normative-surface ratification requires human review.

This PR touches gated protocol source under static/schemas/source/** (added bidding-policy.json, budget-allocation.json; modified media-buy.json, proposal.json, package.json, pricing-options/*, enums/error-code.json, and 20+ others). gated_paths: true and review_decision: REVIEW_REQUIRED (not APPROVED), so the deterministic hard gate (decision-table row 2) is not satisfied.

The reviewer found no blocking findings: the package-update bid_price/bidding mutual-exclusion delta is correct (null bidding may accompany bid_price or a legacy monetary optimization-goal target for atomic migration; a non-null canonical bidding block still conflicts), the not.anyOf/if-then guards correctly gate on bidding being type:object, update_media_buy.mdx matches with no schema↔docs drift, and four added schema tests isolate the two accept and two reject paths. A changeset is present. Clean pass on content.

However, this adds new normative protocol surface (new schemas, new fields, new error codes, new capability-gated conformance scenarios) — spec ratification is a human act and is never auto-approved absent an APPROVED review. Prior decision was also escalate for the same gate. A human/CODEOWNERS approval is required before merge.

Escalation reasons

  • Gated file(s) under static/schemas/source/** with review_decision: REVIEW_REQUIRED (not APPROVED) — human/CODEOWNERS review required (decision row 2).
  • Adds new normative protocol surface (new bidding-policy/budget-allocation schemas, new fields, new error codes, new conformance scenarios) — spec ratification is a human act, never auto-approved absent an APPROVED review.

Why human review

  • Gated protocol source under static/schemas/source/** (bidding-policy.json, budget-allocation.json, media-buy.json, proposal.json, package.json, pricing-options/*, error-code.json, and 20+ others) is under a hard, non-overridable approval gate and review_decision is REVIEW_REQUIRED, not APPROVED — human/CODEOWNERS review required before merge.
  • Adds new normative protocol surface (new schemas, new fields, new error codes, new capability-gated conformance scenarios) — spec ratification is a human act and is never auto-approved absent an APPROVED review.
  • This PR touches a path under a hard, non-overridable approval gate (static/schemas/source/core/bidding-policy.json (added) matches static/schemas/source/**; static/schemas/source/core/budget-allocation.json (added) matches static/schemas/source/**; static/schemas/source/core/media-buy-features.json (modified) matches static/schemas/source/**; static/schemas/source/core/media-buy.json (modified) matches static/schemas/source/**; static/schemas/source/core/optimization-goal.json (modified) matches static/schemas/source/**; static/schemas/source/core/package.json (modified) matches static/schemas/source/**; static/schemas/source/core/planned-delivery.json (modified) matches static/schemas/source/**; static/schemas/source/core/pricing-option.json (modified) matches static/schemas/source/**; static/schemas/source/core/product-allocation.json (modified) matches static/schemas/source/**; static/schemas/source/core/proposal.json (modified) matches static/schemas/source/**; static/schemas/source/enums/error-code.json (modified) matches static/schemas/source/**; static/schemas/source/enums/media-buy-valid-action.json (modified) matches static/schemas/source/**; static/schemas/source/index.json (modified) matches static/schemas/source/**; static/schemas/source/media-buy/create-media-buy-request.json (modified) matches static/schemas/source/**; static/schemas/source/media-buy/create-media-buy-response.json (modified) matches static/schemas/source/**; static/schemas/source/media-buy/get-media-buy-delivery-response.jso

…(truncated)…

@bokelley

Copy link
Copy Markdown
Contributor Author

Issue #6106 proposes an explicit bidding.max_bid buyer-authored per-auction ceiling with deterministic bid_price normalization — same surface as this PR's BiddingPolicy; consider referencing or closing it from the PR body before merge.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ladon/needs-human-review Ladon has escalated this PR for human review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RFC(media-buy): add an explicit buyer-authored per-auction bid cap

1 participant