---
id: w3/m7/t001
title: Event model + store view — compose deploys + audit_events + scale/sleep transitions
worker: worker3
status: todo
estimate: 35m
depends_on: []
---

## Objective

Define the event vocabulary and build the read view. Sources already in the store: the deploys table (w2/m5 — deploy started/live/failed/canceled transitions) and `audit_events` (w4/m10 — every Core write with caller identity: suspend/resume/restart/scale/plan/env changes). Decide per event type whether it's **derived** (read straight from those tables — preferred, zero new writes) or **recorded** (needs a new write — e.g. operator-driven autoscale/sleep transitions that no Core verb sees; if any type needs recording, add the lightest possible write and document it; if it's not cheaply recordable, omit the type honestly rather than faking it). Map to Render's event-type names where its OpenAPI defines them; bex-named otherwise.

## Context

- `lego/backend/internal/deploys/` + `internal/audit/` + `internal/store/` — the view composes these; a new `internal/events/` feature package holds it (one service, thin adapters — the house pattern).
- Cursor requirement: stable ordering across two heterogeneous sources needs a total order (timestamp + source + id tiebreak) that pagination can resume from.
- Redaction is structural: audit events already exclude values (m10 DoD) — the view must not re-join anything that could reintroduce them.

## Steps

1. Enumerate Render's event types from the OpenAPI/docs; map each to a bex source or an honest omission (table in the package doc comment).
2. Implement the composed, cursor-paged store view (SQL union/view or two-source merge — pick, justify).
3. Decide the operator-transition question (autoscale/sleep) — derived from existing data, tiny new write, or omitted — and record why.

## Files

- `lego/backend/internal/events/` (new feature package)
- `lego/backend/internal/store/` (view/query; migration only if a recorded type is chosen)

## Acceptance criteria

- [ ] Every emitted event type has a documented source (derived/recorded) or the type is listed as omitted with a reason; the cursor is stable under concurrent inserts.

## Out of scope

- Adapters (t002/t003); dashboard (w5/007); workspace-level (non-service) event feeds.
