Repository navigation
feat(cost): Cloudflare as a cost source, read with a pasted API token - #2541
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.
CostConnectionStoreseals one credential per org and provider, refusing another account'sCloudflareCostConnectionServicechecks a token, refuses excess permissions, then stores itCloudflareCostProviderpulls 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
- Migration · Check it is safe on production data and with the code still deployed ·
packages/db/drizzle/20261112221939_cost_connections/migration.sql - Manual · Roll out the
cloudCostfeature flag to the organizations that should see the Cloudflare row
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
loadfilters 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.