Repository navigation
feat(cost): Vercel as a cost source - #2542
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.
streamBillingChargesstreams and sums a team's FOCUS charges by day, product, project and regionVercelCostSourceServiceadds and removes thecost_sourcesrow 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.