---
id: w4/m10/t003
title: Read surface — REST in Render's audit-logs shape + GraphQL query, admin-scoped
worker: worker4
status: done
estimate: 30m
depends_on: [w4/m10/t002]
---

## Objective

Expose the audit trail to workspace admins **in Render's audit-logs shape**: an owner/workspace-scoped REST path modeled on Render's `GET /owners/{ownerId}/audit-logs` (Render field vocabulary, cursor paging, newest-first, filterable by resource/time) and a matching GraphQL query named consistently with that shape (`auditLogs`) — gated on the OpenFGA manage scope (`can_manage`), 403 for everyone else, 503 store-less. Do **not** invent a bex-own `GET /v1/audit-events`.

## Context

- _2026-07-11 correction:_ this task originally claimed "Render's REST spec has no audit endpoint (dashboard-only)" — false. The parity ledger (verified 2026-07-09, after this task was authored) records Render's `GET /owners/{id}/audit-logs` and `/organizations/{id}/audit-logs` (docs/ADR018-render-parity.md, "Audit logs" row, mapped to w4/m10), and this milestone's own t007 compares against that shape. Adopt Render's surface so t007 can flip the ledger row toward ✅ instead of documenting an avoidable divergence.
- Follow the feature-package layout: this likely becomes a small `internal/audit` feature package with `service.go` + `rest.go` + `graphql.go` fragments composed in `server.go`.
- Scope reads to the caller's workspace (tenant column from t002) so w1/m9 enforcement applies automatically; the `{ownerId}` path segment must match the caller's workspace (Render's owner ≈ bex's workspace `tea-…` id).

## Steps

1. `audit` feature package: `List(ctx, filter)` verb over the store.
2. REST fragment on Render's audit-logs path/vocabulary/cursor paging + GraphQL fragment; wire into `server.go`.
3. Update `docs/ADR006-bex-api.md` (new section) and `docs/ADR012-auth.md` consequences.

## Files

- `lego/backend/internal/audit/` (new feature package)
- `lego/backend/internal/api/server.go`
- `docs/ADR006-bex-api.md`, `docs/ADR012-auth.md`

## Acceptance criteria

- [x] Admin caller lists events newest-first with Render-shaped paging/filters honored; non-admin gets 403; store-less gets 503.
- [x] Both surfaces delegate to the one `List` verb.
- [x] The REST path and field vocabulary match Render's audit-logs endpoint per docs/ADR018-render-parity.md; any residual divergence documented for t007.

## Out of scope

- Dashboard Audit Log page (a later w5 note once this API exists); MCP exposure (not in Render's official server).
