Tempio Docs

How a change gets made in Tempio

Every write to an ad entity is a change_request row before it is a change on a platform. This note is the lifecycle — the seven statuses, who may approve, what no rule may ever approve, and what an agent should do at each step.

Last reviewed 2026-09-13.

Nothing in Tempio writes to a campaign, an ad group or an ad directly. Every such write becomes a change_request row first, and the row is what an agent creates, edits, approves and reverts. That is an architecture rule, not a promise that a person reads every one: a change is approved by you, or by a rule you wrote, and both paths go through the same row.

Planning

Before drafting, know which shape you are asking for. CHANGE_OPERATIONS names every operation a row may carry — the creates (create_campaign, create_ad_group, create_ad, create_creative, create_keyword, create_negative_list, create_asset_group, create_lead_form), the updates (update_budget, update_status, update_name, update_targeting, update_bid, update_schedule, update_creative, update_keyword, update_negative_list, update_asset_group, update_account_defaults), and the two that are neither (upload_media, remove_entity), plus the escape hatch's platform_api_request.

An operation the connected platform's adapter does not list in capabilities.operations is refused at draft time, before a reviewer ever sees it. Check what a connection can express rather than drafting hopefully.

Doing

createDraft runs in a fixed order, and every step of it is a refusal you can plan around: the connection must belong to the workspace; the adapter must support the operation; the payload is parsed against its versioned Zod schema; the target account or campaign must belong to this workspace and this connection, so a draft cannot steer another connection's spend; the payload's currency is stamped from the owning ad account and a client-supplied value is overwritten without comment; and the budget guardrails run. The row is inserted as pending_approval with the mirror's pre-image stamped on it, which is what makes the before-and-after panel and the undo possible later.

Several operations submitted together become one batch through createDraftBatch: the rows share a correlationId, keep their order in batchSeq, and a child create that depends on a parent carries dependsOnId. A batch is one reviewable unit. It is approved whole or not at all — if a row is refused during the claim, the rows already claimed are returned to the queue with the reason on their audit rows.

updateDraft edits a row in place. It is allowed while a draft is still pending_approval, and also once it has failed, which is how a stopped batch is fixed and resumed. The edited payload is re-parsed, re-stamped with the account's currency, re-guarded and re-validated against the adapter; nothing about having been drafted once exempts it from a check.

Approving is approveDrafts, over either a list of draftIds or one correlationId. Each row is claimed atomically — an UPDATE … WHERE status = 'pending_approval' moving it to applying — so a second caller gets an invalid_state rather than a double apply. The claim re-runs the kill switch and the budget check against current state, because the world moves between drafting and approving. Only then is the apply dispatched. rejectDrafts is the same shape in the other direction and applies nothing.

The seven statuses of CHANGE_STATUSES are draft, pending_approval, applying, applied, partial, failed and rejected. partial means the operation half-landed — Meta's create_ad is two Graph calls, so a created creative with a failed ad is honestly neither applied nor failed. A row left in applying after an adapter threw is deliberate: the outcome is unknown, and resolveStuckDraft is the manual path for a row stuck there longer than STUCK_APPLYING_MINUTES (15), which marks it failed with a note saying so.

Every result carries a review deep-link: reviewUrlFor points at the batch when there is a correlationId, at the single draft when there is not, and at the queue when a write named no row at all. Hand a person that URL — it is absolute, because a bare path is not something anyone can open.

Who may approve

APPROVER_KINDS is human, mcp_key and policy. A person approving in the app records their user. An MCP key minted with the approve scope may approve, including a draft it wrote itself, and the record says which — a key approving its own work is a different fact from a person approving it. The in-app chat agent never holds that scope. A rule the workspace wrote is the third authority, and the row names the rule.

What no rule may ever approve is fixed in POLICY_NEVER_OPERATIONS: every create_* operation, upload_media, remove_entity, update_account_defaults, and platform_api_request. A rule is written against a shape, and a raw platform request has none. A rule also refuses a budget or bid increase, an amount over its own stated cap, an amount in a currency other than its own, a change whose direction cannot be read for want of a pre-image, a draft the adapter's dry run refused, and anything on a connection whose kill switch is on. Because a batch is one unit, a rule clears a batch only when it clears every row in it.

Undo

revertDraft builds a NEW draft from the pre-image and sends it to the same gate. Nothing is reversed behind anyone's back. It is refused for remove_entity — the entity is gone on the platform and nothing here brings it back — and for a row that is not applied or partial, because there is nothing to put back.

So: never describe a Tempio write as having been made. Say that it is drafted, name what will change, give the review link, and say who or what can clear it.

Sources

  • createDraft, approveDrafts, rejectDrafts, updateDraft, resolveStuckDraft — repo://apps/app/lib/ads/drafts.service.ts (read 2026-09-13)
  • createDraftBatch, reviewUrlFor, revertDraft — repo://apps/app/lib/ads/drafts/batch.ts (read 2026-09-13)
  • The policy evaluator and POLICY_NEVER_OPERATIONS — repo://apps/app/lib/ads/policy/auto-approve.ts (read 2026-09-13)
  • CHANGE_STATUSES, CHANGE_OPERATIONS, APPROVER_KINDS — repo://packages/db/schema.ts (read 2026-09-13)
  • Drafts — the product page for the same lifecycle — repo://apps/docs/content/docs/drafts/index.mdx (read 2026-09-13)