You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
evaluateTenantQuota() (packages/loopover-engine/src/tenant-quota.ts) is a complete, tested, pure quota-decision function (compute/time/concurrency dimensions) — but its only caller anywhere in src/**/packages/** is customer-loop-view.ts, which uses it purely to render a read-only dashboard number and explicitly discards the concurrency dimension. src/queue/job-dispatch.ts — the actual dispatch path — has zero tenant/quota references. A tenant that exceeds its compute/time/concurrency allocation is not stopped anywhere in the runtime today. The issue that produced this function (#4796, closed) had explicit deliverables/acceptance-criteria calling for a tenant to be "stopped cleanly" when over quota; what shipped didn't meet that bar.
Area
Platform / cost-control. Directly addresses the maintainer's own standing top concern: an unbounded tenant burning real compute/API spend with nothing stopping it.
Proposal
Wire evaluateTenantQuota() into job-dispatch.ts's real admission path: before dispatching a job for a tenant, evaluate its quota and refuse dispatch (with a clear, actionable rejection) when exceeded — mirroring the concurrency dimension customer-loop-view.ts currently discards, not just compute/time.
This does not need to wait on #4920 (the persisted plan/quota/subscription data model, still fully undone) to provide real protection now — scope the quota SOURCE as config/env-injectable defaults for this pass (a real but interim input), so the actual numbers are trivially swappable for #4920's persisted model later without touching the enforcement wiring itself. Don't invent plan-tier business logic here — that's #4920/#4804's job; this issue is the mechanism, not the numbers.
Deliverables
Real admission-time enforcement in job-dispatch.ts calling evaluateTenantQuota() (all dimensions, including concurrency) before dispatching a tenant's job.
A config/env-sourced interim quota input, structured so it can be swapped for a persisted-data-model source later without changing the enforcement call site.
A clear, tenant-facing rejection reason when blocked (not a silent drop).
Resources
packages/loopover-engine/src/tenant-quota.ts (the existing pure decision function)
packages/loopover-engine/src/customer-loop-view.ts (the only current, non-enforcing consumer)
src/queue/job-dispatch.ts (the real dispatch path, currently tenant-unaware)
No plan-tier business logic, no real pricing/limit numbers invented here — mechanism only, using config/env-injectable interim values. No reward/pricing figures in this issue.
maintainer-only — this is the admission-control path for the whole dispatch queue; a mistake here fails either open (quota does nothing, the exact gap this closes) or closed (blocks legitimate work fleet-wide). First pass needs maintainer review, matching this repo's own treatment of the gate's own merge/close authority.
Problem
evaluateTenantQuota()(packages/loopover-engine/src/tenant-quota.ts) is a complete, tested, pure quota-decision function (compute/time/concurrency dimensions) — but its only caller anywhere insrc/**/packages/**iscustomer-loop-view.ts, which uses it purely to render a read-only dashboard number and explicitly discards the concurrency dimension.src/queue/job-dispatch.ts— the actual dispatch path — has zero tenant/quota references. A tenant that exceeds its compute/time/concurrency allocation is not stopped anywhere in the runtime today. The issue that produced this function (#4796, closed) had explicit deliverables/acceptance-criteria calling for a tenant to be "stopped cleanly" when over quota; what shipped didn't meet that bar.Area
Platform / cost-control. Directly addresses the maintainer's own standing top concern: an unbounded tenant burning real compute/API spend with nothing stopping it.
Proposal
Wire
evaluateTenantQuota()intojob-dispatch.ts's real admission path: before dispatching a job for a tenant, evaluate its quota and refuse dispatch (with a clear, actionable rejection) when exceeded — mirroring the concurrency dimensioncustomer-loop-view.tscurrently discards, not just compute/time.This does not need to wait on #4920 (the persisted plan/quota/subscription data model, still fully undone) to provide real protection now — scope the quota SOURCE as config/env-injectable defaults for this pass (a real but interim input), so the actual numbers are trivially swappable for #4920's persisted model later without touching the enforcement wiring itself. Don't invent plan-tier business logic here — that's #4920/#4804's job; this issue is the mechanism, not the numbers.
Deliverables
job-dispatch.tscallingevaluateTenantQuota()(all dimensions, including concurrency) before dispatching a tenant's job.Resources
packages/loopover-engine/src/tenant-quota.ts(the existing pure decision function)packages/loopover-engine/src/customer-loop-view.ts(the only current, non-enforcing consumer)src/queue/job-dispatch.ts(the real dispatch path, currently tenant-unaware)Boundaries
No plan-tier business logic, no real pricing/limit numbers invented here — mechanism only, using config/env-injectable interim values. No reward/pricing figures in this issue.
maintainer-only — this is the admission-control path for the whole dispatch queue; a mistake here fails either open (quota does nothing, the exact gap this closes) or closed (blocks legitimate work fleet-wide). First pass needs maintainer review, matching this repo's own treatment of the gate's own merge/close authority.