Catch approval, mapping, destination and placement errors before a large Meta creative launch reaches Ads Manager.
AI proposes the mapping. Deterministic rules verify every row. A person decides every ambiguous case.
Open the case study · Try the interactive review demo · Fix and revalidate a blocked row
100 synthetic creatives · 70 seeded issues detected and routed · 10 ambiguous cases held for human review · 0 external writes
Designed and built end to end by Mathieu Petroni: product framing, AI orchestration, evaluation design, trust boundaries, deterministic validation, and responsive workflow UX.
Creative launches rarely fail because a team cannot make another ad. They fail in the handoff: a destination drifts, an approval is missing, a placement is invalid, a duplicate is unexplained, or a brief never becomes a clean campaign map.
This project makes that last mile inspectable.
Brief / manifest
↓
bounded mapping proposal
↓
schema + evidence + allowlist policy
↓
field-level human review
↓
deterministic launch QA
↓
local review state + non-executable platform preview
The system cannot publish ads, call Meta, load tokens, upload customer files, or change spend.
The useful model task is narrow: turn ambiguous prose into a typed mapping proposal. It is not a chatbot and it does not control the workflow.
Every field includes:
- a proposed value or an abstention;
- a verbatim evidence quote from the brief;
- evidence strength and an explicitly uncalibrated confidence band;
- a reason code;
- a pending human-review state.
Missing critical information, contradictions, prompt injection, credential signals, customer-data signals, real destinations, invalid values, provider errors, refusals, or malformed output all fail closed.
The default provider is a transparent deterministic baseline for CI. An optional OpenAI Responses API provider uses strict Structured Outputs, no tools, store=False, a bounded timeout, and synthetic-data preflight. No live model score is claimed without a real versioned run.
Read the governed intake evidence, model card, evaluation protocol, and system architecture.
The browser lab closes one small operational loop without pretending a production backend exists. Change placement, approval and UTM fields on a blocked synthetic row, then replay one of eight versioned scenarios generated by the Python validators.
The browser does not duplicate the validation logic. CI regenerates the rule pack from Python and verifies the committed page stays current.
git clone https://github.com/mattyu-dev/creative-launch-workspace.git
cd creative-launch-workspace
python3 -m venv .venv
source .venv/bin/activate
pip install -e '.[dev]'Create a review-only brief proposal:
creative-launch brief-propose \
fixtures/synthetic_campaign_brief.txt \
--out runs/brief/proposal.jsonRecord an explicit decision for every field, then materialize the accepted mapping into a two-row synthetic manifest and run the existing launch validators:
creative-launch brief-review runs/brief/proposal.json \
--brief fixtures/synthetic_campaign_brief.txt \
--reviewer 'Synthetic Approver' \
--decision campaign_key=accepted --decision adset_key=accepted \
--decision objective=accepted --decision country=accepted \
--decision language=accepted --decision placement=accepted \
--decision destination_url=accepted --decision utm_campaign=accepted \
--out runs/brief/review.json
creative-launch brief-materialize \
runs/brief/proposal.json fixtures/synthetic_creative_template.csv \
--brief fixtures/synthetic_campaign_brief.txt \
--reviewer 'Synthetic Approver' \
--decision campaign_key=accepted --decision adset_key=accepted \
--decision objective=accepted --decision country=accepted \
--decision language=accepted --decision placement=accepted \
--decision destination_url=accepted --decision utm_campaign=accepted \
--out-receipt runs/brief/materialization-review.json \
--out-manifest runs/brief/reviewed-manifest.csv \
--out-plan runs/brief/materialization.jsonMaterialization performs the review again in the same process. A saved review receipt is inspectable evidence, not an authorization token.
Run the 36-case benchmark:
creative-launch brief-eval \
--dataset evals/brief_mapping/dataset_v1.jsonl \
--out runs/evals/brief-mapping.jsonThe dataset also contains 12 natural-prose/adversarial cases reserved for a repeated live-provider run:
creative-launch brief-eval \
--dataset evals/brief_mapping/dataset_v1.jsonl \
--provider openai --model gpt-5.6-terra --repetitions 3 \
--out runs/evals/openai-live.jsonBuild the 100-row workspace:
creative-launch plan \
fixtures/fake_agency_creatives/manifest_v2.csv \
--out runs/launch/plan.json \
--review runs/launch/review.md \
--html runs/launch/workspace.html \
--html-audit runs/launch/audit.json \
--state runs/launch/state.json \
--platform-preview runs/launch/platform-preview.json \
--sqlite-db runs/launch/workspace.sqlite3Open runs/launch/workspace.html. Filter blocked rows, inspect the proposed fix, record a decision, then export the reviewed local state.
pip install -e '.[ai]'
export OPENAI_API_KEY='...'
creative-launch brief-propose fixtures/synthetic_campaign_brief.txt \
--provider openai \
--model gpt-5.6-terra \
--out runs/brief/openai-proposal.jsonOnly versioned synthetic fixtures are supported. The brief must include Data classification: synthetic_fixture_only, and its SHA-256 must appear in evals/brief_mapping/manifest.json; the registry is also bound to the dataset hash. Credential, account-ID, customer-data, email, phone and real-destination signals are checked again before any request. The key is read from the environment and is never written to an artifact.
The included 100-row fixture spans three campaigns and ten ad sets:
- 30 rows pass the current offline checks;
- 10 need a reviewer decision;
- 60 are blocked by a concrete issue;
- every row retains mapping, lineage, idempotency, owner, issue and proposed fix.
The repo-native benchmark contains 36 labelled contract cases for the deterministic baseline plus 12 natural-prose and adversarial cases for repeated live-provider evaluation. The deterministic baseline passes every contract gate. That is harness proof, not model-quality proof; no live model score is committed without an authenticated run.
Browser QA exercises seven viewport widths and real interactions. The committed workspace and portfolio Lighthouse accessibility reports score 100/100 on desktop and mobile.
- Brief baseline eval
- Reviewed manifest validation
- Runtime browser QA
- Desktop accessibility
- Mobile accessibility
- Portfolio desktop accessibility
- Portfolio mobile accessibility
- installable Python package with a console entry point;
- versioned contracts and deterministic IDs;
- provider isolation and strict Structured Outputs;
- field-level evidence grounding, abstention, review and deterministic materialization;
- a shared fail-closed review policy across browser and SQLite paths;
- real decodable JPEG/MP4 synthetic fixtures with byte and metadata checks;
- reproducible generated artifacts through
SOURCE_DATE_EPOCH; - Ruff, mypy on trust boundaries, branch coverage, CodeQL and browser QA in CI;
- no network dependency in the static workspace.
This is a synthetic, offline reference implementation. It does not prove Meta API compatibility, production tenancy, customer-data safety, model quality, or business impact. The platform boundary and threat model name what would have to be proven next.
meta_importer/ai/ proposal contracts, providers, policy, review, evals
meta_importer/ manifest QA, state, storage, preview, HTML workspace
evals/brief_mapping/ 48-case synthetic benchmark and generator
fixtures/ synthetic brief, manifests and decodable media
docs/evidence/ versioned eval, browser and accessibility evidence
docs/ product, architecture, AI, QA, security and design notes
tests/ unit, negative-path and contract tests
MIT licensed. Built by Mathieu Petroni.

