Skip to content

Commit b60bed3

Browse files
docs(provisioner): retire stale "-1 = unlimited (team/growth)" comment examples (P4-1) (#49)
* docs(provisioner): retire stale "-1 = unlimited (team/growth)" comment examples Bug-hunt iter4 P4-1. The strict-80 margin redesign (merged to common+api master 2026-06-05) made every tier's postgres_connections and redis_memory_mb finite (team=100 conns/1536 MB, growth=20 conns/1024 MB, pro=20 conns/512 MB). No tier passes the -1 "no limit" wire sentinel anymore. The comments still gave the misleading example "-1 = unlimited (team/growth)" / "team: unlimited (-1)". Comments-only for production code: the -1/<=0 sentinel branches stay (still the wire contract; the pool prewarm path still passes -1 deliberately), and the two dedicated-pod sizing literals (redis k8s maxmemoryMB, postgres k8s connLimit) are byte-identical — only their surrounding comments are corrected to note the runtime cap is reconciled to the finite registry value via Regrade. Also fixes one pre-existing FAILING test left behind by the strict-80 common bump: TestRegradeResource_Redis_ProTier_AppliesCap/team asserted maxmemory=0 (old team=-1) while the registry now returns 1536. Reworked to derive expected values from the live plans registry (rule 18) so a future registry change can never silently drift it again. No production behavior change — the server already applied the registry value; only the test expectation was stale. Coverage: Symptom: comment example "-1 = unlimited (team/growth)" + test want 0 Enumeration: rg -n 'unlimited|connLimit.*-1|-1.*unlimited' internal/ Sites found: stale tier-attributed examples across 6 source files + 2 tests Sites touched: all stale tier attributions corrected; pure-sentinel mech docs kept (accurate) with a post-strict-80 note on entry-point docs Coverage test: TestRegradeResource_Redis_ProTier_AppliesCap now registry-driven Live verified: doc-only/test-only — make gate green (build+vet+test ./...) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(server): cover regradeRedis "no cap" sentinel branch (close 100%-patch gate) The comment-only edit in this PR re-touched the diff hunk around regradeRedis's `if memLimitMB < 0 { targetMaxmemoryMB = 0 }` block, pulling that executable branch into the diff. diff-cover's 100%-patch gate (coverage.yml "Patch coverage gate") correctly flagged it uncovered — no production tier resolves a negative redis_memory_mb post the strict-80 margin redesign, so the branch had no test. Add a registry-seam test (SwapRegradeConnLimits + a fixture plans.yaml with redis_memory_mb: -1) that drives the sentinel and asserts maxmemory=0 is targeted (rule 18: registry-derived, not a hand-faked constant). The seam lives in a new export_test.go so the external server_test package can swap the unexported package-level registry. diff-cover now reports internal/server/server.go (100%), Missing: 0 lines. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Manas Srivastava <[email protected]> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 2743a0d commit b60bed3

9 files changed

Lines changed: 196 additions & 54 deletions

File tree

internal/backend/postgres/backend.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,11 @@ func k8sEnvInt(key string, fallback int) int {
3535
// Backend is the interface every Postgres provisioning backend must implement.
3636
type Backend interface {
3737
// Provision creates the database and role for the given token.
38-
// connLimit is the CONNECTION LIMIT to apply to the role (-1 = unlimited,
39-
// 0 = unlimited). The caller (provisioner server) computes this from the
40-
// shared plans.Registry so the provisioner stays a dumb executor.
38+
// connLimit is the CONNECTION LIMIT to apply to the role; <= 0 is the "no cap"
39+
// sentinel (Postgres treats both -1 and 0 as "no limit" here). The caller
40+
// (provisioner server) computes this from the shared plans registry so the
41+
// provisioner stays a dumb executor. Every tier's postgres_connections is finite
42+
// post strict-80 (2026-06-05), so the registry does not pass the sentinel today.
4143
Provision(ctx context.Context, token, tier string, connLimit int) (*Credentials, error)
4244
StorageBytes(ctx context.Context, token, providerResourceID string) (int64, error)
4345
Deprovision(ctx context.Context, token, providerResourceID string) error

internal/backend/postgres/k8s.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,12 @@ func sizingForTier(tier string) tierSizing {
144144
pvcGi: 200,
145145
qCPURequests: "1", qMemRequests: "4Gi",
146146
qCPULimits: "8", qMemLimits: "16Gi",
147-
connLimit: -1, // unlimited; capped only by pod max_connections
147+
// "no cap" sizing default; the enforced cap comes from the registry via
148+
// Regrade (plans.yaml: team postgres_connections=100, growth=20 — both
149+
// finite post strict-80, 2026-06-05). Note sz.connLimit is also what
150+
// Provision's initDatabase uses, so the role is briefly uncapped until the
151+
// reconciler's first Regrade applies the finite registry value.
152+
connLimit: -1,
148153
}
149154
default:
150155
// Unknown tier → conservative hobby-equivalent sizing rather than fail-open.

internal/backend/postgres/local.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,12 @@ func generatePassword(n int) (string, error) {
119119
}
120120

121121
// Provision creates a Postgres database and user for the given token.
122-
// connLimit is the CONNECTION LIMIT to apply to the role; -1 means unlimited
123-
// (omits the clause). This value comes from the API handler via plans.Registry
124-
// so the provisioner stays a dumb executor and the API remains the policy owner.
122+
// connLimit is the CONNECTION LIMIT to apply to the role; <= 0 is the "no cap"
123+
// sentinel (omits the clause; Postgres treats -1 as "no limit"). This value comes
124+
// from the plans registry so the provisioner stays a dumb executor and the policy
125+
// owner sets the cap. Every tier's postgres_connections is finite post the strict-80
126+
// margin redesign (2026-06-05), so the registry does not pass the sentinel today —
127+
// it is retained for the wire contract.
125128
func (b *LocalBackend) Provision(ctx context.Context, token, tier string, connLimit int) (*Credentials, error) {
126129
dbName := dbNamePrefix + token
127130
username := userNamePrefix + token
@@ -374,8 +377,10 @@ func (b *LocalBackend) Deprovision(ctx context.Context, token, providerResourceI
374377
// user on the shared local Postgres cluster. This is called by the entitlement
375378
// reconciler on plan upgrades/downgrades to ensure the role cap matches the new tier.
376379
//
377-
// connLimit <= 0 means unlimited (pass -1 from plans.Registry). Postgres uses -1
378-
// internally for "no limit"; ALTER ROLE with CONNECTION LIMIT -1 removes any cap.
380+
// connLimit <= 0 is the "no cap" sentinel. Postgres uses -1 internally for "no
381+
// limit"; ALTER ROLE with CONNECTION LIMIT -1 removes any cap. The registry returns
382+
// a finite value for every tier post strict-80 (2026-06-05), so this branch is the
383+
// wire contract, not a path any current tier exercises.
379384
func (b *LocalBackend) Regrade(ctx context.Context, token, providerResourceID string, connLimit int) (RegradeResult, error) {
380385
// P0-2: a pool-claimed role is named from the pool token; resolve it from
381386
// provider_resource_id so ALTER ROLE targets the role that actually exists.

internal/backend/redis/backend.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,9 @@ type Backend interface {
4949
// without error, and the reconciler leaves the row for the next sweep.
5050
type Regrader interface {
5151
// Regrade connects to the dedicated Redis pod and adjusts maxmemory to
52-
// match targetMaxmemoryMB. targetMaxmemoryMB <= 0 means unlimited (maxmemory 0).
52+
// match targetMaxmemoryMB. targetMaxmemoryMB <= 0 is the "no cap" sentinel
53+
// (maxmemory 0); every tier's redis_memory_mb is finite post strict-80
54+
// (2026-06-05), so the registry does not pass the sentinel today.
5355
// Returns RegradeResult.Applied=false + SkipReason when the pod is already
5456
// correctly configured (idempotent no-op) or unreachable (soft skip).
5557
Regrade(ctx context.Context, token, providerResourceID string, targetMaxmemoryMB int) (RegradeResult, error)

internal/backend/redis/k8s.go

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,11 @@ const (
7979
// instead of "allkeys-lru" silently evicting the customer's oldest keys.
8080
// Silent eviction also contradicts --appendonly yes (durability). See P1-C.
8181
redisMaxmemoryPolicyCapped = "noeviction"
82-
// redisMaxmemoryPolicyUnlimited is the policy for unlimited tiers (team):
83-
// no cap, so eviction never triggers; "noeviction" is Redis's default.
82+
// redisMaxmemoryPolicyUnlimited is the policy used when a tier resolves to the
83+
// "no cap" sentinel (maxmemoryMB <= 0): no cap, so eviction never triggers;
84+
// "noeviction" is Redis's default. Post the strict-80 margin redesign
85+
// (2026-06-05) every tier's redis_memory_mb is finite, so no current tier uses
86+
// this policy — it is retained for the sentinel contract.
8487
redisMaxmemoryPolicyUnlimited = "noeviction"
8588

8689
// redisK8sOwnerTeamLabel is applied to dedicated customer namespaces.
@@ -137,14 +140,19 @@ func routeKeyTTLForTier(tier string) time.Duration {
137140
// the default user (no ACL), so pod-level is the natural lever. Since each pod is
138141
// dedicated to one customer this is functionally a per-customer cap.
139142
//
140-
// maxmemoryMB is the Redis --maxmemory flag value in MB. A value of -1 means
141-
// unlimited (the flag is omitted entirely). This enforces the per-resource memory
142-
// limit advertised in plans.yaml at the Redis level. The noeviction policy is used
143-
// (see redisMaxmemoryPolicyCapped) so writes fail loudly with an OOM error at the
144-
// cap rather than silently evicting customer data. Values mirror plans.yaml
145-
// redis_memory_mb:
143+
// maxmemoryMB is the Redis --maxmemory flag value in MB. A value of <= 0 is the
144+
// "no cap" sentinel (the flag is omitted entirely). This enforces the per-resource
145+
// memory limit at the Redis level. The noeviction policy is used (see
146+
// redisMaxmemoryPolicyCapped) so writes fail loudly with an OOM error at the cap
147+
// rather than silently evicting customer data. These pod-start values are the
148+
// dedicated-pod sizing defaults; the runtime-enforced cap is reconciled to the
149+
// plans registry by RegradeResource (server.go) on provision and plan change.
146150
//
147-
// anonymous: 5 MB, hobby: 50 MB, pro: 512 MB, growth: 1024 MB, team: unlimited (-1)
151+
// anonymous: 5 MB, hobby: 50 MB, pro: 512 MB, growth: 1024 MB, team: -1 (no flag)
152+
//
153+
// NOTE: the registry's redis_memory_mb is finite for every tier post the strict-80
154+
// margin redesign (2026-06-05; team=1536), so the team -1 below is a pod-start
155+
// default that Regrade overrides — it no longer mirrors plans.yaml.
148156
type tierSizing struct {
149157
cpuReq, memReq string
150158
cpuLim, memLim string
@@ -156,7 +164,9 @@ type tierSizing struct {
156164
// Bounds total simultaneous TCP clients connected to this pod.
157165
maxClients int
158166
// maxmemoryMB is the Redis --maxmemory limit in MB applied at pod start.
159-
// -1 means unlimited (flag omitted). Mirrors plans.yaml redis_memory_mb.
167+
// <= 0 is the "no cap" sentinel (flag omitted). The pod-start value is a sizing
168+
// default; the runtime cap is reconciled to the plans registry by Regrade. The
169+
// registry's redis_memory_mb is finite for every tier post strict-80 (2026-06-05).
160170
// The noeviction maxmemory-policy is applied alongside this limit (P1-C).
161171
maxmemoryMB int
162172
}
@@ -225,15 +235,17 @@ func sizingForTier(tier string) tierSizing {
225235
maxmemoryMB: 1024, // plans.yaml: growth redis_memory_mb = 1024
226236
}
227237
case "team", "team_yearly":
228-
// team_yearly mirrors team (plans.yaml: identical -1 limits, annual billing).
238+
// team_yearly mirrors team (identical limits, annual billing).
229239
return tierSizing{
230240
cpuReq: "500m", memReq: "1Gi",
231241
cpuLim: "4", memLim: "4Gi",
232242
pvcMi: 51200, // 50Gi
233243
qCPURequests: "1", qMemRequests: "2Gi",
234244
qCPULimits: "8", qMemLimits: "8Gi",
235-
maxClients: 1000,
236-
maxmemoryMB: -1, // unlimited — team dedicated pods have no memory cap
245+
maxClients: 1000,
246+
// "no cap" pod-start default; Regrade reconciles to the registry value
247+
// (plans.yaml: team redis_memory_mb = 1536, finite post strict-80).
248+
maxmemoryMB: -1,
237249
}
238250
default:
239251
// Unknown tier → conservative hobby-equivalent sizing.
@@ -624,8 +636,9 @@ type RegradeResult struct {
624636
// - targetMaxmemoryMB > 0 → set maxmemory to that many MB + noeviction policy
625637
// (P1-C: writes fail loudly at the cap, no silent eviction), then CONFIG
626638
// REWRITE so the cap survives a pod restart.
627-
// - targetMaxmemoryMB <= 0 → unlimited tier (team/growth): set maxmemory to 0
628-
// (Redis "no cap") + CONFIG REWRITE so it explicitly overrides any leftover cap.
639+
// - targetMaxmemoryMB <= 0 → "no cap" sentinel: set maxmemory to 0 (Redis "no
640+
// cap") + CONFIG REWRITE so it explicitly overrides any leftover cap. No current
641+
// tier resolves here post strict-80 (2026-06-05) — every redis_memory_mb is finite.
629642
//
630643
// Idempotent: reads CONFIG GET maxmemory first and short-circuits if the value
631644
// already matches, returning Applied=false + SkipReason="already correct".
@@ -851,7 +864,7 @@ func (b *K8sBackend) regradeViaExec(ctx context.Context, ns, token string, targe
851864
return RegradeResult{Applied: false, SkipReason: "exec fallback: CONFIG GET output unparseable"}, nil
852865
}
853866

854-
// Compute target bytes (0 = unlimited for team tier).
867+
// Compute target bytes (targetMaxmemoryMB <= 0 → 0 bytes = Redis "no cap").
855868
var targetBytes int64
856869
if targetMaxmemoryMB > 0 {
857870
targetBytes = int64(targetMaxmemoryMB) * 1024 * 1024
@@ -1104,10 +1117,11 @@ func (b *K8sBackend) applyDeployment(ctx context.Context, ns string, sz tierSizi
11041117
"--dir", "/data",
11051118
"--maxclients", fmt.Sprintf("%d", sz.maxClients),
11061119
}
1107-
// Only add --maxmemory when the tier has a defined cap.
1108-
// -1 means unlimited (team/growth) — omit the flag so Redis
1109-
// uses its default (no cap). This matches plans.yaml semantics
1110-
// where -1 = unlimited.
1120+
// Only add --maxmemory when the sizing has a defined cap.
1121+
// sz.maxmemoryMB <= 0 is the "no cap" sentinel — omit the
1122+
// flag so Redis uses its default (no cap). This is a pod-start
1123+
// default; Regrade later reconciles the cap to the registry
1124+
// value, which is finite for every tier post strict-80.
11111125
if sz.maxmemoryMB > 0 {
11121126
cmd = append(cmd,
11131127
"--maxmemory", fmt.Sprintf("%dmb", sz.maxmemoryMB),

internal/backend/redis/k8s_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ func TestSizingForTier_MaxmemoryMB_MatchesPlansYAML(t *testing.T) {
6666
{"hobby_plus_yearly", 50, true}, // plans.yaml: hobby_plus_yearly mirrors hobby_plus
6767
{"pro", 512, true}, // plans.yaml: pro redis_memory_mb = 512
6868
{"pro_yearly", 512, true},// plans.yaml: pro_yearly mirrors pro
69-
{"team", -1, false}, // unlimited — flag omitted
70-
{"team_yearly", -1, false}, // plans.yaml: team_yearly mirrors team (unlimited)
69+
{"team", -1, false}, // "no cap" pod-start default — flag omitted; Regrade reconciles to registry (team=1536, finite post strict-80)
70+
{"team_yearly", -1, false}, // team_yearly mirrors team's pod-start sizing default
7171
{"growth", 1024, true}, // plans.yaml: growth redis_memory_mb = 1024
7272
{"unknown", 50, true}, // unknown → hobby fallback
7373
}

internal/server/export_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package server
2+
3+
import "instant.dev/common/plans"
4+
5+
// SwapRegradeConnLimits temporarily replaces the package-level plans registry
6+
// used by RegradeResource and returns a restore func. It exists so external
7+
// tests can drive the "no cap" sentinel branch in regradeRedis (a tier whose
8+
// redis_memory_mb resolves to <= 0), which no current production tier triggers
9+
// post the strict-80 margin redesign — the branch is kept for the wire
10+
// contract, so it needs an explicit registry-seam test rather than a live tier.
11+
func SwapRegradeConnLimits(r *plans.Registry) (restore func()) {
12+
prev := regradeConnLimits
13+
regradeConnLimits = r
14+
return func() { regradeConnLimits = prev }
15+
}

internal/server/server.go

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,11 @@ func isDedicatedTier(tier string) bool {
378378

379379
func (s *Server) provisionPostgres(ctx context.Context, req *provisionerv1.ProvisionRequest) (*provisionerv1.ProvisionResponse, error) {
380380
// Resolve the connection limit for this tier from the shared plans registry.
381-
// -1 = unlimited (team/growth); > 0 = enforced cap at the Postgres role level.
381+
// <= 0 is the wire sentinel for "no role-level cap" (Postgres uses -1 for "no
382+
// limit"); > 0 is an enforced cap at the Postgres role level. Post the strict-80
383+
// margin redesign (2026-06-05) every tier's postgres_connections is finite
384+
// (e.g. pro=20, growth=20, team=100), so no tier passes the sentinel today — the
385+
// branch is kept for the wire contract, not because any current tier uses it.
382386
// The provisioner owns the plans registry so the cap stays consistent with
383387
// what RegradeResource applies on plan upgrades.
384388
connLimit := regradeConnLimits.ConnectionsLimit(req.Tier, "postgres")
@@ -1031,8 +1035,10 @@ func (s *Server) regradePostgres(ctx context.Context, req *provisionerv1.Regrade
10311035
// redisBackend does not implement redis.Regrader) return {applied:false} gracefully so
10321036
// the shared redis-provision pod is never accidentally capped.
10331037
//
1034-
// Team/growth tiers (memory limit = -1 in plans.yaml) set maxmemory=0 (Redis "unlimited")
1035-
// so existing pods are never accidentally capped.
1038+
// A tier whose redis_memory_mb resolves to <= 0 sets maxmemory=0 (Redis "no cap").
1039+
// Post the strict-80 margin redesign (2026-06-05) every tier's redis_memory_mb is
1040+
// finite in plans.yaml (e.g. pro=512, growth=1024, team=1536), so no tier hits this
1041+
// branch today — it remains for the sentinel contract, not for any current tier.
10361042
func (s *Server) regradeRedis(ctx context.Context, req *provisionerv1.RegradeRequest) (*provisionerv1.RegradeResponse, error) {
10371043
// Resolve the effective provider ID (k8s namespace).
10381044
// Case 1: prid is already "instant-customer-<something>" → use as-is.
@@ -1095,12 +1101,16 @@ func (s *Server) regradeRedis(ctx context.Context, req *provisionerv1.RegradeReq
10951101

10961102
// Memory cap comes from the shared plans registry.
10971103
// StorageLimitMB("redis") returns plans.yaml redis_memory_mb:
1098-
// anonymous=5, hobby=50, pro=512, team/growth=-1 (unlimited).
1099-
// -1 (unlimited) → targetMaxmemoryMB=0 → Regrade sets maxmemory=0 (no cap).
1104+
// anonymous=5, hobby=50, pro=512, growth=1024, team=1536.
1105+
// All current tiers are finite post the strict-80 margin redesign (2026-06-05).
1106+
// The < 0 branch below is the wire sentinel for "no cap" (-> targetMaxmemoryMB=0
1107+
// -> Regrade sets maxmemory=0); no current tier passes it, but it is kept so a
1108+
// future unlimited tier would behave correctly.
11001109
memLimitMB := regradeConnLimits.StorageLimitMB(req.Tier, "redis")
11011110
targetMaxmemoryMB := memLimitMB
11021111
if memLimitMB < 0 {
1103-
// Unlimited tier — explicitly clear any cap on the pod.
1112+
// "No cap" sentinel (no current tier passes it post strict-80) — explicitly
1113+
// clear any cap on the pod.
11041114
targetMaxmemoryMB = 0
11051115
}
11061116

0 commit comments

Comments
 (0)