Skip to content

feat(cost): Vercel as a cost source - #2542

Merged
Makisuo merged 11 commits into
mainfrom
cost/vercel-source
Nov 13, 2026
Merged

Makisuo merged 11 commits into
mainfrom
cost/vercel-source

fix(cost): name a row's actions by account when an org has several so…

f621808
Select commit
Loading
Failed to load commit list.
Maple Review Bot / Maple / review succeeded Nov 13, 2026 in 2m 53s

Confidence 10/10 · No issues found

🟢 Confidence 10/10 · safe to merge. Clean diff, tests pin the sync windows, the add lock and the dialog states; only Vercel's live FOCUS charge shape is unverified.
no findings · tests covered · risk medium · 4/4 new units observable

Adds Vercel as a cost source: a charges adapter, a cost-source service behind cloudCost with audited add and remove, the dialog step and a resource dimension on /cost. No new table or migration, and the tenant scoping and the connection-lock decisions hold. The VcsWebhookEnqueueFailure errors in http-graph.ts are untouched — this diff only registers the new route there, so it neither fixes nor worsens them.

  • streamBillingCharges streams and sums a team's FOCUS charges by day, product, project and region
  • VercelCostSourceService adds and removes the cost_sources row keyed by the connection's team id
  • Reconnect removes the source on a team change and releases it on a new token, inside the lock
  • Cost page groups and filters by resource, shown only where some cost names one

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.

Production traffic of the changed files (last 7 days)
File Calls/day Busiest operations
packages/backend/src/queries/cost-reads.ts 32 CostReads.sources 32/day, 0.0% err
Telemetry this change adds and removes (13)
  • ➕ span name HttpVercelCost.status · apps/api/src/routes/internal/vercel-cost.http.ts:33
  • ➕ span name HttpVercelCost.add · apps/api/src/routes/internal/vercel-cost.http.ts:47
  • ➕ span name HttpVercelCost.remove · apps/api/src/routes/internal/vercel-cost.http.ts:59
  • ➕ span name CostSyncService.reconcileVercel · packages/backend/src/services/cost/CostSyncService.ts:223
  • ➕ span name VercelCostSourceService.status · packages/backend/src/services/cost/VercelCostSourceService.ts:130
  • ➕ span name VercelCostSourceService.add · packages/backend/src/services/cost/VercelCostSourceService.ts:143
  • ➕ span name VercelCostSourceService.remove · packages/backend/src/services/cost/VercelCostSourceService.ts:214
  • ➕ span name VercelCostProvider.read · packages/backend/src/services/cost/providers/vercel/VercelCostProvider.ts:116
  • ➕ attribute maple.cost.vercel.error · packages/backend/src/services/cost/providers/vercel/VercelCostProvider.ts:166
  • ➕ attribute maple.cost.vercel.status · packages/backend/src/services/cost/providers/vercel/VercelCostProvider.ts:167
  • ➕ attribute maple.cost.vercel.detail · packages/backend/src/services/cost/providers/vercel/VercelCostProvider.ts:168
  • ➕ attribute maple.cost.vercel.charge_groups · packages/backend/src/services/cost/providers/vercel/VercelCostProvider.ts:187
  • ➕ attribute maple.cost.error_reason · packages/backend/src/services/cost/providers/vercel/VercelCostProvider.ts:192
What was checked
  • Every Vercel query filters orgId (VercelCostProvider.read, cost-reads.ts:325, VercelCostSourceService.load)
  • The add re-checks connection id, team id and the sealed token under a share lock before inserting
  • Only a 200 counts as the whole range; 204/206 and unreadable lines fail the pull, so no half day is stored
Observability coverage: 4 of 4 changes observable
Change Kind Observable Evidence
GET/PUT/DELETE /internal/cost/vercel handlers inbound HTTP route yes Each handler pipes Effect.withSpan("HttpVercelCost.status|add|remove") (vercel-cost.http.ts:33,47,59)
Vercel GET /v1/billing/charges outbound HTTP call yes Effect.annotateSpans("peer.service", "vercel") in streamBillingCharges (vercel/api.ts:550); failure kind, status and detail annotated on the pull span
Cost sync for vercel sources background worker step yes VercelCostProvider.read annotates maple.cost.provider/start_day/end_day/rows; CostSyncService.reconcileVercel annotates maple.cost.sources_removed
Cost reads: sources with the Vercel team slug database query yes CostReads.sources Effect.fn span plus the extra VercelConnections read, tenant filtered (cost-reads.ts:318-339)

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