feat(media-buy): add revenue-share pricing - #6095
Conversation
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — adds a single 409-line draft design proposal (specs/revenue-share-pricing.md) for a revenue_share media pricing model.
What I checked:
- No changes to
static/schemas/source/**,docs/reference/**,dist/**, orpackage.json— no wire/artifact-immutability, schema↔docs coherence, oneOf-discriminator, or changeset-scope rules are triggered. A design note underspecs/requires no changeset. - The doc's claims about existing surfaces (pricing_model enum, CPA event_type/custom_event_name/event_source_id, is_fixed_price semantics, available metrics, report_usage vendor_cost, measurement_terms.billing_measurement) were verified accurate by the reviewer.
- Naming conventions correct (AdCP, AgenticAdvertising.org); fictional companies (Pinnacle, Nova) used appropriately.
Gates: high_risk false, gated_paths false, no no-auto-approve team match, first review. review_decision is REVIEW_REQUIRED but gated_paths is false, so row 2 does not fire.
Only one low-severity precision note (commission_rate range (0,1] vs cited precedent) — omitted as a non-blocking nit. No critical/high/medium findings. Falls through to row 9.
f052e63 to
ac51500
Compare
| } | ||
|
|
||
| const pricingContexts = pricingContextsForUsage(session, record); | ||
| if (record.pricing_option_id && pricingContexts && !pricingContexts.packagePricingOptionIds.has(record.pricing_option_id)) { |
There was a problem hiding this comment.
Medium: This guard rejects any usage record that carries both media_buy_id and a pricing_option_id not present in the buy's packages — but pricing_option_id's documented namespace is the vendor's discovery/execution response (get_signals, list_creatives, build_creative), not media-buy package pricing options. The existing creative path validates the same field against creative.pricingOptionId (base L7891), so the two namespaces collide. A creative/signals usage record that also references a media_buy_id in the session would fail here with INVALID_PRICING_OPTION before reaching the creative validation. Today nothing sends media_buy_id (it's new), so no live flow breaks; consider scoping this membership check to revenue-share records (e.g. only when pricingContexts.revenueShares matches, or when no creative_id/signal_agent_segment_id is present).
There was a problem hiding this comment.
Ladon verdict: Escalate to human review
Escalate — gated schema paths require human/CODEOWNERS review.
This PR touches static/schemas/source/** (a hard, path-based approval gate) and review_decision is REVIEW_REQUIRED, not APPROVED. Per decision rule row 2, the gate is not satisfied and this cannot auto-approve.
Escalation reasons
- Modifies/adds gated schema files under
static/schemas/source/**(e.g.pricing-option.json,pricing-model.json, newpricing-structure.jsonenum, newrevenue-share-option.json,delivery-metrics.json,get-media-buy-delivery-response.json). Human/CODEOWNERS approval is required before merge.
Medium findings
- server/src/training-agent/task-handlers.ts:8014 — report_usage package-membership guard treats
pricing_option_idas a media-buy-package namespace, colliding with its documented vendor-discovery namespace.
Notes for the reviewer: schema↔docs coherence holds across all seven touched schema files; the changeset is a minor and the additions are wire-additive (new enum value, new optional fields, new discriminated oneOf branch) — Normative, non-breaking, so the breaking-class hard gate does not apply. The one Medium above is worth a look but is not itself blocking. The gate lifts once a human/CODEOWNERS approval flips review_decision to APPROVED.
Medium findings
- server/src/training-agent/task-handlers.ts:8014 — report_usage package-membership guard collides with vendor pricing_option_id namespace
Why human review
- Gated paths: PR modifies/adds files under static/schemas/source/** (pricing-option.json, pricing-model.json, pricing-structure.json, revenue-share-option.json, delivery-metrics.json, get-media-buy-delivery-response.json, and others) while review_decision is REVIEW_REQUIRED — human/CODEOWNERS approval required.
- This PR touches a path under a hard, non-overridable approval gate (static/schemas/source/account/report-usage-request.json (modified) matches
static/schemas/source/**; static/schemas/source/compliance/comply-test-controller-request.json (modified) matchesstatic/schemas/source/**; static/schemas/source/core/delivery-metrics.json (modified) matchesstatic/schemas/source/**; static/schemas/source/core/measurement-terms.json (modified) matchesstatic/schemas/source/**; static/schemas/source/core/pricing-option.json (modified) matchesstatic/schemas/source/**; static/schemas/source/core/product-filters.json (modified) matchesstatic/schemas/source/**; static/schemas/source/enums/available-metric.json (modified) matchesstatic/schemas/source/**; static/schemas/source/enums/pricing-model.json (modified) matchesstatic/schemas/source/**; static/schemas/source/enums/pricing-structure.json (added) matchesstatic/schemas/source/**; static/schemas/source/index.json (modified) matchesstatic/schemas/source/**; static/schemas/source/media-buy/get-media-buy-delivery-response.json (modified) matchesstatic/schemas/source/**; static/schemas/source/pricing-options/revenue-share-option.json (added) matchesstatic/schemas/source/**) and the current GitHub review decision is 'REVIEW_REQUIRED', not APPROVED. This is a hard gate enforced in code — Ladon cannot auto-approve until a human/CODEOWNERS approval is recorded, regardless of how clean the diff is.
| {/* The v3 major-version alias resolves to the current v3 schema set. */} | ||
| ```json | ||
| { | ||
| "$schema": "https://adcontextprotocol.org/schemas/v3/pricing-options/revenue-share-option.json", |
There was a problem hiding this comment.
MUST FIX: /schemas/v3/ does not resolve to the dev schema set. Per build-schemas.cjs (L21, L200), aliases['v3'] is built only from getAllReleasedVersions() — it points to the latest stable release (currently 3.1.8). revenue-share-option.json is added in this PR under a minor changeset and is not yet released, so /schemas/v3/pricing-options/revenue-share-option.json resolves to 3.1.8/…, where this file does not exist — a dead $schema reference in the published example. The comment this replaced said exactly that: "Update to /schemas/v3/ after release." The new comment ("The v3 major-version alias resolves to the current v3 schema set") is wrong — that is what /schemas/latest/ is for. Revert to /schemas/latest/ until the schema ships in a release.
There was a problem hiding this comment.
Ladon verdict: Request changes
Request changes — 2 blocking finding(s)
The current pass surfaces two high findings from schema↔docs coherence / spec-drift analysis. Applying the decision table top-down, row 1 fires (findings with severity high present), so the outcome is request-changes.
Blocking findings
- docs/media-buy/advanced-topics/pricing-models.mdx:395 —
$schemapoints at/schemas/v3/revenue-share-option.json, but the releasedv3alias (built only from released versions per build-schemas.cjs L21/L200 → 3.1.8) does not contain the unreleasedrevenue-share-option.json. The reference is broken/drifted. - docs/accounts/tasks/report_usage.mdx:14 — request-schema link swapped to
/schemas/v3/, which lacks the new reconciliation fields this PR adds. Docs assert a wire shape the released alias does not encode — spec drift.
Both docs edits swap revenue-share/report-usage schema references from /schemas/latest/ to /schemas/v3/ and claim v3 resolves to the current schema set. Since v{major} resolves to the latest stable release (3.1.8), which predates the unreleased schemas/fields in this PR, the references ship drifted. This also reverts a deliberate, correct prior decision to use /schemas/latest/. Fix the references back to the unreleased/latest schema set before merge.
Note: the prior [medium] task-handlers.ts:8014 finding is not in this delta and was not re-raised, so it does not carry forward. gated_paths is also true with review_decision not APPROVED — the schema-source hard approval gate remains unsatisfied and a human/CODEOWNERS approval is required regardless.
Blocking findings
- docs/media-buy/advanced-topics/pricing-models.mdx:395 — pricing-models.mdx points $schema at /schemas/v3/revenue-share-option.json, which the released v3 alias does not contain (spec drift / broken schema ref)
- docs/accounts/tasks/report_usage.mdx:14 — report_usage.mdx request-schema link swapped to /schemas/v3/, which lacks the new reconciliation fields this PR adds (docs assert a wire shape v3 does not encode)
There was a problem hiding this comment.
Ladon verdict: Escalate to human review
Escalate — gated schema paths require human/CODEOWNERS approval and review_decision is CHANGES_REQUESTED.
This is a subsequent pass. Both prior high findings are resolved in this delta: pricing-models.mdx dropped the $schema pointing at the v3 alias that lacked revenue-share-option.json, and report_usage.mdx removed the /schemas/v3/ request-schema link that lacked the new reconciliation fields (both replaced with deferral comments until the fields ship in a stable release). The new task-handlers.ts usesVendorPricingNamespace guard correctly scopes media-buy pricing_option_id validation and revenue-share reconciliation to media-namespace records. No new blocking or Medium findings in the delta.
However, the PR touches gated protocol-source paths under static/schemas/source/** (12 files including new pricing-structure.json and revenue-share-option.json). gated_paths: true AND review_decision: CHANGES_REQUESTED (not APPROVED) → decision-table row 2 forces escalate. The gate is only satisfied by a real human/CODEOWNERS APPROVED review, which is not present. This is also a substantive new-fields/new-enum-value protocol surface change (revenue-share pricing) that warrants human ratification.
Escalation reasons
static/schemas/source/**(schema source-of-truth) modified/added whilereview_decisionis CHANGES_REQUESTED — hard approval gate not satisfied; human/CODEOWNERS review required.
No blocking findings remain from the reviewer; the escalation is purely the unsatisfied gated-paths gate.
Why human review
- Touches gated protocol-source paths under static/schemas/source/** (12 files, incl. new pricing-structure.json and revenue-share-option.json) while review_decision is CHANGES_REQUESTED — hard approval gate not satisfied; requires human/CODEOWNERS APPROVED review before merge.
- This PR touches a path under a hard, non-overridable approval gate (static/schemas/source/account/report-usage-request.json (modified) matches
static/schemas/source/**; static/schemas/source/compliance/comply-test-controller-request.json (modified) matchesstatic/schemas/source/**; static/schemas/source/core/delivery-metrics.json (modified) matchesstatic/schemas/source/**; static/schemas/source/core/measurement-terms.json (modified) matchesstatic/schemas/source/**; static/schemas/source/core/pricing-option.json (modified) matchesstatic/schemas/source/**; static/schemas/source/core/product-filters.json (modified) matchesstatic/schemas/source/**; static/schemas/source/enums/available-metric.json (modified) matchesstatic/schemas/source/**; static/schemas/source/enums/pricing-model.json (modified) matchesstatic/schemas/source/**; static/schemas/source/enums/pricing-structure.json (added) matchesstatic/schemas/source/**; static/schemas/source/index.json (modified) matchesstatic/schemas/source/**; static/schemas/source/media-buy/get-media-buy-delivery-response.json (modified) matchesstatic/schemas/source/**; static/schemas/source/pricing-options/revenue-share-option.json (added) matchesstatic/schemas/source/**) and the current GitHub review decision is 'CHANGES_REQUESTED', not APPROVED. This is a hard gate enforced in code — Ladon cannot auto-approve until a human/CODEOWNERS approval is recorded, regardless of how clean the diff is.
Summary
revenue_shareas a first-class media pricing model with a decimalcommission_rate, explicit attribution source, currency, and human-readable commission basisfixed,auction, andcontingentpricing structures so buyers can discover revenue-share products without treating every option lackingfixed_priceas an auctionbid_price, delivery reporting, and formula-checkedreport_usagereconciliationcommissionable_valueto delivery and usage metrics, including cumulative package commission-budget enforcementContract
The MVP intentionally supports one rate and one settled billing basis:
conversion_valueremains the gross analytics value.commissionable_valueis the settled amount eligible under the agreement after exclusions and returns. Packagebudgetis the maximum payable commission.Revenue share is a contingent pricing structure. It does not use
fixed_price,floor_price,price_guidance,max_bid, or buy-timebid_price. It is channel-independent even though affiliate/content commerce is the motivating use case.Implementation
pricing-options/revenue-share-option.jsonandenums/pricing-structure.jsonschemasreport_usagemedia_buy_seller/revenue_share_pricingcompliance storyboardValidation
node tests/schema-validation.test.cjs— 21/21 passednode scripts/check-registry-completeness.cjsnode scripts/audit-oneof.mjs --checktsc --project server/tsconfig.json --noEmitvitest run server/tests/unit/training-agent.test.ts— 534/534 passednode scripts/build-schemas.cjsnode scripts/build-compliance.cjsgit diff --checkThe repository-wide snippet runner also encounters its existing local optional-dependency failures (
adcp.testingis not installed); none of the changed docs add executable snippets.Feedback requested
commission_rateversus a 0–100 percentage fieldcommissionable_valueis the right portable billing basisevent_source_idis_fixed_priceCloses #5754