Skip to content

feat(plans): flag-gated per-service resource-count caps (Task #55)#263

Merged
mastermanas805 merged 2 commits into
masterfrom
feat/resource-count-caps
Jun 5, 2026
Merged

feat(plans): flag-gated per-service resource-count caps (Task #55)#263
mastermanas805 merged 2 commits into
masterfrom
feat/resource-count-caps

Conversation

@mastermanas805
Copy link
Copy Markdown
Member

What

Closes the biggest remaining strict-≥80%-margin hole: today only queue_count is capped, so a tenant can create MANY postgres/vector/redis/mongodb/storage resources each at the per-resource size cap and blow the saturated-COGS bound (Redis the binding constraint at $6.50/GB-mo). This adds a per-tier active-resource COUNT cap per service, enforced like the existing queue_count A6 block — but flag-gated, default OFF, so it cannot surprise-break existing heavy tenants with a 402.

Depends on common#47 (merged) for the ResourceCountLimit accessors.

Flag — default OFF, inert when off

RESOURCE_COUNT_CAPS_ENABLED (config.go, default false). When off, enforceResourceCountCap returns immediately and runs no count query — zero behavior change. Proven inert by TestResourceCountCap_FlagOffIsInert (HTTP) + TestEnforceResourceCountCap_FlagOffInert_Whitebox. Operator enables after a usage audit.

Enforcement sites (mirror queue.go A6: 402 + agent_action + metric)

  • db.go (postgres), vector.go, cache.go (redis), nosql.go (mongodb), storage.go
  • Shared helper internal/handlers/resource_count_cap.go — one call site per handler, not a copy-pasted block. Fails closed on a count-query error when enabled (cheap indexed COUNT; must not silently bypass a cost cap).

Per-tier counts (COGS-derived; redis the binding line)

tier pg vector redis mongodb storage
anon/free 1 1 1 1 1
hobby 2 2 2 2 2
hobby_plus 3 3 3 3 3
pro 5 5 3 5 5
growth 6 6 3 6 6
team 5 8 4 6 6

Invariant: count × per-resource-size-cap × unit-COGS ≤ tier 20%-of-price budget per service. api/plans.yaml mirrors common defaultYAML.

Surfaces (rule 22)

  • /api/v1/capabilitiesresource_count_limit map (registry-iterating, auto-surfaces new tiers/services).
  • /api/v1/billing/usagecount + count_limit on storage services.
  • openapi.go schemas for both.
  • content/llms.txt + instanode-web public/llms.txt — separate PRs.

Metric (rule 25)

instant_resource_count_limit_blocked_total{service,team_tier}. Alert + Prom rule + dashboard tile + catalog row in infra PR (resource-count-caps-observability).

Tests

  • TestResourceCountCap_FlagOnAtLimitRejects — rule-18 registry-iterating guard: every count-capped service 402s at cap when on (service N+1 can't ship uncapped).
  • Flag-off-inert, under-limit-passes, whitebox edge branches (unlimited / count-error / nil cfg) → enforceResourceCountCap 100%.
  • Capabilities surface guard, config flag test, strict_margin guard extended to the new *_count fields.
  • make gate green. The one unrelated failure (models.TestLinkGitHubID) is pre-existing — reproduces with this change stashed and touches no file in this diff.

Ship note

Flag is OFF in prod → enforcement inert until the operator runs kubectl set env -n instant deploy/instant-api RESOURCE_COUNT_CAPS_ENABLED=true after a usage audit. Awaiting operator enable of RESOURCE_COUNT_CAPS_ENABLED.

🤖 Generated with Claude Code

Closes the strict-≥80%-margin hole where only queue_count was capped: a tenant
could create MANY postgres/vector/redis/mongodb/storage resources each at the
per-resource size cap and blow the saturated-COGS bound (Redis binding at
$6.50/GB). Adds a per-tier active-resource COUNT cap per service, enforced like
the existing queue_count A6 block — but FLAG-GATED, default OFF.

Flag: RESOURCE_COUNT_CAPS_ENABLED (config.go, default false). When off, the new
enforceResourceCountCap helper returns immediately and runs NO count query —
zero behavior change, proven inert by TestResourceCountCap_FlagOffIsInert +
TestEnforceResourceCountCap_FlagOffInert_Whitebox. Operator enables after a usage
audit so no current tenant is retroactively over a cap.

Enforcement sites (mirror queue.go's A6 block; 402 + agent_action + metric):
  db.go (postgres), vector.go, cache.go (redis), nosql.go (mongodb), storage.go.
Shared helper: internal/handlers/resource_count_cap.go (one call site per
handler, not a copy-pasted block). Count cap fails CLOSED on a count-query error
when enabled (a cheap indexed COUNT; must not silently bypass a cost cap).

Per-tier numbers (api/plans.yaml; mirrors common defaultYAML, depends on
common#47 which is merged): anon/free=1 each; hobby=2; hobby_plus=3;
pro pg/vec/mongo/storage=5 redis=3; growth=6 redis=3; team pg=5 vec=8 redis=4
mongo=6 storage=6. redis_count is the most conservative line everywhere (binding
COGS). Derived so count×size×unit-COGS ≤ tier 20%-of-price budget per service.

Surfaces (rule 22): /api/v1/capabilities resource_count_limit map;
/api/v1/billing/usage count+count_limit on storage services; openapi.go schemas;
content/llms.txt + instanode-web public/llms.txt (separate PRs).

Metric (rule 25): instant_resource_count_limit_blocked_total{service,team_tier}
(metrics.go). Alert + Prom rule + dashboard tile + catalog row in infra PR.

Tests: registry-iterating flag-on guard (rule 18,
TestResourceCountCap_FlagOnAtLimitRejects) so service N+1 can't ship uncapped;
under-limit pass; whitebox edge branches (unlimited, count-error, nil cfg) →
enforceResourceCountCap 100%; capabilities surface guard; config flag test;
strict_margin guard extended to the new *_count fields. make gate green (the one
unrelated pre-existing failure, models.TestLinkGitHubID, reproduces with this
change stashed and touches no files in this diff).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mastermanas805 mastermanas805 enabled auto-merge (squash) June 5, 2026 18:36
…limit (Task #55)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mastermanas805 mastermanas805 merged commit 9a6fc90 into master Jun 5, 2026
19 checks passed
mastermanas805 added a commit that referenced this pull request Jun 5, 2026
# Conflicts:
#	internal/config/config.go
#	internal/config/config_test.go
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.

1 participant