---
id: w8/m2/t001
title: Core Usage verb — month-to-date per workspace, OpenFGA-authorized
worker: worker8
status: todo
estimate: 45m
depends_on: []
---

## Objective

Add the single core verb the three adapters will share: `Usage(ctx, workspaceID, period)` in the `internal/usage` feature package — month-to-date quantities per service and kind (tier-split for `instance_seconds`), reading m1's store aggregation (w8/m1/t005), authorized as a workspace-scoped read via OpenFGA.

## Context

- Pattern: one core service, thin adapters (docs/ADR006-bex-api.md); neighboring feature packages (`internal/apps`, `internal/postgres`, `internal/secrets`) show the shape.
- Authz: viewer-level check on `workspace:tea-<id>` via `internal/authz` (allow-all when `BEX_OPENFGA_URL` unset, like every other verb).
- Return a typed result (service id, kind, tier, quantity) — adapters format; the core stays unit-agnostic (seconds/bytes as stored).
- Store unavailable (`BEX_CP_DB_URI` unset) ⇒ the verb reports unavailable the same way env-vars verbs 503 without OpenBao — consistent error shape, no panic.

## Steps

1. Define the result types + `Usage` method on the usage service (extend the m1 service struct — same package, read side).
2. Wire the OpenFGA check and the store dependency.
3. Unit-test against the store fake: happy path, denied caller, store-unset unavailable.

## Files

- `lego/backend/internal/usage/service.go` (+ tests)

## Acceptance criteria

- [ ] Verb returns the fake-store aggregate for an authorized caller; denial and unavailable paths return the shared error shapes.

## Out of scope

- Adapters (t002–t004); any new id kinds (none needed).
