feat(store,webhook): usage metering, daily caps, and per-installation concurrency (#15)#57
Merged
Merged
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR implements Issue #15’s usage metering and installation-tier limits in the GitHub adapter, adding a tenant-scoped usage rollup endpoint plus enforcement for per-installation daily task caps (intake) and concurrency caps (claim).
Changes:
- Add
GET /api/github/usagewith the same tenant boundary + audit trail semantics as the task API. - Enforce
max_tasks_per_dayat webhook intake (recording overflow deliveries asignored:quota_exceeded) andmax_concurrentat claim time (skip saturated installations without starving others). - Extend config schema (
[installations.limits]) and store logic/tests to support these behaviors.
Reviewed changes
Copilot reviewed 8 out of 9 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| README.md | Updates capability matrix to include usage metering + tier limits. |
| crates/worker/src/lib.rs | Threads per-installation concurrency caps into task-claiming. |
| crates/webhook/src/routes.rs | Adds /api/github/usage route and enforces daily intake cap with tests. |
| crates/webhook/Cargo.toml | Adds chrono dependency for rolling 24h cutoff calculation. |
| crates/store/src/lib.rs | Implements claim-time concurrency skipping, metering rollup, and daily-cap helper with tests. |
| crates/server/src/main.rs | Wires the new /api/github/usage route into the server router. |
| crates/config/src/lib.rs | Adds installation limits config, helpers, and doctor validation for zero limits. |
| config/example.toml | Documents [installations.limits] example configuration. |
| Cargo.lock | Locks chrono for the webhook crate dependency change. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+557
to
+561
| "SELECT t.installation_id, t.repo, t.familiar_id, | ||
| COUNT(DISTINCT t.id), | ||
| SUM(CASE WHEN t.state = 'completed' THEN 1 ELSE 0 END), | ||
| SUM(CASE WHEN t.state = 'failed' THEN 1 ELSE 0 END), | ||
| COALESCE(SUM( |
Comment on lines
+424
to
+430
| if let Err(e) = state | ||
| .store | ||
| .record_delivery(delivery, Routing::Ignored("quota_exceeded")) | ||
| .await | ||
| { | ||
| return storage_unavailable(e); | ||
| } |
Comment on lines
+413
to
+417
| let cutoff = (chrono::Utc::now() - chrono::Duration::hours(24)).to_rfc3339(); | ||
| match state | ||
| .store | ||
| .tasks_created_since(task.installation_id, &cutoff) | ||
| .await |
…ation concurrency (#15) Closes #15. Metering: a usage() rollup over the durable store aggregates task counts, terminal outcomes, and attempt wall-clock runtime by installation, repo, and familiar, served at GET /api/github/usage behind the same fail-closed tenant boundary and api_audit trail as the task list (#3). Limits ride the [[installations]] policy (#7) as [installations.limits]: - max_tasks_per_day is enforced at intake: over-quota deliveries are recorded ignored:quota_exceeded (visible in the delivery audit), answered 200 with the reason, and never become tasks - max_concurrent is enforced at claim: a saturated installation's queued tasks are skipped without starving other installations, and capacity frees as its running tasks finish (the claim loop's poll timer retries) Doctor rejects zero limits (nothing would ever run). Metering events are the store's own rows — tasks, attempts, deliveries — so billing and support read the same audit surface Cave does. Per-container resource limits (cpu/mem/disk) remain with worker isolation (#5). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Val Alexander <bunsthedev@gmail.com>
BunsDev
force-pushed
the
feat/issue-15-metering
branch
from
July 7, 2026 06:32
a7e0aaa to
23fc82f
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #15 — makes familiar execution meterable and boundable.
Metering
Store::usage(scope): task counts, completed/failed outcomes, and attempt wall-clock runtime rolled up by installation × repo × familiar — computed from the durable rows billing/support/Cave already shareGET /api/github/usagebehind the same fail-closed tenant boundary as/tasks(Add tenant-scoped task API authentication for CovenCave and hosted clients #3): tenant tokens see only their installation, every read auditedLimits (
[installations.limits], riding the #7 policy blocks)max_tasks_per_day— enforced at intake over a rolling 24h window: overflow deliveries are recordedignored:quota_exceededin the delivery audit, answered200 {"ignored":"quota_exceeded"}, and never become tasksmax_concurrent— enforced at claim: saturated installations are skipped without starving others (proven: capped installation's second task waits while another installation's task claims; capacity frees on finish)Scope note
Per-container cpu/mem/disk/network limits belong to worker isolation (#5), as the issue's dependency notes.
Verification
-D warningsclean · python gates green · demo 21/21