Skip to content

feat(cost): Cloudflare as a cost source, read with a pasted API token - #2541

Merged
Makisuo merged 22 commits into
mainfrom
cost/cloudflare-source
Nov 13, 2026
Merged

Makisuo merged 22 commits into
mainfrom
cost/cloudflare-source

Merge remote-tracking branch 'origin/main' into cost/cloudflare-source

14eb216
Select commit
Loading
Failed to load commit list.
Maple Review Bot / Maple / review succeeded Nov 13, 2026 in 3m 50s

Confidence 10/10 · No issues found

🟢 Confidence 10/10 · safe to merge. Sealing, org scoping, upsert guard and pagination are pinned by service tests; the least-privilege probes are exercised only against the fake.
no findings · tests covered · risk high · 4/4 new units observable

Adds Cloudflare as a cost source read with a pasted API token: a sealed cost_connections row, a PUT/DELETE /internal/cost/cloudflare route behind integrations:manage, a billing client and normalizer, and a dialog driven by credential-sources.ts. Safe to merge. The two open VcsWebhookEnqueueFailure issues pointing at http-graph.ts are untouched: this change only registers a handler there.

  • CostConnectionStore seals one credential per org and provider, refusing another account's
  • CloudflareCostConnectionService checks a token, refuses excess permissions, then stores it
  • CloudflareCostProvider pulls usage in 55-day pieces and places subscription fees
  • The cost dialog's credential step is one data-driven form for OpenRouter and Cloudflare

Before merge

Production impact

Open errors in the changed files (1)
Issue Service Occurrences File
@maple/api/routes/VcsWebhookEnqueueFailure (2 issues) maple-api 9 apps/api/src/runtime/http-graph.ts

After this merges, Maple checks whether they stop.

Telemetry this change adds and removes (13)
  • ➕ span name HttpCloudflareCost.set · apps/api/src/routes/internal/cloudflare-cost.http.ts:36
  • ➕ span name HttpCloudflareCost.remove · apps/api/src/routes/internal/cloudflare-cost.http.ts:47
  • ➕ span name CloudflareCostConnectionService.validateWithCloudflare · packages/backend/src/services/cost/CloudflareCostConnectionService.ts:85
  • ➕ span name CloudflareCostConnectionService.set · packages/backend/src/services/cost/CloudflareCostConnectionService.ts:129
  • ➕ span name CloudflareCostConnectionService.remove · packages/backend/src/services/cost/CloudflareCostConnectionService.ts:168
  • ➕ span name CostConnectionStore.save · packages/backend/src/services/cost/CostConnectionStore.ts:88
  • ➕ span name CostConnectionStore.remove · packages/backend/src/services/cost/CostConnectionStore.ts:159
  • ➕ span name CostConnectionStore.load · packages/backend/src/services/cost/CostConnectionStore.ts:180
  • ➕ span name CostSyncService.reconcileConnections · packages/backend/src/services/cost/CostSyncService.ts:236
  • ➕ span name CloudflareCostProvider.pull · packages/backend/src/services/cost/providers/cloudflare/CloudflareCostProvider.ts:70
  • ➕ attribute maple.cost.error_reason · packages/backend/src/services/cost/providers/cloudflare/CloudflareCostProvider.ts:118
  • ➕ attribute maple.cost.cloudflare.listed · packages/backend/src/services/cost/providers/cloudflare/cloudflare-billing-api.ts:68
  • ➕ attribute maple.cost.cloudflare.read · packages/backend/src/services/cost/providers/cloudflare/cloudflare-billing-api.ts:69
What was checked
  • load filters org, provider and connection id, so a source never opens another org's credential (CostConnectionStore.ts:189)
  • The upsert is guarded on accountId, so a token reading another account writes nothing (CostConnectionStore.ts:127)
  • Each request spans 55+31+1 = 87 days, inside Cloudflare's 90-day cap (CloudflareCostProvider.ts:20, cloudflare-billing-api.ts:197)
Observability coverage: 4 of 4 changes observable
Change Kind Observable Evidence
PUT/DELETE /internal/cost/cloudflare inbound yes Effect.withSpan("HttpCloudflareCost.set"/".remove") in apps/api/src/routes/internal/cloudflare-cost.http.ts:36, same as the sibling cost routes
Cloudflare billing API calls (accounts, usage, subscriptions, token probes) outbound yes HttpClient client spans with peer.service=cloudflare and maple.cost.cloudflare.* error attributes (cloudflare-billing-api.ts:110-118, cloudflare-token-probes.ts:68)
Cloudflare cost sync pull background yes Effect.fn("CloudflareCostProvider.pull") with maple.cost.start_day, maple.cost.rows, maple.cost.cloudflare.fees_skipped
Connection-to-source reconcile on each sync tick background yes Effect.fn("CostSyncService.reconcileConnections") annotating maple.cost.sources_created/_removed (CostSyncService.ts:236)

14eb216 · Reply "won't fix" to dismiss a finding, or mention @maple-review-bot to ask about one.