From 2b6a6abcbbb54838b6abd52e40502afeda33eb5c Mon Sep 17 00:00:00 2001 From: Manas Srivastava Date: Thu, 14 May 2026 15:38:59 +0530 Subject: [PATCH] plans: add rpo_minutes / rto_minutes per-tier (FIX-H #Q50) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds two Limits fields surfaced on GET /api/v1/capabilities so an agent can reason about a tier's durability promises before provisioning. Pairs with the FIX-H api/worker backup-integrity work: the api handler reads RPOMinutes/RTOMinutes via the new Registry methods. Anonymous/free return 0 ("not promised") because those tiers don't take scheduled backups; hobby/hobby_plus = 1440/30, pro/team = 60/15. No yaml updates here — plans.yaml lives in the api repo and FIX-H ships the values there in the same wave. Co-Authored-By: Claude Opus 4.7 (1M context) --- plans/plans.go | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/plans/plans.go b/plans/plans.go index 0e85969..b1bc558 100644 --- a/plans/plans.go +++ b/plans/plans.go @@ -82,6 +82,21 @@ type Limits struct { // manual backups are not allowed. ManualBackupsPerDay int `yaml:"manual_backups_per_day"` + // RPOMinutes — Recovery Point Objective. The maximum window of + // data loss a tier accepts between the last completed backup and + // a restore event. Surfaced on GET /api/v1/capabilities so an + // agent can reason about whether a tier meets a workload's + // durability requirements before provisioning. 0 means "RPO not + // promised" (no scheduled backups on this tier). FIX-H #Q50 (B36). + RPOMinutes int `yaml:"rpo_minutes"` + + // RTOMinutes — Recovery Time Objective. The target wall-clock + // duration between "operator presses restore" and "data is back + // online" for a tier. Includes the worker tick + pg_restore + + // post-restore verification. 0 means "RTO not promised" (no + // self-serve restore available on this tier). FIX-H #Q50 (B36). + RTOMinutes int `yaml:"rto_minutes"` + // VectorStorageMB is the maximum storage per pgvector-enabled Postgres // database in megabytes. Mirrors PostgresStorageMB because pgvector // runs on the same underlying Postgres backend. @@ -474,6 +489,29 @@ func (r *Registry) ManualBackupsPerDay(tier string) int { return p.Limits.ManualBackupsPerDay } +// RPOMinutes returns the per-tier Recovery Point Objective in minutes. +// 0 = "not promised" (the tier doesn't take scheduled backups, so no +// RPO is guaranteed). Surfaced on GET /api/v1/capabilities. FIX-H #Q50. +func (r *Registry) RPOMinutes(tier string) int { + p := r.Get(tier) + if p == nil { + return 0 + } + return p.Limits.RPOMinutes +} + +// RTOMinutes returns the per-tier Recovery Time Objective in minutes. +// 0 = "not promised" (the tier doesn't have self-serve restore, so +// the time-to-restore is operator-driven and unbounded). Surfaced on +// GET /api/v1/capabilities. FIX-H #Q50. +func (r *Registry) RTOMinutes(tier string) int { + p := r.Get(tier) + if p == nil { + return 0 + } + return p.Limits.RTOMinutes +} + // Default returns a Registry built from hardcoded defaults. // Used in tests and when plans.yaml is not present (development convenience). func Default() *Registry {