Skip to content

feat(ads): ads read surfaces — routes, CLI, MCP toolkit, doctor (v4.79.0)#696

Open
arberx wants to merge 1 commit into
feat/openai-ads-read-pathfrom
feat/openai-ads-surfaces
Open

feat(ads): ads read surfaces — routes, CLI, MCP toolkit, doctor (v4.79.0)#696
arberx wants to merge 1 commit into
feat/openai-ads-read-pathfrom
feat/openai-ads-surfaces

Conversation

@arberx

@arberx arberx commented Jun 11, 2026

Copy link
Copy Markdown
Member

What

Surfaces half of the ads read path (PR 3b of plans/openai-ads-integration.md). Stacked on #695 (engine) → #694 (client) — review bottom-up.

  • Routes: POST /ads/connect (validates the SDK key against the upstream ad account via injected verifyAdsAccount BEFORE any write; credential goes through the injected adsCredentialStore into config.yaml, never the DB), DELETE /ads/connection, GET /ads/status, POST /ads/sync (run row + host callback), GET /ads/campaigns (nested snapshots incl. context hints), GET /ads/insights (derived ctr/cpcMicros server-side, null on zero denominators), GET /ads/summary (campaign-level totals only — ad-group rows are subdivisions and never double-counted). All typed in the OpenAPI spec, SDK regenerated.
  • CLI: canonry ads connect|disconnect|status|sync|campaigns|insights|summary, jsonl on the collections, money rendered via formatMicros.
  • MCP: new ads toolkit — 4 read tools + the ads-sync trigger; connect/disconnect classified deferred with rationale (credential-bearing). Tool counts move 105→110 (69→73 read).
  • Doctor: ads.auth.connection + ads.data.recent-sync, both skipped when not connected, so the integration stays invisible for non-ads projects.
  • data-refresh now fans out to ads when connected (self-gating like the other integrations).
  • Lint: paid/sponsored vocabulary rule banning paid-vs-organic conflations.

Tests

TDD per layer: contracts calculation tests (exact micros math from real captured values), 11 route tests (connect validation/idempotency, audit assertions, derived-metric math, summary no-double-counting, level filters), 6 doctor tests, data-refresh + MCP registry/stdio assertions updated. Full suite 4337 green; typecheck and lint clean; codegen drift gate satisfied.

🤖 Generated with Claude Code

…9.0)

Surfaces half of the OpenAI ads read path (stacked on the ads-sync engine):

- contracts: ads DTOs with derived-metric helpers (adsCtr / adsCpcMicros,
  null on zero denominators) + calculation tests
- api-routes: /ads/{connect,connection,status,sync,campaigns,insights,
  summary}; connect validates the SDK key upstream via the injected
  verifyAdsAccount before any write and stores the credential through the
  injected adsCredentialStore (config.yaml, never the DB); summary sums
  campaign-level rollups only so ad-group subdivisions are never double
  counted; all ops in the OpenAPI spec with typed schemas, SDK regenerated
- canonry: cnry ads connect|disconnect|status|sync|campaigns|insights|
  summary (jsonl on collections, money via formatMicros), data-refresh
  fans out to ads when connected, server wires the credential store +
  verifier + manual-sync callback
- MCP: new 'ads' toolkit (4 read tools + ads-sync trigger); connect/
  disconnect classified deferred (credential-bearing); tool counts 110/73
- doctor: ads.auth.connection + ads.data.recent-sync (skipped when not
  connected), wired through DoctorContext
- eslint: paid/sponsored vocabulary rule banning paid-vs-organic
  conflations (ad mentions, paid citations, …)
- docs: AGENTS key-files + doctor table + schedule examples, docs/mcp.md
  toolkit table, skills canonry-cli.md ads section, db-dto-coverage
  entries flipped from internal-only to dto

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant