9.4 KiB
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)
-
Four directories plus an index.
docs/tutorial/,docs/how-to/,docs/reference/,docs/explanation/, anddocs/README.md— the index names the four modes, says who each is for, and links every page. The olddocs/{operator,architecture,config-reference,api,performance}.mdare deleted by the orchestrator once the new pages exist; no redirect stubs (greenfield repo, AGENTS.md forbids compatibility leftovers). -
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. -
Every command block in
tutorial/andhow-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 (commit35f2324) 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. -
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). -
The tutorial has one guaranteed outcome.
docs/tutorial/first-run.mdtakes 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". -
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).
-
Drift guards repointed and kept honest.
docs/docs.zigembedsreference/api.md,reference/configuration.md,reference/cli.md.src/docs_drift_test.zigkeeps its three guards against those paths: every served route as a full| METHOD | \pattern` |row; everytoSettingskey verbatim; every subcommand as its own heading.reference/cli.mdgives each subcommand a## `name`` heading so the guard anchors on structure, not prose. -
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. -
Docs-only. The only non-docs edits are the orchestrator's:
docs/docs.zig,src/docs_drift_test.zig,README.mdlinks, 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
### \nameto## `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 ## \exportheading 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:
nxdns runexits 1 for seed-file errors (ParseZon,MissingDefaultGroup,NoUpstreams) whilecheckandimportexit 2 for the same file, becauseapp.isConfigFaultlists onlyNoUsableUpstreams,BadBindAddress,BadRateLimit,BadCertificate.nxdns checkprintsOK: no problems foundand exits 0 after emitting WARN lines.nxdns checknever parses a certificate or tests that the key matches it — it checks path readability and key mode only. A greencheckis followed byrunexiting 2 withBadCertificateon a mismatched pair.check --config <missing file>exits 1; the implicit path printsnothing to checkand exits 2.- A blocklist source attached to no group is silently inert, and
validate.zighas no diagnostic for it. nxdns checksays it validates without writing, but theconfig.dbbranch chmods, enables WAL and runs migrations.pruneOrphansmatches only.list/.wild, so an orphaned.raw.tmpis never swept.isEmptycounts auto-materialised client rows, so a server that has answered one query ignores a seed file placed afterwards.- PLAN §18 and the docs say 10 MB / 15 MB; CI asserts 10 MiB / 15 MiB.
Acceptance (milestone complete)
- The five old
docs/*.mdfiles are gone; the four directories and the index exist with every page in ruling 6. - Every command in
tutorial/andhow-to/was executed on this host, or is marked in-page as unverified with a reason. zig build testpasses with the three drift guards pointing at the new reference pages: 16/16 steps, 1175 pass, 113 skip (integration-gated), 0 failed.zig build test -Dintegrationpasses: 1284 pass, 4 skip (the live-network TLS tests excluded by milestone-1 design), 0 failed. No src/ behavior change.- No page mixes modes: no procedure in
reference/, no field table in anyhow-to/ortutorial/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.