AGENTS.md@internal/service · git:20261020.8d47d7a · 2026-10-20 · sha256 1f1e5e461ed5e4f0

AGENTS.md@internal/service git:20261020.8d47d7aA

Immutable. This exact content never changes and is served at /api/v1/blob/1f1e5e461ed5e4f0.

# Agent Instructions — `internal/service/`

The rules in [`internal/AGENTS.md`](../AGENTS.md) apply here too.

## Events stay in sync with mutations

Service mutate paths (create, update, delete, state flips, factor and authz
writes) own Path B emitters: `audit.Emit` in the same transaction as the
write. Adding, changing, or removing a mutation **must** keep events current
— including **adding** a type when a new semantic mutation appears, and
**removing** a type that no longer has a producer, unless rows were ever
stored under it, in which case it moves to the catalog's Retired table.

A change is incomplete if it:

- adds a mutate without an emit,
- changes a payload so it no longer matches the catalog, or
- removes a mutate but leaves the type in the live Path B table. A type
  that has stored rows moves to Retired and keeps its `EventType` constant
  and OpenAPI member so those rows still decode, until its rows have aged
  out of retention, when the type is removed.

Authority (do not duplicate payload rules or the catalog table here):

- Catalog: [`docs/design/api/events-catalog.md`](../../docs/design/api/events-catalog.md)
  (payload rules, live Path B table, Retired table, deferred list). Context:
  [ADR 048](../../docs/adrs/048-wide-events-internal-audit-primitive.md),
  [ADR 049](../../docs/adrs/049-events-api-retention-export.md).
- Types: [`internal/domain/event.go`](../domain/event.go) `EventType`
  constants.
- Payloads: [`internal/domain/event_payload.go`](../domain/event_payload.go).
- OpenAPI: payload YAML under `api/openapi/endpoints/events/payloads/`, the
  type's entries in `payloadByEventType` and `webhookByEventType`
  ([`api/cmd/gen_event_schemas/main.go`](../../api/cmd/gen_event_schemas/main.go)),
  and its webhook in the root `webhooks:` list of
  [`api/openapi/openapi-spec.yaml`](../../api/openapi/openapi-spec.yaml),
  which the generator checks. Regenerate with `moon run server:generate`.
- Producer allowlist:
  [`internal/audit/catalog_producers_test.go`](../audit/catalog_producers_test.go)
  — live catalog types must have a producer; do not list deferred types as
  live.