📦 WP-114 — docs(site): a MkDocs Material site over docs/¶
Engineering log · Phase 11 — Rolling, toward v0.5.0
WP-114 — a default list that vanishes when you name one member¶
The site publishes what the repository already holds: mkdocs.yml owns no prose beyond docs/index.md, no plugin generates a page from source, and every register stays a plain markdown file a reader can open on GitHub without the toolchain. The work was configuration, and configuration is where a wrong value reports success.
Naming one member of a MkDocs default list replaces the whole list. Supplying plugins: at all drops the implicit search, so a site whose search box finds nothing builds green; supplying markdown_extensions: drops tables, and this repository is wall-to-wall pipe tables — the assumption register, the roadmap, every acceptance table would render as literal pipe characters on a build that reports no error. Both are restated explicitly, and tables is asserted in scripts/_tests/test_audit_docs_site.py rather than trusted to stay listed.
--strict is the whole value of building locally. It fails on a link to a page that does not exist and on a page the nav never lists. A register nobody can navigate to is one nobody reads, and neither failure raises anything without the flag.
A second config file needed a second hook instance, for the same reason docs/ did. Material's mermaid fence is enabled through a !!python/name: tag; MkDocs parses its config with yaml.Loader and constructs it, pre-commit's check-yaml uses safe_load and cannot. So check-yaml is now two instances — the default one excluding mkdocs.yml, and an --unsafe one scoped to it, which still validates the syntax of a 120-line nav without importing anything from it. mdformat split the same way and for the same shape of reason: docs/ is now rendered by a dialect the rest of the tree is not written in, and running the mkdocs plugin over the README or AGENTS.md would apply that dialect to files no site renders.
The audit has to run where the exposure enters. The docs group is deliberately outside make setup and outside dev: no gate imports it, and a contributor who never builds the site never installs the tree. The consequence is that no other workflow's environment contains it, so the licence audit — which scans the installed environment rather than the diff (D15, D16) — runs in the docs job, ahead of the build. Measured over the installed tree: 106 distributions, 343 shipped binaries, no GPL-family licence declared, bundled or shipped. One transitive is not permissive and is named rather than buried: certifi is MPL-2.0, weak file-level copyleft, reached through mkdocs-material → requests; MPL-2.0 was already present via pathspec, which arrives with mypy.
configure-pages reads by default, and cannot do otherwise with the token CI has. Its enablement input defaults to false, and its own action.yml states that enabling requires a token other than GITHUB_TOKEN. So pages: read is the ceiling that job can use rather than a permission it is being denied, and the step is skipped on pull requests: it fails when Pages is not enabled, which would turn every PR red for a reason no PR can fix.