# Milestone 13: restructure the documentation to Diátaxis Goal: replace the four mixed-mode documents with the four Diátaxis modes, add the missing tutorial, and prove every instruction by running it. ## Rulings (binding) 1. **Four directories plus an index.** `docs/tutorial/`, `docs/how-to/`, `docs/reference/`, `docs/explanation/`, and `docs/README.md` — the index names the four modes, says who each is for, and links every page. The old `docs/{operator,architecture,config-reference,api,performance}.md` are deleted by the orchestrator once the new pages exist; no redirect stubs (greenfield repo, AGENTS.md forbids compatibility leftovers). 2. **A document serves one mode.** The defect being fixed is `operator.md`, which interleaves install procedure, CLI reference, exit-code tables and troubleshooting in 418 lines. Reference pages state what is; how-to pages state what to do; explanation states why; the tutorial teaches. When porting text, move a paragraph to the mode it belongs to rather than keeping it where it was. 3. **Every command block in `tutorial/` and `how-to/` is executed verbatim by the session that writes it**, on this host, before the session reports. A command that cannot run here (needs a Raspberry Pi, root, a domain, hardware TLS) is marked in the page itself as not verified on this host, with the reason. This ruling exists because milestone 11's docs were written from source-reading alone: the first real blocklist download aborted the process (commit 35f2324) and a stale SPA bundle crashed the settings page — both would have surfaced if the documented paths had been run. No session may report a page complete on the strength of having read the code. 4. **Content is verified against `src/`, not copied from the old pages.** Defaults, flags, exit codes, paths and route names come from the code at HEAD. Report any discrepancy found; do not fix source in this milestone (docs-only, ruling 9). 5. **The tutorial has one guaranteed outcome.** `docs/tutorial/first-run.md` takes a reader from a clean checkout to a running nxdns that answers a query and blocks a domain from a real blocklist, on unprivileged ports in a scratch directory, then stops it cleanly. It teaches by doing and states what the reader will have at the end. No branching, no options menu, no "you may also". 6. **File map.** - `docs/tutorial/first-run.md` — new. - `docs/how-to/`: `install-with-systemd.md` (incl. the Raspberry Pi 5 aarch64 binary), `install-with-docker.md`, `upgrade.md`, `troubleshoot.md`, `enable-doh-and-dot.md`, `set-up-admin-authentication.md`, `back-up-and-restore.md`, `measure-performance.md`. - `docs/reference/`: `configuration.md` (every section, field, default, range), `api.md` (all 56 operations), `cli.md` (six subcommands, every flag, exit codes), `files-and-directories.md` (data dir layout, file modes), `performance.md` (targets + measured numbers). - `docs/explanation/`: `architecture.md`, `configuration-model.md` (the file seeds the database once, the database is truth, export/import round trip), `performance-and-testing.md` (why the targets exist, why CI does not gate on them, what hermetic tests do and do not prove). 7. **Drift guards repointed and kept honest.** `docs/docs.zig` embeds `reference/api.md`, `reference/configuration.md`, `reference/cli.md`. `src/docs_drift_test.zig` keeps its three guards against those paths: every served route as a full `| METHOD | \`pattern\` |` row; every `toSettings` key verbatim; every subcommand as its own heading. `reference/cli.md` gives each subcommand a `## \`name\`` heading so the guard anchors on structure, not prose. 8. **Style.** Plain sentences. No banned vocabulary (leverage, seamless, robust as filler, rule-of-three padding, "it's important to note"). No badges, no marketing. Code blocks are copy-pasteable and use the scratch paths the tutorial establishes, not `/var/lib/nxdns`, unless the page is about a real install. 9. **Docs-only.** The only non-docs edits are the orchestrator's: `docs/docs.zig`, `src/docs_drift_test.zig`, `README.md` links, and PLAN.md's repo-layout line. No behavior changes. ## Sessions X1, X2, X3, X4, X5 run in parallel — every session owns distinct files. ## Session X1: index + tutorial + README links Owns `docs/README.md`, `docs/tutorial/first-run.md`, `README.md` (links section only). Rulings 1, 5, 3 (the tutorial is executed end to end). ## Session X2: how-to, install and operations Owns `docs/how-to/{install-with-systemd,install-with-docker,upgrade,troubleshoot}.md`. Rulings 2, 3, 4. ## Session X3: how-to, security and measurement Owns `docs/how-to/{enable-doh-and-dot,set-up-admin-authentication,back-up-and-restore,measure-performance}.md`. Rulings 2, 3, 4. ## Session X4: reference Owns `docs/reference/{configuration,api,cli,files-and-directories,performance}.md`. Rulings 2, 4, 7 (heading convention). ## Session X5: explanation Owns `docs/explanation/{architecture,configuration-model,performance-and-testing}.md`. Rulings 2, 4. ## File ownership X1 docs/README.md + docs/tutorial/* + README.md; X2 and X3 disjoint files under docs/how-to/; X4 docs/reference/*; X5 docs/explanation/*. Orchestrator: docs/docs.zig, src/docs_drift_test.zig, PLAN.md, deletion of the old pages, spec. ## Delivered Seventeen pages: `docs/README.md`, one tutorial, eight how-to guides, five reference pages, three explanation pages. The five old documents are deleted. The orchestrator repointed `docs/docs.zig` at `reference/{api,configuration,cli}.md` and `src/docs_drift_test.zig` at the same three, moved the subcommand anchor from `### \`name` to `## \`name`, fixed the README Quickstart link that pointed at the deleted `operator.md`, and corrected PLAN.md's repo-layout line and its §13.2 OpenAPI paragraph (which still promised a renderer and contract tests that were never built). All three guards were proven able to fail: deleting one API table row, one settings key row and one `## \`export` heading each produced a named build failure. The settings guard was strengthened during review — it searched for a bare key, which prose and the annotated example could satisfy after a field's row was deleted; it now anchors on `| \`key\` |`. Ruling 3 held. The tutorial ran end to end twice, the second time after the review fixes. DoH and DoT answered real queries, `POST /api/certs/reload` returned both its success and its failure payload, the export/import round trip was byte-identical, a Docker image was built and its container served DNS, and the offline password change was proven (old password 401, new 200). What could not run here — root-only steps, a second LAN host, a Raspberry Pi, a registry push, the full-scale bench, `dnsperf` — is marked in the page that documents it, with the reason. Five review rounds: 15 findings, then 8, then 1, then 1, then clean. One round-1 finding was rejected on evidence (`troubleshoot.md` already stated the verified exit codes; the contradiction was in `reference/cli.md`). ## Discrepancies found, not fixed Docs-only per ruling 9. These are source-side and belong to a later milestone: 1. `nxdns run` exits 1 for seed-file errors (`ParseZon`, `MissingDefaultGroup`, `NoUpstreams`) while `check` and `import` exit 2 for the same file, because `app.isConfigFault` lists only `NoUsableUpstreams`, `BadBindAddress`, `BadRateLimit`, `BadCertificate`. 2. `nxdns check` prints `OK: no problems found` and exits 0 after emitting WARN lines. 3. `nxdns check` never parses a certificate or tests that the key matches it — it checks path readability and key mode only. A green `check` is followed by `run` exiting 2 with `BadCertificate` on a mismatched pair. 4. `check --config ` exits 1; the implicit path prints `nothing to check` and exits 2. 5. A blocklist source attached to no group is silently inert, and `validate.zig` has no diagnostic for it. 6. `nxdns check` says it validates without writing, but the `config.db` branch chmods, enables WAL and runs migrations. 7. `pruneOrphans` matches only `.list`/`.wild`, so an orphaned `.raw.tmp` is never swept. 8. `isEmpty` counts auto-materialised client rows, so a server that has answered one query ignores a seed file placed afterwards. 9. PLAN §18 and the docs say 10 MB / 15 MB; CI asserts 10 MiB / 15 MiB. ## Acceptance (milestone complete) - [x] The five old `docs/*.md` files are gone; the four directories and the index exist with every page in ruling 6. - [x] Every command in `tutorial/` and `how-to/` was executed on this host, or is marked in-page as unverified with a reason. - [x] `zig build test` passes with the three drift guards pointing at the new reference pages: 16/16 steps, 1175 pass, 113 skip (integration-gated), 0 failed. - [x] `zig build test -Dintegration` passes: 1284 pass, 4 skip (the live-network TLS tests excluded by milestone-1 design), 0 failed. No src/ behavior change. - [x] No page mixes modes: no procedure in `reference/`, no field table in any `how-to/` or `tutorial/` page. ## Anti-requirements - No new documentation subjects (no metrics-families reference, no security-model page) — this milestone moves and splits existing content plus the tutorial. - No redirect stubs or `docs/legacy/`. - No source fixes; report discrepancies instead. - No doc generator, no site builder, no MkDocs.