---
id: w1/m8/t001
title: "Tier catalog: lego/types/tiers package — embedded YAML + loader + validation (no prices)"
worker: worker1
status: done
estimate: 40m
depends_on: []
---

## Objective

Create the single source of truth for the instance ladder: a hand-maintained `tiers.yaml` embedded in a new `tiers` package of the contract module (`lego/types`), exposed as a parsed catalog both the operator and backend import.

## Context

- `lego/types` is the module both sides may import (`operator → types ← backend`). "Leaf; imports nothing" in CLAUDE.md means no bex modules — it already depends on apimachinery, so parse the YAML with `k8s.io/apimachinery/pkg/util/yaml` and validate quantities with `k8s.io/apimachinery/pkg/api/resource`: **zero new module dependencies**.
- Put it in a sibling package (`lego/types/tiers/`), not `v1alpha1` — the CRD package stays codegen-only (controller-gen reads it; don't make it embed files).
- Ladder mirrors Render verbatim (render.com/docs/compute-plans, verified 2026-07-08) and must byte-match the existing copies it will replace: operator `tierResources` (`app_controller.go:158`) incl. `free: 100m/512Mi`, and the store list (`store/api.go:322`).
- Schema per entry: `id` (hyphenated, the `spec.tier` value), `renderPlan` (Render REST `plan` spelling — verify underscore forms like `pro_plus` against Render's OpenAPI during implementation), `cpu`, `memory` (k8s `resource.Quantity` strings, requests==limits). **No price field** — pricing is Metronome's per the recorded decision (`store/api.go:327`). Leave room for future flags (e.g. `idleSuspend`) without adding them.
- Accessors: lookup by `id`, lookup by `renderPlan`, list of valid ids (for error messages), and the default id (`free` — the store's existing empty-tier default, `normalizeTier`). A malformed catalog must fail at init/test time, never at reconcile time.
- New Go files carry the Apache-2.0 header (`lego/operator/hack/boilerplate.go.txt`).

## Steps

1. Write `lego/types/tiers/tiers.yaml` with the seven Render tiers (comment pointing at Render's docs).
2. Add `go:embed` + parse-once loader + accessors in `lego/types/tiers`.
3. Validation test in the package: ids and renderPlans unique + non-empty, quantities parse, default id present — the CI drift-guard.

## Files

- `lego/types/tiers/tiers.yaml`, `lego/types/tiers/tiers.go`, `lego/types/tiers/tiers_test.go` (new)

## Acceptance criteria

- [ ] `go test ./...` in `lego/types` fails on malformed YAML, duplicate ids/renderPlans, or an unparseable quantity.
- [ ] Catalog values byte-match the operator's `tierResources` map (which t002 deletes).
- [ ] `go.mod` of `lego/types` gains no new module requirements.

## Out of scope

- Consumers (t002/t003); prices; runtime (ConfigMap) loading.
