---
id: w1/m14/t001
title: `KeyValue` CRD type + Valkey tier catalog in `lego/types`
worker: worker1
status: done
estimate: 30m
depends_on: []
---

## Objective

Define a `KeyValue` CRD type in the `lego/types` module — the leaf CRD contract — modelled on `Database` (`app.bex.co/v1alpha1`): a spec (name, plan/tier, optional `public`) and a status (phase, host). Add a Valkey tier catalog entry to `lego/types/tiers` mirroring the Postgres tier family, then regenerate deepcopy + the CRD manifest via codegen. This is the type-level foundation the reconciler (t002) projects into runtime objects.

## Context

- `lego/types/v1alpha1/database_types.go` — the analog to copy: `DatabaseSpec` (name, plan, version, storage, `public`) + `DatabaseStatus` (phase, host). New file: `lego/types/v1alpha1/keyvalue_types.go`.
- `lego/types/v1alpha1/groupversion_info.go` — register the new kind in the `app.bex.co/v1alpha1` scheme (`SchemeBuilder.Register`).
- `lego/types/tiers/tiers.yaml` (+ `tiers.go`, `tiers_test.go`) — the `postgres` tier family; add a `valkey` (or `keyvalue`) family with single-instance tiers differing by compute (resources.requests==limits) and storage, mirroring the free / basic-256mb / basic-1gb Postgres shape.
- Codegen runs from `lego/operator/` (`make manifests generate`): controller-gen reads CRD/deepcopy markers from `../types/...`; deepcopy lands in `lego/types/v1alpha1/zz_generated.deepcopy.go`, the CRD YAML in `lego/operator/config/crd/bases/`. Both are generated — never hand-edit. See `lego/operator/CLAUDE.md`.
- The types module is a leaf: it imports nothing from `operator/` or `backend/`.

## Steps

1. Add `lego/types/v1alpha1/keyvalue_types.go` with `KeyValue`, `KeyValueList`, `KeyValueSpec` (name, plan/tier, optional `public`), `KeyValueStatus` (phase, host) and the kubebuilder object/status/printcolumn markers, mirroring `database_types.go`.
2. Register the new kind in `groupversion_info.go` (`SchemeBuilder.Register(&KeyValue{}, &KeyValueList{})`).
3. Add a Valkey tier family to `lego/types/tiers/tiers.yaml` (single-instance tiers, compute + storage per tier) and extend `tiers.go`/`tiers_test.go` to cover it.
4. From `lego/operator/`, run `make manifests generate`; keep the generated deepcopy and CRD YAML.

## Files

- `lego/types/v1alpha1/keyvalue_types.go` (new)
- `lego/types/v1alpha1/groupversion_info.go`
- `lego/types/v1alpha1/zz_generated.deepcopy.go` (generated)
- `lego/types/tiers/tiers.yaml`, `lego/types/tiers/tiers.go`, `lego/types/tiers/tiers_test.go`
- `lego/operator/config/crd/bases/` (generated CRD YAML)

## Acceptance criteria

- [ ] `KeyValue` type exists in `lego/types/v1alpha1`, registered in the `app.bex.co/v1alpha1` scheme, with spec (name, plan/tier, optional `public`) + status (phase, host).
- [ ] A Valkey tier family exists in `lego/types/tiers` with single-instance tiers mirroring the Postgres tiers; `go test ./tiers/...` passes.
- [ ] `make manifests generate` produces the deepcopy and the `KeyValue` CRD manifest with no dirty hand-edits; `cd lego/types && go build ./...` is clean.

## Out of scope

- The reconciler that acts on the type (t002).
- Any REST/GraphQL/MCP/dashboard surface for `KeyValue` (deferred to w2/w5).
