---
id: w2/m7/t004
title: Suspend/resume — CR field + operator scale-to-zero + surface verbs
worker: worker2
status: done
estimate: 45m
depends_on: [w2/m7/t003]
---

## Objective

Complete Render's KV lifecycle: add `spec.suspended` to the `KeyValue` CRD (mirroring `App`'s suspend semantics), teach `KeyValueReconciler` to scale the Valkey StatefulSet to zero replicas when suspended (and back on resume, data preserved on the PVC), and expose `POST /v1/key-value/{idOrName}/suspend` + `/resume` returning Render's captured response.

## Context

- `lego/types/v1alpha1/keyvalue_types.go` + `lego/operator/internal/controller/keyvalue_controller.go` (w1/m14); `App`'s `spec.suspended` handling in `app_controller.go` is the pattern.
- Codegen after the CRD edit: `make manifests generate` from `lego/operator/` — **and the CRD is already listed in `config/crd/kustomization.yaml`** (registered by ddc412e; nothing to add, just don't remove it).
- Status phase while suspended per capture (Render reports a suspended status string) — surface it through the core's status mapping.

## Steps

1. Add `Suspended *bool`/`suspended` to the CR spec; regenerate deepcopy/CRD.
2. Reconciler: replicas 0 when suspended, restore plan replicas on resume; phase reflects it; envtest case.
3. Core + REST verbs (`Suspend`/`Resume` methods, two routes); response per t001 capture.

## Files

- `lego/types/v1alpha1/keyvalue_types.go` (+ generated deepcopy/CRD)
- `lego/operator/internal/controller/{keyvalue_controller.go,keyvalue_test.go}`
- `lego/backend/internal/keyvalue/{service.go,rest.go}`

## Acceptance criteria

- [ ] Suspend drops the Valkey pod (STS 0/0), PVC intact; resume restores service with the same password and data.
- [ ] Phase/status reflects suspension across REST reads; both verbs return Render-shaped responses.
- [ ] `make test` (operator) and backend tests green.

## Out of scope

- Auto-sleep/idle hibernation for KV (App-style activator) — not a Render KV feature.
