A quick update on recent work in apps/demo.

Restructuring

The api_foo.go / impl_foo.go split got pushed further — implementation files now live in api/api_impl, a separate package from the type and route definitions. The API contract is easier to browse without the implementation in the way.

Code Generator

api/api_gen parses the API package using go/packages and generates:

  • API.md — endpoint reference
  • client.go — typed Go client
  • client.js / client.d.ts — JS client with TypeScript types
  • openapi.json — OpenAPI document

Generated files get a header comment marking them as such. The OpenAPI output is the most useful immediately — it's easy to throw at external tooling for testing.

HTTP Request Logger

A small middleware now logs method, path, status code, and duration for every request. /health is excluded. Nothing clever, just useful.

Audit Log

An append-only audit_log table records who did what to which resource. Entries are written in the same transaction as the mutation they describe — the log and the data can never disagree. There's no foreign key to tenants, so a Tenants_Delete entry survives the tenant.

Append has no capability check. It's a side effect; the caller can't opt out.

Covered: Tenants, TenantMembers, TenantInvitations, Grants, Roles, Groups, Bots, BotKeys, Domains.

Identity and account events (login, registration, password reset) are also recorded, but they're user-scoped rather than tenant-scoped. These entries have a Subject field — the user the event concerns — separate from Actor. On a token-based password reset, Subject is the account owner, Actor is empty. Failed logins get their own transaction since the login transaction rolls back. Unknown emails aren't recorded — no account to attach to, and recording them would let an attacker probe for accounts.

Audit_ListByUser lets users read their own trail. Reading anyone else's requires system-scoped Audit.Read.


Check out the demo app code at https://atlas9.dev/src/apps/demo/