PUT /campaigns/{id} validated the raw request payload instead of the
merged campaign state, so omitting ad_account_id skipped the minimum
check and a full model_dump() overwrote every unset field (targeting,
guardrails, objective, etc.) with its default, silently destroying
data. Validate against the effective post-merge budget/guardrails/
account and only apply fields the client actually sent
(exclude_unset=True).
Also: persist company_id on UPDATE (was accepted but dropped), log
a warning when Meta account lookups fail so the budget-minimum
fail-open is observable, reword the non-editable-status 409 message
to not prescribe an impossible reject-to-draft action, and add
coverage for Meta error surfacing, LookupError-to-404 on PUT/submit,
reject's pending_approval->draft transition, and GET-unknown-id 404.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>