docs(fase-13): history doc + deployment server admin runbook + CLAUDE.md status

Wraps Fase 13:
  - docs/history/fase-13-server-admin.md captures the full fase
    breakdown (model, RPCs, UI, store wiring, tests, verification,
    deviations from plan, scope out).
  - docs/deployment.md gains a Server administration section with the
    SERVER_ADMIN_EMAIL bootstrap recipe + the "first prod boot has no
    users yet" gotcha + manual re-run command (with the </dev/null
    redirect for the heredoc-stdin gotcha) + last-admin guard note +
    audit log query snippet.
  - CLAUDE.md "Project Status" updated: MVP2 now 5/6, Fase 13 , test
    totals, the one pre-existing SV-02 flake noted.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-18 06:26:51 +02:00
parent 27afda74f1
commit 0485ccc75e
3 changed files with 202 additions and 1 deletions

View File

@@ -60,6 +60,49 @@ COMMIT;
```
CASCADE also empties `auth.mfa_factors`, `auth.mfa_amr_claims`, `auth.mfa_challenges`, `auth.one_time_tokens` — expected. Never touch `auth.schema_migrations` or `public._applied_migrations` (they track what DDL has been applied).
## Server administration (Fase 13)
The instance has a global `server_admin` role separate from the per-collective `admin` role. Server admins can list/soft-delete/restore/hard-delete any collective, remove members, set server-level section visibility defaults, and read the append-only audit log at `/admin/audit`.
### Promoting the first admin
Set `SERVER_ADMIN_EMAIL` in `.env` to the operator's email BEFORE first deploy. `infra/db-init/10-server-admin-seed.sh` runs once on first volume init and inserts the matching user into `public.server_admins`.
**Gotcha**: `docker-entrypoint-initdb.d` fires before any user has signed in via Keycloak, so `public.users` is empty and the script will log `WARNING: no public.users row matches email <...>, skipping`. To actually bootstrap:
1. Bring up the stack normally (`docker compose --env-file .env -f infra/docker-compose.erosi.yml up -d`).
2. Sign in once via Keycloak using `SERVER_ADMIN_EMAIL` to populate `public.users`.
3. Re-run the bootstrap script manually:
```bash
docker compose -f infra/docker-compose.erosi.yml exec -T db \
bash /docker-entrypoint-initdb.d/10-server-admin-seed.sh </dev/null
```
The `</dev/null` redirect is mandatory per the heredoc-stdin gotcha (the surrounding bash heredoc on the SSH side would otherwise be drained).
After this `server_admins` is non-empty and the script is a no-op forever.
### Promoting subsequent admins
Use `/admin/admins` → "Promote user" with the target's email. Or directly in SQL:
```sql
INSERT INTO public.server_admins (user_id, granted_by)
SELECT id, '<your-user-id>'::uuid FROM public.users WHERE email = '<target>'
ON CONFLICT (user_id) DO NOTHING;
```
### Last-admin guard
`revoke_server_admin()` refuses to remove the only remaining admin (`P0003 'last_admin'`). If you really need to demote the last admin (e.g. handover), promote the replacement FIRST.
### Audit log
Every privileged RPC writes to `public.admin_actions`. Read it from `/admin/audit` or with:
```sql
SELECT created_at, actor_id, action, target_type, target_id, payload
FROM public.admin_actions ORDER BY created_at DESC LIMIT 100;
```
The log is append-only via RLS — there are no INSERT/UPDATE/DELETE policies, only the SECURITY DEFINER RPCs can write. A `postgres` superuser shell can still delete rows; the threat model is "malicious admin via the product UI", not "operator with DB access".
## Not yet configured on ambrosio
- SMTP (Resend)