@@ 1,6 1,6 @@
# lethe-server
-**Status:** Execute (verify pending)
+**Status:** Verified
**Module:** `sourcecraft.dev/bigbes/lethe`
**Branch:** master
**Worktree:** none
@@ 396,6 396,66 @@ Greenfield — no compat surface. Wire format is the only forward-compat concern
- Per-route per-user rate limits (would need `Authenticator` to expose a per-identity bucket).
- Pluggable auth backends beyond Authelia (the OIDC verifier is generic enough that pointing at a different IdP works, but only Authelia is documented).
+## Verify
+
+Date: 2026-04-26. Run against `master` HEAD; binary built fresh from `cmd/lethe`; e2e smoke driven through a real listener on `127.0.0.1:18888` with `tmp/lethe.yaml` (sqlite at `tmp/lethe.db`, forward-auth header trust, `alice/bob/admin` in allowlist, `admin` is admin).
+
+### Positive
+
+- `go test ./... -race -count=1` — green across all packages.
+- `go build ./cmd/lethe` — binary at `tmp/lethe`.
+- Server listens on `127.0.0.1:18888`; PID file written.
+- `GET /healthz` → `200 ok`.
+- `GET /readyz` → `200` with `{"checks":{"database":"ok"}}`.
+- `GET /metrics` → `200` Prometheus exposition.
+- `POST /api/v1/ingest` (alice, NDJSON, `application/x-ndjson`) → `200 {"accepted":2}`; second identical post → `200 {"accepted":2}` (idempotent upsert).
+- `GET /api/v1/sessions` (alice) → `200` with one session, `ended_at` extended by `MAX(turn.timestamp)`.
+- `GET /api/v1/sessions/{tool}/{host}/{sid}` (alice) → `200` with turns inline in `seq` order.
+- Admin override: `GET /api/v1/sessions?owner=alice` (admin) → `200` with alice's session.
+
+### Negative
+
+All return `application/problem+json`:
+- `POST /api/v1/ingest` no auth → `401`.
+- `POST /api/v1/ingest` non-allowlisted user → `403`.
+- `POST /api/v1/ingest` wrong `Content-Type` → `415`.
+- `GET /api/v1/sessions/claude-code/phoebe/nope-id` → `404`.
+- `GET /no-such-route` → `404` (chi `NotFound` handler routed through `apierror.Render`).
+- `GET /api/v1/sessions?owner=alice` as non-admin → `403`.
+- Per-line malformed JSON in NDJSON → `200` with `errors[]`; `accepted` reflects committed lines only (chunk aborts before commit on parse error, per spec).
+- Bind validation: `bind: 0.0.0.0:18889` and `bind: 127.0.0.1:999999` both rejected at config load with `err.code=CONFIG_VALIDATE` on `loopback_bind` tag; process exits `1` before any listener opens.
+
+### Per-user isolation (security boundary)
+
+- Same `(tool, host, session_id)` ingested by alice and bob coexist as distinct rows (`owner` first in PK).
+- `GET /api/v1/sessions` returns only the caller's sessions.
+- `GET /api/v1/sessions/{tool}/{host}/{sid}` for another owner's session → `404` (does not leak existence).
+- Wire-payload `owner` injection ignored: ingesting NDJSON whose Session/Turn JSON includes a stray `"owner"` key still attributes the row to the authenticated identity (confirmed via `internal/shared/wire/` having no `owner` field — the wire types literally cannot deserialize it).
+
+### Graceful shutdown
+
+`SIGTERM` to the running server → process exits within 2s. Final log lines:
+
+```
+"signal received; shutting down"
+"stopping component (CallStop)" component=server.Server
+"stopped component (CallStop)" component=server.Server
+"destroying component" component=database.Database
+"lethe stopped"
+```
+
+No in-flight request errors, no panic, exit code 0.
+
+### Invariants (re-confirmed)
+
+- `internal/shared/wire/` has no `owner` field anywhere (`grep -rin "owner" internal/shared/wire/` empty).
+- All HTTP error paths render through `internal/pkg/apierror/apierror.go:88`, which sets `Content-Type: application/problem+json`. The chi `NotFound`/`MethodNotAllowed` handlers route through the same renderer (Phase 5 fix).
+- Composite primary keys lead with `owner` (`internal/platform/database/migrations/0001_init.up.sql:39,60` — sessions: `(owner, tool, host, session_id)`; turns: `(owner, tool, host, session_id, turn_id)`).
+
+### Result
+
+All positive, negative, isolation, shutdown, and invariant checks pass. No regressions. Task is verified.
+
## Conclusion
### Deviations from plan