feat(media-buy): add atomic total budget updates - #6271
Conversation
| @@ -0,0 +1,5 @@ | |||
| --- | |||
| "adcontextprotocol": minor | |||
There was a problem hiding this comment.
MUST FIX: minor under-classifies a breaking wire change. Pre-PR, fixed-mode update_media_buy accepted total_budget alongside packages/new_packages when the amount equaled the resulting sum (schema: "In fixed mode the amount MUST equal the sum of package budgets after applying this update"). This PR rejects that exact combination with VALIDATION_ERROR (server handleUpdateMediaBuy, schema not.allOf), and redefines fixed-mode total_budget from a checked assertion into a proportional-redistribution trigger. A previously-valid request shape now fails, and the field's semantics change — Breaking-class per the constitution (semantic/default change), not Normative. The redistribution itself is additive; the mutual-exclusivity rejection is the break. Ship major and route for human ratification, or preserve the additive path (accept a consistent total_budget+packages request).
There was a problem hiding this comment.
Ladon verdict: Request changes
Request changes — 1 blocking finding.
Blocking findings
- .changeset/add-atomic-total-budget-update.md:2 — Minor changeset under-classifies a breaking wire change. This PR now rejects a previously-valid
total_budget+packagescombination and redefines fixed-modetotal_budgetsemantics — a Breaking-class change to the published protocol surface (static/schemas/source/**). Per repo policy, a breaking wire change requires amajorchangeset; aminorchangeset shipping it ishigh.
Medium findings
- server/src/training-agent/task-handlers.ts:1808 — Docs assert a per-package zero-budget rejection that the reference implementation does not enforce (schema/docs↔impl coherence gap).
Note: This change is also Breaking-class and touches gated paths (static/schemas/source/**) with review_decision: REVIEW_REQUIRED, so even absent the changeset blocker it would require human/CODEOWNERS ratification and cannot auto-approve. Row 1 (high finding) governs the outcome here. Resolve the changeset classification (bump to major) and align the reference impl with the documented zero-budget rejection, then re-request review.
Blocking findings
- .changeset/add-atomic-total-budget-update.md:2 — Minor changeset under-classifies a breaking wire change; breaking wire changes require a major changeset
Medium findings
- server/src/training-agent/task-handlers.ts:1808 — Docs assert a zero-budget rejection the reference impl does not enforce
WG decision memo — wire compatibilityThe implementation bug is closed in bfd98cc: an active zero-budget package now makes proportional redistribution fail atomically. One decision remains. Option A — preserve the 3.2 minor contract (recommended). Keep the existing combined-request assertion path: when Option B — ratify a breaking contract. Keep this PR’s mutual exclusion and redefine Requested outcome: choose A or B. I recommend A for 3.2; it delivers the atomic shortcut without invalidating the assertion form already published. |
|
Decision: Option A. The binding constraint is the changeset class. This PR carries Resulting wire semantics under Option A:
{
"media_buy_id": "mb_001",
"total_budget": { "amount": 50000, "currency": "USD" }
}
{
"media_buy_id": "mb_001",
"total_budget": { "amount": 50000, "currency": "USD" },
"packages": [
{ "package_id": "pkg_001", "budget": 30000 },
{ "package_id": "pkg_002", "budget": 20000 }
]
}Seller rejects only when Changes needed to implement Option A in this PR:
Generated by Claude Code |
There was a problem hiding this comment.
Ladon verdict: Escalate to human review
Escalate — gated-path schema change without required approval.
Escalation reasons
- This PR modifies published protocol source under
static/schemas/source/**(update-media-buy-request.json,update-media-buy-response.json), which is under a hard, deterministic approval gate (gated_paths: true).review_decisionisCHANGES_REQUESTED, notAPPROVED, so the gate is not satisfied. Human/CODEOWNERS approval is required before merge.
Review context
- No current blocking (critical/high) or medium findings were surfaced this pass. The delta since the prior review is a single guard change (
pkg.budget < 0→<= 0) intask-handlers.tsplus an added test, which resolves the prior medium (docs asserted a zero-budget rejection the impl did not enforce). The rest of the setup delta is merge noise. - The prior high finding (changeset under-classifying a breaking wire change) was on
.changeset/add-atomic-total-budget-update.md, which was not modified this pass and is not re-raised by the reviewer.
Because the diff is clean, this is not request-changes; but row 2 (gated_paths true AND review_decision ≠ APPROVED) forces escalate. The gate lifts once a human/CODEOWNERS approval lands.
Why human review
- Modifies gated protocol source static/schemas/source/media-buy/update-media-buy-request.json and update-media-buy-response.json — hard approval gate (gated_paths: true) with review_decision=CHANGES_REQUESTED, not APPROVED. Human/CODEOWNERS approval required before merge.
- This PR touches a path under a hard, non-overridable approval gate (static/schemas/source/media-buy/update-media-buy-request.json (modified) matches
static/schemas/source/**; static/schemas/source/media-buy/update-media-buy-response.json (modified) 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.
…_budget + packages Per the decision in #6271 comments: total_budget alone invokes proportional redistribution; total_budget + packages/new_packages invokes the #6088 assertion path (total must equal resulting package sum). - Remove not/allOf mutual-exclusion block from update-media-buy-request.json - Remove mutual-exclusion early return in task-handlers.ts - Gate fixedRedistribution on req.packages === undefined && req.new_packages === undefined - Update total_budget field description in schema and docs to document both paths - Convert rejection test and compliance scenario to test assertion mismatch (total ≠ sum) rather than mutual exclusion
|
Option A applied in ef4f688. Five changes from the decision comment:
Generated by Claude Code |
There was a problem hiding this comment.
Ladon verdict: Escalate to human review
Escalate — gated protocol source modified with review_decision=CHANGES_REQUESTED (not APPROVED).
This PR modifies gated protocol schema source under static/schemas/source/**:
static/schemas/source/media-buy/update-media-buy-request.jsonstatic/schemas/source/media-buy/update-media-buy-response.json
gated_paths: true and review_decision is CHANGES_REQUESTED, not APPROVED, so the hard approval gate (decision-table row 2) is not satisfied. Human/CODEOWNERS approval must be recorded before this can merge, regardless of how clean the diff is.
Blocking findings: none — the reviewer found no critical/high/medium issues.
Review notes: The delta restores a coherent two-path model for total_budget (proportional redistribution when alone in fixed mode; assertion/VALIDATION_ERROR otherwise). Handler guards partition cleanly, and the schema description, docs prose, and compliance scenario are mutually consistent. Changeset is a correctly-scoped minor for an additive capability. This is a net-clean pass with no new findings.
Escalation reason: Modifies gated protocol source update-media-buy-request.json and update-media-buy-response.json (hard approval gate) while the GitHub review decision is CHANGES_REQUESTED. Ladon cannot auto-approve until a human/CODEOWNERS approval is recorded. This is consistent with the prior escalate decision; the gate remains unsatisfied.
Why human review
- Modifies gated protocol source static/schemas/source/media-buy/update-media-buy-request.json and update-media-buy-response.json — hard approval gate (gated_paths: true) with review_decision=CHANGES_REQUESTED, not APPROVED. Human/CODEOWNERS approval required before merge.
- This PR touches a path under a hard, non-overridable approval gate (static/schemas/source/media-buy/update-media-buy-request.json (modified) matches
static/schemas/source/**; static/schemas/source/media-buy/update-media-buy-response.json (modified) 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.
Closes #4241.
Summary
total_budgetmutually exclusive withpackagesandnew_packagestotal_budget_redistributioncompliance scenarioValidation
git diff --check: passedReview note
#6088 already introduced the optional field and seller-optimized shared-pool behavior. This PR supplies the remaining accepted #4241 contract without treating seller-optimized package caps as fixed allocations.