---
id: w6/m1/t002
title: "Store schema: plan on tenants + lifecycle columns; migration + plan-limits constants"
worker: worker6
status: done
estimate: 30m
depends_on: [w1/m9]
---

## Objective

Grow the control-plane store so a tenant row can carry Render's new plan model: a `plan` column (`hobby | pro | scale | enterprise`, default `hobby`) plus whatever lifecycle metadata create/rename/delete need (display name distinct from slug if not already, created/updated timestamps). Define the plan-limit constants in one place: 5 Hobby workspaces per user, 25 services per Hobby workspace, 1 member on Hobby, unlimited on paid.

## Context

- Control-plane store: `lego/backend/internal/store/` (migrations run when `BEX_CP_DB_URI` is set — docs/ADR003-control-plane.md).
- w1/m9 t001 mints `tenants` + `tenant_members` rows on first login; this task only extends the schema, never forks the write path.
- Plan lineup is the post-2026-04-23 flat-rate set (RESEARCH-workspaces.md finding 1) — do **not** model legacy Professional/Organization.
- Workspace **plans** ≠ w1/m8 **instance types** (per-service compute tiers); keep the catalogs separate.

## Steps

1. Add a migration: `plan` column with CHECK constraint + default `hobby`; any missing name/timestamp columns.
2. Add plan-limit constants + a `PlanLimits(plan)` lookup in the store or core package.
3. Extend store methods (get/list tenants) to surface `plan`.

## Files

- `lego/backend/internal/store/` (migration + queries)
- `lego/backend/internal/core/` (limits lookup, if it lives there)

## Acceptance criteria

- [ ] Migration applies cleanly on an existing m9-era database (existing rows default to `hobby`)
- [ ] `PlanLimits` returns Render's verified numbers (5/25/1 for Hobby; unlimited members paid)
- [ ] `go build ./... && go test ./...` green in `lego/backend/`

## Out of scope

- Enforcing the limits (t003/t005).
- Billing/subscription fields.
