Skip to content

feat(google-ads): budget type (daily vs CUSTOM_PERIOD total) get and set (#366)#370

Closed
hyoshi wants to merge 3 commits into
feat/366-google-ads-readonly-criteria-assetsfrom
feat/366-google-ads-budget-period
Closed

feat(google-ads): budget type (daily vs CUSTOM_PERIOD total) get and set (#366)#370
hyoshi wants to merge 3 commits into
feat/366-google-ads-readonly-criteria-assetsfrom
feat/366-google-ads-budget-period

Conversation

@hyoshi

@hyoshi hyoshi commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

Part of #366 (task 3 — budget type get/set). Stacked on #369 (which stacks on #368): merge #368#369 first, then retarget this PR's base to main. Closes #366 together with #369.

Summary

Google Ads budgets come in two types — DAILY and CUSTOM_PERIOD (campaign-lifetime total). mureo previously read/wrote only the daily amount. This PR:

Surface Change
google_ads_budget_get Now returns name, period (DAILY / CUSTOM_PERIOD), delivery_method, enum-mapped status, total_budget / total_amount_micros (null unless CUSTOM_PERIOD), reference_count — fixing the long-standing drift where the description promised these fields
google_ads_budget_update Accepts total_amount / total_amount_micros alongside (or instead of) the daily amount, with a dynamic update mask
google_ads_budget_create Accepts period + total amounts; required relaxed to ["name"] + anyOf so a pure total budget needs no daily amount

Design notes

  • period is create-only: the Google Ads API marks CampaignBudget.period Immutable (verified from the proto docstrings), so update_budget deliberately does not accept it, despite the issue text suggesting both. Symmetric fail-fast guards: a total without period='CUSTOM_PERIOD' and CUSTOM_PERIOD without a total are both ValueErrors before any API call.
  • Catastrophe guard composition: every amount path (daily + total, create + update) routes through the shared _resolve_amount_micros — positive, _MAX_BUDGET_AMOUNT_MICROS ceiling, bool/inf rejection, pair mutual exclusion. The create path's legacy bare int(amount_micros) (no ceiling) is gone.
  • Guardrail gate composition (feat(policy): built-in STRATEGY.md guardrail gate (OSS enforcement) #360/Meta Ads: add end_time (incl. 0=no-end) and lifetime_budget to meta_ads_ad_sets_update #367): max_lifetime_budget_per_campaign now catches Google totals in both spellings (total_amount currency units, total_amount_micros ÷1e6), and the daily cap additionally reads amount_micros — closing parameter-form bypasses found in security review.
  • BYOD: get_budget shape parity (period=DAILY, null totals).
  • Docs: _mureo-google-ads + _mureo-strategy skills (canonical + packaged byte-identical).

Test plan

  • Client: get_budget field mapping (CUSTOM_PERIOD + DAILY/null-total), total update exact micros / unit conversion / mutual exclusion / zero / ceiling, daily+total combined mask, create with period+total, contradictory combinations, invalid period, no-amount rejection — plus all 16 pre-existing budget tests unchanged
  • Gate: total_amount_micros and total_amount deny over cap / allow under; amount_micros hits the daily cap; no daily↔lifetime cross-application
  • MCP: schemas (anyOf), handler forwarding (total-only update, period+total create, legacy daily create unchanged)
  • ruff / black / mypy (CI flags) clean; full suite green except known local plugin-env noise

TDD (tests first). Reviews: security-reviewer (HIGH total_amount gate bypass → fixed → CONFIRMED) + python-reviewer (MEDIUM schema wording + 2 LOW → fixed → CONFIRM).

https://claude.ai/code/session_0195WBK6YopTveq9RAQE2xMz

hyoshi added 3 commits July 8, 2026 18:19
…ds_ad_sets_update (#367)

- Expose end_time (ISO 8601 string | UNIX timestamp | 0 to clear the end
  date per the Meta Marketing API convention) and lifetime_budget on the
  meta_ads_ad_sets_update tool inputSchema and forward them to
  update_ad_set.
- Normalize the string "0" to the integer 0 before the contradiction
  guard so a string-typed zero cannot sidestep it; reject bool, negative,
  and blank end_time values.
- Validate daily_budget/lifetime_budget mutual exclusion and refuse
  lifetime_budget combined with end_time=0 (a lifetime budget requires a
  schedule end).
- Route lifetime_budget through _validate_positive_money.
- StrategyPolicyGate: new max_lifetime_budget_per_campaign guardrail so
  lifetime-budget mutations cannot sidestep STRATEGY.md budget caps
  (fail-open contract preserved); documented in the strategy skill.

Closes #367
…set (#366)

- get_budget now returns name, period (DAILY / CUSTOM_PERIOD),
  delivery_method, status (enum-mapped), total_budget /
  total_amount_micros (null unless CUSTOM_PERIOD), and reference_count.
- update_budget accepts total_amount / total_amount_micros alongside the
  daily amount, with a dynamic update mask. period is create-only — the
  API marks CampaignBudget.period immutable.
- create_budget accepts period (DAILY / CUSTOM_PERIOD whitelist) and
  total amounts; totals require CUSTOM_PERIOD and vice versa (fail fast).
- All amount paths (daily + total, create + update) route through the
  shared _resolve_amount_micros validator: positive, catastrophe ceiling,
  bool/inf rejection, pair mutual exclusion.
- StrategyPolicyGate: max_lifetime_budget_per_campaign now also catches
  Google total budgets in both spellings (total_amount, currency units;
  total_amount_micros, micros); the daily cap additionally reads
  amount_micros — closing parameter-form bypasses found in review.
- BYOD get_budget shape parity; budget tool schemas + handlers updated;
  _mureo-google-ads and _mureo-strategy skills documented.

Covers issue #366 task 3. Closes #366 together with #369.

Claude-Session: https://claude.ai/code/session_0195WBK6YopTveq9RAQE2xMz
@hyoshi
hyoshi deleted the branch feat/366-google-ads-readonly-criteria-assets July 8, 2026 11:44
@hyoshi hyoshi closed this Jul 8, 2026
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