# Shared schemas and guardrails

Used by all MGID MCP skills. Tool names: [ads-mcp-tools.md](ads-mcp-tools.md).

**Thresholds marked `ASSUMPTION`** are unvalidated defaults — tune per account
after backtesting (see [assumptions.md](assumptions.md)).

---

## Recommendation schema

Signal skills emit objects in this shape:

```json
{
  "id": "rec-1-campaign-12061865-source-242669",
  "source_skill": "mgid-source-auditor",
  "action_type": "block_source",
  "entity": {
    "type": "source",
    "id": "242669",
    "name": "example.pt",
    "campaign_id": 12061865
  },
  "reason": "CPA $32.05 is 2.1× target ($15.50) with 828 clicks in 7d",
  "evidence": {
    "metric": "cpa",
    "current": 32.05,
    "target": 15.5,
    "baseline": null,
    "sample_size": { "clicks": 828, "conversions": 5, "spend_usd": 160.25 }
  },
  "proposed_change": {
    "field": "status",
    "from": "active",
    "to": "blocked"
  },
  "confidence": "high",
  "risk": "low",
  "impact_estimate": {
    "spend_delta_daily_usd": [-23, -18],
    "conversions_delta_daily": null,
    "notes": "Estimated wasted spend removed"
  },
  "blocked_by": []
}
```

Use **human-readable** `id` values (rank + entity), not UUIDs.

### `action_type` enum

| Value | ads-mcp write (when available) |
|---|---|
| `increase_budget` | `update_campaign` (`limits.dailyLimit`) |
| `increase_tcpa` | `update_campaign` (`bidding.price`) |
| `reallocate_budget` | `update_campaign` (limits) |
| `adjust_qf` | `update_quality_factors` |
| `block_source` / `unblock_source` | `update_source_filters` |
| `block_widget` / `unblock_widget` | `update_source_filters` |
| `block_provider` / `unblock_provider` | `update_source_filters` or provider API |
| `pause_teaser` / `unpause_teaser` | `set_teaser_status` |
| `alert_anomaly` | none |
| `fix_tracking` | none (guidance) |
| `launch_teaser_variant` | manual / future tool |

### `entity.type` enum

`account` | `campaign` | `source` | `widget` | `provider` | `teaser`

---

## OptimizationPlan schema

Written to a **file** (e.g. `optimization-plan-{plan_id}.json`), not pasted into chat.

```json
{
  "plan_id": "opt-2026-07-06-001",
  "mode": "simulation",
  "generated_at": "2026-07-06T14:00:00Z",
  "scope": {
  "auth_id": 200316,
  "client_id": 330838,
  "account_id": 330838,
    "total_active_campaigns": 16,
    "analyzed_campaign_ids": [12061397, 12061865],
    "analyzed_count": 16,
    "scope_mode": "all_active",
    "scope_limit": 25,
    "user_confirmed": true
  },
  "date_range": { "from": "2026-06-29", "to": "2026-07-05" },
  "goal": { "type": "cpa", "target": 15.5, "target_source": "campaign_bidding" },
  "gates": { "tracking_health": "pass", "details": [] },
  "recommendations": [],
  "suppressed": [],
  "stats_snapshot": {
    "12061865": { "spent": 7422.99, "conversions_buy": 439, "cpa": 16.91 }
  }
}
```

### Stats snapshot (apply-time drift check)

Store plain numeric aggregates per campaign at plan generation. At apply, re-fetch
`get_campaign_statistics` totals; if **spent** or **CPA** moved more than
**10%** (`ASSUMPTION`) vs snapshot, warn and require re-confirmation.

Do **not** use sha256 — agents compare numbers directly.

---

## Chat vs file output

| Channel | Content |
|---|---|
| **Chat** | Summary block + recommendation **table** only |
| **File** | Full `OptimizationPlan` JSON for apply phase |

Do not emit full plan JSON or audit JSON into chat unless the user asks.

---

## Global guardrails

1. Never apply without explicit user approval.
2. **ASSUMPTION** min data: sources/widgets/providers ≥ 50 clicks or ≥ 5 conversions;
   teasers ≥ 100 clicks or 1,000 impressions; budget moves need ≥ 3 days donor spend.
3. **ASSUMPTION** max budget shift 20% of donor daily limit per plan.
4. **ASSUMPTION** max QF change ±25% per entity per plan.
5. **ASSUMPTION** min spend $10 before block recommendation.
6. Never pause the last active teaser in a campaign.
7. Return impact **ranges**, confidence, and risk.
8. **CPA Tune learning:** do not recommend manual source blocks on campaigns in
   learning phase — see [assumptions.md](assumptions.md).

---

## Suppression reason codes

| Code | Meaning |
|---|---|
| `tracking_unhealthy` | Failed tracking gate |
| `insufficient_data` | Below min clicks/spend |
| `cpa_tune_learning` | CPA Tune still learning — manual block harmful |
| `guardrail_cap` | Exceeded max QF or budget shift |
| `last_active_teaser` | Cannot pause only creative |
| `top_converter` | Block downgraded |
| `permission_denied` | Missing write scope |
| `mcp_tool_unavailable` | Write tool not exposed |
