Commit Graph
17 Commits
Author SHA1 Message Date
AFFAANhandClaude Opus 5 04ad516fe2 test(campaigns): exercise repository SQL against PostgreSQL
Phase 1 covered only the pure row mapper, so every SQL path in
CampaignRepository was untested — parameter ordering, RETURNING clauses and
transaction behaviour had never been executed. Phase 2 creates real Meta
objects on top of this layer, so the gap is closed first.

Seven tests cover the CRUD round trip, LookupError on unknown ids, event
recording, and that update_campaign_with_event writes both rows or neither.
They skip rather than fail when PostgreSQL is unreachable, and delete every
row they create in a finally block.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 00:51:49 +05:30
AFFAANh 50345a1c89 feat(campaigns): require an operator identity to approve
approved_by was client-supplied, making approval forgeable ahead of
Phase 2 pushing real spend to Meta. Add a proportionate operator-token
check (no login system) via a new require_operator FastAPI dependency:
MASKANX_OPERATOR_TOKENS maps name:token pairs, and approve_campaign now
derives its actor solely from the resolved operator, never from the
client-supplied ActorPayload.actor. Unset env resolves to
"unauthenticated" so local dev and existing tests are not blocked;
set-but-unrecognised tokens 401. submit and reject remain
unauthenticated since neither authorises spend.

Updates test_submit_then_approve_moves_through_states to assert
approved_by == "unauthenticated" (env var unset in tests) instead of
the previously-trusted client actor, since a forged actor must now be
ignored.
2026-08-03 00:17:49 +05:30
AFFAANh 2e46f8485d feat(campaigns): write status change and audit event atomically
_transition previously called update_campaign then add_event as two
separate commits, so a failed add_event left a status change with no
audit row. Phase 2 launches campaigns that authorise spend, so a
compliance hole like that has to close first.

Add CampaignRepository.update_campaign_with_event, which runs the
UPDATE and the event INSERT on one connection inside one transaction
and commits once. Route campaign transitions (submit/approve/reject)
through it instead of the update_campaign + add_event pair; update_campaign
and add_event themselves are unchanged for other callers.

Also update test_campaign_api.py's FakeRepo/monkeypatch to implement the
new repository method, since production code now calls it in the
submit/approve/reject path.
2026-08-03 00:10:40 +05:30
AFFAANhandClaude Opus 5 62c8281ed0 Merge Phase 1 campaign management
Campaign records, approval workflow, budget guardrails against the ad
account minimum, and Meta ad preview. Creates nothing in Meta.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 23:35:04 +05:30
AFFAANhandClaude Opus 5 4d205c1086 docs(campaigns): document phase 1 campaign management
Records what Phase 1 does and deliberately does not do: no Meta writes, no
launch or sync endpoint, previews rendered without creating objects, budget
minimums enforced locally, and the fact that a payment method can only be
attached in Meta Business Manager.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 19:06:14 +05:30
AFFAANhandClaude Opus 5 3859f937a2 fix(campaigns): close PUT budget-minimum bypass and data-destruction bug
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>
2026-08-01 14:00:10 +05:30
AFFAANh d0c8ed43aa feat(campaigns): add campaign API with approval workflow and preview 2026-08-01 13:43:57 +05:30
AFFAANh 4098af5a55 fix(campaigns): use RETURNING in create/update_campaign to avoid extra round-trip and TOCTOU race
create_campaign/update_campaign previously committed then opened a
second connection via get_campaign() just to re-read the row they had
just written, adding an extra round-trip and a race window where a
concurrent delete between commit and re-fetch was misreported as
"disappeared immediately after insert/update". Both now RETURNING the
row from the same statement/cursor that wrote it, using a shared
_RETURNING_CLAUSE derived from _SELECT_COLUMNS so the two column lists
cannot drift apart. update_campaign now raises LookupError for an
unknown id (0 rows affected) instead of misdiagnosing it as a race.

Also: delete_campaign uses the (cur.rowcount or 0) > 0 idiom already
established in postgres_repo.py, the module docstring documents that
this repository's SQL paths rely on integration tests rather than
unit tests, and a new DB-free test asserts the RETURNING and SELECT
column lists stay identical.
2026-08-01 13:36:36 +05:30
AFFAANh 1df49aee37 feat(campaigns): add PostgreSQL campaign repository
Adds CampaignRepository with list/get/create/update/delete for
campaigns plus add_event/list_events for the audit trail, following
the existing postgres_repo.py connection pattern. Replaces the
brief's two bare `assert created/updated is not None` checks with
explicit RuntimeError raises so the guard survives python -O.
2026-08-01 13:27:08 +05:30
AFFAANh 985527a451 fix(meta): cover access-token env handling, JSON-encoded creative, and previews fail-fast contract
Address code review findings on the read-only Meta Graph client: add
monkeypatch-based tests for access_token_from_env/MetaNotConfiguredError,
assert generate_previews JSON-encodes the creative param, pin the
existing fail-fast-on-partial-error contract with a dedicated test, and
declare httpx as an explicit dependency since it was only resolving
transitively.
2026-08-01 13:21:57 +05:30
AFFAANh fd421d2f53 feat(meta): add read-only Graph client for accounts and previews 2026-08-01 13:10:36 +05:30
AFFAANhandClaude Opus 5 4a306c2e18 fix(campaigns): use explicit None check for auto-pause daily budget comparison
validate_guardrails used a truthy check on daily_budget, which silently
skipped the auto-pause-above-daily-budget check when daily_budget was 0 -
exactly the case where a positive auto-pause threshold can never trigger.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 13:06:38 +05:30
AFFAANhandClaude Opus 5 6bb8b1338a feat(campaigns): validate budgets against account minimum
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 12:59:55 +05:30
AFFAANhandClaude Opus 5 74eeb60191 fix(campaigns): use (str, Enum) for 3.10 compat, add state-machine test coverage
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 12:56:09 +05:30
AFFAANhandClaude Opus 5 d22e414869 feat(campaigns): add domain models and status state machine
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 12:30:10 +05:30
AFFAANh 38b6118fc3 feat(campaigns): add campaign management schema migration 2026-08-01 12:23:23 +05:30
AFFAANhandClaude Opus 5 19e1e84fb7 Initial commit: MaskanX backend
Independent FastAPI backend for the MaskanX agentic growth platform.

Includes the agent runtime, MCP client integrations (Meta Ads, LinkedIn,
HubSpot, Tavily, Exa, xAI, Citedy, image generation), PostgreSQL storage
for chats and cron jobs, provider and secret management, and the CLI.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 10:28:22 +05:30