Skip to content

feat: per-developer billing concurrency stat endpoints (GrantFox FWC26) - #849

Closed
eliassmthl-collab wants to merge 1 commit into
CalloraOrg:mainfrom
eliassmthl-collab:task/concurrency-stat
Closed

feat: per-developer billing concurrency stat endpoints (GrantFox FWC26)#849
eliassmthl-collab wants to merge 1 commit into
CalloraOrg:mainfrom
eliassmthl-collab:task/concurrency-stat

Conversation

@eliassmthl-collab

Copy link
Copy Markdown

Summary

Implements per-developer billing concurrency stat endpoints for the GrantFox FWC26 campaign (issue #608).

Admins can now inspect live billing concurrency pressure per developer in real time — useful for diagnosing overdraft guards, rate-limit tuning, and on-call triage.


Endpoints

GET /api/admin/metrics/concurrency

Returns a real-time snapshot of active billing slot counts for all developers currently holding (or recently holding) a slot. Developers with zero active slots are omitted to keep the payload compact.

{
  "data": {
    "developerCounts": { "dev_abc": 1, "dev_xyz": 2 },
    "totalActive": 3,
    "campaign": "GrantFox FWC26"
  }
}

GET /api/admin/metrics/concurrency/:developerId

Returns the active slot count, queue depth, and at-limit flag for a single developer. Safe to call for unknown IDs — returns zeros rather than 404.

{
  "data": {
    "developerId": "dev_abc",
    "activeCount": 1,
    "queueLength": 0,
    "atLimit": true,
    "campaign": "GrantFox FWC26"
  }
}

Both endpoints require admin auth (x-admin-api-key) and are protected by the IP allowlist — inherited from the parent admin router, no duplicate guard needed.


Changes

src/utils/developerSemaphore.ts

Added three public introspection methods, mirroring the existing KeySemaphore API:

  • getActiveSlotCount(developerId) — active slots for one developer, 0 if not tracked
  • getQueueLength(developerId) — requests waiting for a slot (queue depth)
  • isAtLimit(developerId) — whether the developer has exhausted their concurrency budget

These are read-only; they do not affect semaphore behaviour.

src/routes/admin/metrics.ts (new)

Router factory createAdminMetricsConcurrencyRouter(deps?) with:

  • Dependency injection — accepts a DeveloperSemaphore instance; defaults to the shared billingConcurrencySemaphore from billing.ts so production sees live traffic without any wiring
  • Input validation — Zod schema enforces developerId is non-empty before the handler runs
  • Structured audit logging — every read emits a logger.audit() event with event name, actor, client IP, User-Agent, and x-request-id/x-correlation-id for full traceability
  • Error handlingAppError subtypes propagate via next(); unexpected errors are wrapped in InternalServerError and logged before forwarding

src/routes/admin.ts

Imported and mounted the new router at /metrics.

src/routes/admin/metrics.test.ts (new)

14 focused unit tests covering:

  • Empty state (no active developers)
  • Single developer holding one slot
  • Single developer holding multiple slots
  • Multiple developers active simultaneously
  • Slot release clears the count
  • atLimit: true when concurrency limit is saturated
  • Admin authentication guard (401 without key)
  • Missing developerId path segment falls through gracefully
  • campaign label present in both response shapes

Tests use an isolated DeveloperSemaphore instance (not the singleton) so they are fully independent and do not affect each other or production state.

README.md

Documented both endpoints in the "What's included" section alongside the existing admin endpoints.


Test output

PASS src/routes/admin/metrics.test.ts
  GET /api/admin/metrics/concurrency
    ✓ returns empty counts when no developers are active
    ✓ reflects a single developer holding one slot
    ✓ reflects a developer holding multiple slots
    ✓ reflects multiple developers holding slots simultaneously
    ✓ omits developers with zero active slots from developerCounts
    ✓ requires admin authentication
    ✓ includes campaign label in the response
  GET /api/admin/metrics/concurrency/:developerId
    ✓ returns zero counts for an unknown developer
    ✓ returns active count for a developer currently holding a slot
    ✓ returns activeCount=0 once the slot is released
    ✓ reports atLimit: true when developer is at their concurrency limit
    ✓ requires admin authentication
    ✓ returns 404 when developerId path segment is missing
    ✓ includes campaign label in the detail response

Tests: 14 passed, 14 total

PASS src/utils/developerSemaphore.test.ts  (3 existing — no regressions)
PASS src/services/billing.semaphore.test.ts  (4 existing — no regressions)

Lint: clean on all changed files (eslint exits 0).


Security notes

  • Endpoints are read-only — no state mutation possible
  • Auth and IP allowlist enforced by the parent admin router before any handler runs
  • developerId in audit logs comes from validated route params, not raw user input
  • Response payload contains only in-process semaphore state — no DB queries, no PII beyond the developer ID used as a semaphore key

Closes #608

Implements GET /api/admin/metrics/concurrency and
GET /api/admin/metrics/concurrency/:developerId backed by the shared
billingConcurrencySemaphore so counts reflect live billing traffic.

Changes:
- src/utils/developerSemaphore.ts: add getActiveSlotCount(),
  getQueueLength(), and isAtLimit() methods to DeveloperSemaphore
- src/routes/admin/metrics.ts: createAdminMetricsConcurrencyRouter()
  factory with list + detail endpoints; DI for test isolation;
  structured audit logging with correlation ID, client IP, user agent
- src/routes/admin.ts: mount /metrics router
- src/routes/admin/metrics.test.ts: 14 focused tests covering empty
  state, single/multi-slot, multi-developer, slot release, atLimit
  flag, auth guard, and campaign label
- README.md: document the two new admin endpoints

Closes #608
@drips-wave

drips-wave Bot commented Jul 26, 2026

Copy link
Copy Markdown

@eliassmthl-collab Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@eliassmthl-collab eliassmthl-collab closed this by deleting the head repository Jul 27, 2026
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.

Add per-developer concurrency stat

1 participant