Files
nxdns/specs/milestone-13.md
T

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)

  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; everytoSettingskey verbatim; every subcommand as its own heading.reference/cli.mdgives 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.

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:

  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 <missing file> 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)

  • The five old docs/*.md files are gone; the four directories and the index exist with every page in ruling 6.
  • Every command in tutorial/ and how-to/ was executed on this host, or is marked in-page as unverified with a reason.
  • 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.
  • 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.
  • 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.