* docs(b3-3): mark B3-3 in progress and record HP-3's merge SHA HP-3 (#1461) merged as `52601114`; B3-3 (lifecycle extraction into `Server/internal/app/`) starts on `feat/b3-3-lifecycle`. Status line only — no step-table or scorecard edits. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011EvaP8XtuTJD86eSeuJrce * refactor(b3-3): move the process lifecycle into Server/internal/app (pure move) Every run* block, the healthcheck CLI, the banner and disk helpers, the seq seeding, the bind-retry listener and the restart coordinator move out of `Server/main.go` into a new `Server/internal/app` package, verbatim. `main.go` keeps the CLI dispatch, the log sinks, the `version` symbol `-ldflags` names and the restart handoff, and calls `app.Run`. Behaviour-neutral. The only substitutions are the package clause, `run` -> `Run` and `runHealthcheckCLI` -> `RunHealthcheckCLI` (the two entry points main() calls), the five restart-coordinator identifiers main() still names (`RestartCoordinator`, `NewRestartCoordinator`, `RestartBackstopDelay`, `PerformRestartHandoff`, `Disarm`), and `version` becoming a parameter of `Run`/`runServeAndWait` instead of a package-level var — it has to stay in package main because `-X main.version` is what the Makefile, `release.yml` and the Dockerfile inject. Normalised-diff proof (HP-1's shape): undoing those substitutions over the whole Server diff and running `sort | uniq -u` leaves 45 unpaired lines, all of them comment prose or the new import — no code line is unpaired. `DBImportAllow` swaps its `main.go` row for the four `internal/app` files that now own the handle (all `boundary`); `docs/architecture/server-boundaries.md` is regenerated from it (50 -> 53 importers, boundary 7 -> 10; the summary table's stale 6 is corrected to match the generated line). Full gate green: four tag variants, vet, `go test -race ./...` with coverage (aggregate 80.1%, unchanged), coverage floor, `-tags deadlock ./ws/`, golangci-lint v2.11.3 (0 issues), genprotocol/sqlc/gendocs drift, check:docs, check:hygiene. `TestAuthCharacterization` green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011EvaP8XtuTJD86eSeuJrce * refactor(b3-3): one App with one composite close, replacing run()'s defer stack `type App` threads the dependencies through fields instead of run() locals. Every stage registers exactly one close step as it comes up, in start order, and `App.Close(ctx)` walks them backwards — so there is a single teardown path, taken on a failed start, a serve error and a clean shutdown alike, where run() had a LIFO `defer` stack and an early return that skipped whatever it had not reached. `main.go` is 1,019 -> 99 lines: the CLI dispatch, the log sinks, the restart handoff, and `cfg := app.LoadConfig(...); a := app.New(cfg, ...); a.Run(ctx)`. The three ordering facts the inventory records are preserved, and are now what the reverse walk is FOR rather than emergent from where a `defer` happened to sit: the audit writer and event persistence both stop before `database.Close`, and the hub's GracefulStop runs on every return from Run so a supervised LiveKit process is never orphaned (OC-0027). Test-first. Three RED rows, each with a negative control on this branch: | Property | Mutation that must fail it | Result | | ----------------------------------------------------- | -------------------------------------- | ------ | | close order is the reverse of start order | walk the closers forward | FAIL | | first error returned, every later close still runs | return on the first error | FAIL | | hub stops when a stage after the router fails | skip teardown on a failed start | FAIL | Deliberate, documented changes that come with the contract: * `Run(ctx)` is real: bgCtx and the serve context both descend from it, so cancelling the caller's context stops the server the way a signal or a restart request does (`context.AfterFunc` joins the coordinator's context to it). * the four stop steps that used to build a fresh `context.Background()` with their own 5s cap now take Close's budget as their parent, so a wedged step cannot push teardown past the 30s the operator was told about; * `database.Close`'s error is reported instead of discarded; * the ACME start moves one stage later, after the maintenance loop, which is what makes "reverse of start" equal the order run()'s explicit shutdown call used to impose by hand (drain in-flight HTTP handlers first); * `internal/app/app.go` gains a `DBImportAllow` row; the inventory doc is regenerated (54 importers, boundary 11). Full gate green, aggregate coverage 80.2% (floor 79.8%). `TestAuthCharacterization` green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011EvaP8XtuTJD86eSeuJrce * refactor(b3-3): the hub has one owner — construction moves to internal/app `api.NewRouter` gains an `api.Runtime` parameter and stops returning a hub. `app.StartRuntime` (`Server/internal/app/hub.go`) now builds the rate limiter, the service layer and the hub, applies every pre-Run setter and starts the dispatch goroutine — the `ws.NewHub` call that was at `router.go:106` and the plugin and LiveKit setters that were at `:325-360`. Before this, the hub had two owners: the router built and wired it, and `main.go` set the event persister and the event store after `NewRouter` returned. Both now sit inside `internal/app`, which is what gives B3-4 one place to turn the required setters into validated `HubOptions`. The limiter and the service layer move with the hub because it needs the SAME instances — the limiter persists auth lockouts and the services hold the permission cache the hub invalidates, so a second copy of either would silently split that state. `Runtime` carries them plus `VoiceEnabled`, which is the `lkErr == nil` guard the voice routes were already mounted behind; `routerVoiceRoutes` keeps only the mounting half, `routerPluginWiring` becomes `app.wirePlugins`, and the LiveKit client and companion process are built by `app.startVoice` with its OC-0019 fail-closed ordering unchanged. The hub is its own lifecycle stage now, started before the router, so `App.Close` stops it through the "hub" step exactly as before. Call sites updated at the call site only, wiring with no assertion changes: six `api_test` files and `cmd/gendocs`. `gendocs` produces a byte-identical route index (its drift check is part of the gate). Full gate green: four tag variants, vet, `go test -race ./...` with coverage (aggregate 80.2%), coverage floor, `-tags deadlock ./ws/`, golangci-lint v2.11.3 (0 issues), genprotocol/sqlc/gendocs drift, check:docs, check:hygiene. `TestAuthCharacterization` green. `internal/app/hub.go` gains a `DBImportAllow` row; the inventory doc is regenerated (55 importers). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011EvaP8XtuTJD86eSeuJrce * test(b3-3): fail every lifecycle stage in turn and assert what teardown releases `Server/internal/app/lifecycle_failure_test.go` is the failure-injection report the B3 exit gate asks for. Each of the fourteen stages `App.start` brings up is made to fail in turn, and every row asserts the same four properties: the returned error names the stage, no goroutine is left running (`goleak`), the database handle is closed so the SQLite process lock is released for the successor a restart handoff is about to start, and the listener is not left bound so that successor can take the port. The table is generated from `App.stages()` rather than written out, so a stage added later is covered the day it is added. Two rows are not injected. A real out-of-range port drives the genuine listener-bind failure (the OC-0027 path). And a run that is cancelled while actually serving is the control: the same four properties on the path where nothing fails, with a nil error — so the injected rows are not passing merely because something went wrong. Negative controls on this branch: | Assertion under test | Mutation applied | Result | | --------------------------------- | ------------------------------- | -------------- | | the database handle is closed | drop the `database` close step | FAIL, 11 rows | | the hub's dispatch loop is stopped| drop the `hub` close step | FAIL, 12 rows | Green under `go test -race ./internal/app/`. Full gate green, aggregate coverage 80.2% (floor 79.8%), `internal/app` 66.1%. `TestAuthCharacterization` green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011EvaP8XtuTJD86eSeuJrce * docs(b3-3): evidence block, after-state lifecycle rows, status line and step row Plan §B3-3 gains its evidence block: pre-squash SHA per numbered item with the gate and `TestAuthCharacterization` result for each, `main.go`'s 1,019 → 99 lines, the normalised-diff proof for the pure move (the exact command and its 45 unpaired lines, all comment prose or the new import, broken down per file), the composite-close negative controls, the failure-injection table (sixteen rows × four assertions), the hub-ownership before/after, the build-and-packaging check, the gate list and the coverage figures. `docs/architecture/server-boundaries.md`'s hub lifecycle inventory keeps its before-state tables and gains the after-state: where each of the seven setters is called now, and the fifteen-stage start list with the close step each registers, plus why the resulting close order is what makes the three ordering facts hold by rule rather than by `defer` placement. One correction the check turned up and the evidence records: a plain `go build .` from `Server/` produces a binary named `Server`, not `chatserver` — that comes from the module path and is unchanged by B3-3. Every packaging path (`Makefile`, `Dockerfile`, `release.yml`, all untouched) passes `-o` explicitly, and `-X main.version` still resolves. Status line, step-table row and the `docs/plans/README.md` B3 row point at PR #1464; the squash SHA lands here at merge. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011EvaP8XtuTJD86eSeuJrce * fix(b3-3): keep the background workers alive through the HTTP drain Codex (P2) caught a defect in B3-3's own new `Run(ctx)` contract: `bgCtx` was derived from the caller's context, so cancelling that context stopped the event persister, the audit writer and the maintenance loop immediately — before `Close` ran its HTTP-first drain. That drain exists precisely so in-flight handlers' broadcasts still reach a live hub and event persister and their audit records still reach a live writer; with the consumers already gone, both are dropped. It also made caller-context shutdown behave unlike the SIGTERM and restart paths, which cancel only the serve context. `run()` had this right for free by rooting `bgCtx` at `context.Background()`. `main.go` passes `context.Background()`, so no released build was affected — the defect is in the new contract this PR introduces. `context.WithoutCancel(ctx)`: `bgCtx` inherits the caller's values but not its cancellation. Cancelling ctx still stops serving, because the serve context descends from it in `startSignals`; when the background work stops stays `Close`'s decision, which is what the ordering rule promises. Test-first. `TestAppRun_CallerCancel_KeepsBackgroundWorkersAliveThroughTheDrain` records `bgCtx.Err()` as each close step runs — a new test-only `onCloseStep` seam makes the teardown walk observable — and requires bgCtx still live at `signals`, `http`, `maintenance` and `audit-writer`, and already cancelled by `database` (the `event-persistence` step is what cancels it and joins the pruner). RED on all four rows before the fix; the negative control, restoring `context.WithCancel(ctx)`, fails it again. Full gate green on the merged tree, including `dev`'s new `errorlint`, `exhaustive` and `durationcheck` linters: golangci-lint v2.11.3, 0 issues. Aggregate coverage 80.2% (floor 79.8%). `TestAuthCharacterization` green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011EvaP8XtuTJD86eSeuJrce --------- Co-authored-by: J3vb <dragon613gaming@gmail.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Documentation index
Every document in docs/ is listed here. If it is not on this page it is not
current guidance.
Docs fall into four kinds, and the difference matters when you are deciding whether to trust one: guidance tells you how to do something, reference describes a contract the code actually implements, audits are dated snapshots that were true when written and were never updated, and plans record intent. Read an audit as history, not as status.
Start here
| I want to… | Read |
|---|---|
| Run a server | quick-start.md |
| Deploy for real | deployment.md |
| Contribute a change | contributing.md |
| Understand the system | architecture/ |
| Report a bug | Issues |
| Ask, or suggest an idea | Discussions |
| Report a vulnerability | security.md |
| Know who can read what | trust-model.md |
Guidance
| Document | Covers |
|---|---|
| quick-start.md | Getting a server running with the fewest steps. |
| deployment.md | Production deployment on Windows and Linux. |
| contributing.md | Environment setup, the branch and PR model, coding standards, how to run the checks CI runs. |
| security.md | How to report a vulnerability, and how findings are handled in public vs private. |
| trust-model.md | Who can read what: operator-readable text and files, E2EE media, transport, at rest, what beta does not claim. |
| livekit-setup.md | Standing up the LiveKit SFU for voice and video. |
| port-forwarding.md | Making a server reachable from outside the LAN. |
| tailscale.md | Remote access without port forwarding. |
| mcp-introspect.md | Dev-only MCP server for introspecting a running instance. |
Reference
These describe contracts the code implements. If one disagrees with the code, the code is right and the document is a bug.
| Document | Covers |
|---|---|
| api.md | REST API under /api/v1. |
| protocol.md | WebSocket protocol — frames, sequencing, reconnect. |
| schema.md | SQLite schema and migrations. |
| server-configuration.md | Every server configuration option. |
| credential-storage.md | What the desktop client persists, and where. |
| ../protocol/schema.json | Generated-code source of truth, at the repository root because it is owned by neither side. Server/ws/message_types.go and Client/src/lib/protocolTypes.ts are generated from it — never hand-edit either. See ../protocol/README.md. |
Architecture
architecture/README.md indexes the blueprints and carries the maintenance rule: each blueprint names its source-of-truth files, and a PR touching those updates the blueprint in the same change.
- system-overview.md, server.md, client.md
- data-model.md, websocket.md, voice-e2ee.md
- ux/ — target-state UX spec, per-view states and event→reaction maps
- platform-contracts.md — target-state desktop/browser seam: what has to move behind a contract before the client can run in a browser (B7)
- plugins.md — the experimental WASM plugin boundary: off by default, compiled out of releases, no API promise, what may become a plugin after beta and what never moves
client-architecture.md is a redirect stub; the live document is architecture/client.md.
Audits — dated, not maintained
Point-in-time snapshots. They are not updated as the code moves, and they are deliberately left alone when paths change, so links from commit messages keep resolving. Anything here may be stale; the ledger and the plan index carry current status.
| Audit | Scope |
|---|---|
| audit-2026-08-23-repository-layout.md | Repository layout and contributor experience (RL-01…RL-22). |
| audit-2026-08-23-repository-health.md | Full repository health. |
| audit-2026-08-19.md | Repo health. States "0 open findings" — untrue since; see the ledger. |
| audit-test-coverage-2026-08-19.md | Test audit (T-*, a separate register from the OC-* ledger). |
| audit-2026-08-04-docs-and-coverage.md | Documentation accuracy and UI/UX test coverage. |
| audit-2026-08-04.md | Security review. |
| audit-test-coverage-2026-07-25.md | Test-coverage audit. |
| audit-2026-07-19.md | Architecture and spec-conformance review. |
| audit-2026-04-07.md | First comprehensive audit. |
Plans
plans/README.md indexes every plan with a recorded state — active, partially implemented, design-only, or shipped — and is the authority over a plan's own header, which can drift.
Where status actually lives
Do not read a defect count, or a "what works" claim, out of a document on this page. Status has owners:
| Concern | Source of truth |
|---|---|
| Defect status | .superpowers/findings-ledger.json (FINDINGS.md is rendered from it) |
| Security-sensitive defects | Private GitHub Security Advisories |
| Phase order and gates | plans/repo-health-roadmap-2026-08-23.md |
| Current measured baseline | plans/b0-baseline-2026-08-25.md |
| Generated-code contracts | CLAUDE.md, "Generated code — never hand-edit" |
A CI job checks that documents on this page do not contradict the ledger's
counts. Adding a count to a document means adding it to that check's allow-list
in scripts/check-doc-counts.mjs.