Skip to content

Publish docs

Runbook

Status: Current. Version: 0.1 Date: 2026-08-11

This site is markdown in docs/, built by MkDocs Material, validated by GitHub Actions, and deployed by Cloudflare Pages to docs.kovent.fordsworth.com.

Local preview

./scripts/docs.sh serve      # http://127.0.0.1:8000, live reload
./scripts/docs.sh build      # strict production build into site/
./scripts/docs.sh install    # create .venv-docs and install the toolchain
./scripts/docs.sh clean      # remove site/ and .venv-docs/

The script creates a local virtualenv at .venv-docs on first run. Both site/ and .venv-docs/ are gitignored.

Override the port with DOCS_PORT=8123 ./scripts/docs.sh serve.

The pipeline

Edit docs/ or mkdocs.yml
        ├─► Open PR ──► GitHub Actions (.github/workflows/docs.yml)
        │                 pip install -r requirements-docs.txt
        │                 mkdocs build --strict
        │                 must pass before merge
        └─► Merge to main
                  ├─► GitHub Actions runs again (validation only)
                  └─► Cloudflare Pages (git hook)
                        pip install + mkdocs build
                        deploy → docs.kovent.fordsworth.com
                        usually 1–3 minutes

GitHub Actions does not deploy. It is the quality gate; Cloudflare Pages is the deployer.

Why --strict matters

Strict mode turns warnings into errors, so the build fails on a broken internal link, a nav entry pointing at a missing file, or a page absent from the nav. Because the whole site is cross-referenced, this is the mechanism that stops the documentation rotting as pages move.

If CI fails on a link you believe is correct, check that the path is relative to the file, not to docs/. A page in docs/platform/ linking to an engine needs ../engines/registry.md.

Cloudflare Pages setup

One-time, for whoever provisions the project.

Setting Value
Git repository fordsworth-associates/kovent
Production branch main
Framework preset None
Root directory /mkdocs.yml is at the repository root
Build command pip install -r requirements-docs.txt && mkdocs build
Build output directory site
Environment variable PYTHON_VERSION = 3.12

Set PYTHON_VERSION on both the production and preview environments, or preview builds fall back to the build image default and can resolve a different MkDocs Material version.

Once linked, every push to main deploys to production and every pull request gets its own preview URL, so a docs change can be read in its rendered form before merge.

Scripted alternative

The project can be created through the API instead of the dashboard. This requires the Cloudflare GitHub App to already be installed and authorised on the fordsworth-associates organisation — if it is not, do the first link in the dashboard, because the API cannot grant that consent.

export CF_ACCOUNT_ID=...   # Cloudflare dashboard → Workers & Pages → Account ID
export CF_API_TOKEN=...    # token with the "Cloudflare Pages: Edit" permission

curl -sS -X POST \
  "https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects" \
  -H "Authorization: Bearer ${CF_API_TOKEN}" \
  -H "Content-Type: application/json" \
  --data '{
    "name": "kovent-docs",
    "production_branch": "main",
    "build_config": {
      "build_command": "pip install -r requirements-docs.txt && mkdocs build",
      "destination_dir": "site",
      "root_dir": ""
    },
    "deployment_configs": {
      "production": { "env_vars": { "PYTHON_VERSION": { "type": "plain_text", "value": "3.12" } } },
      "preview":    { "env_vars": { "PYTHON_VERSION": { "type": "plain_text", "value": "3.12" } } }
    },
    "source": {
      "type": "github",
      "config": {
        "owner": "fordsworth-associates",
        "repo_name": "kovent",
        "production_branch": "main",
        "deployments_enabled": true,
        "pr_comments_enabled": true
      }
    }
  }'

Then attach the custom domain:

curl -sS -X POST \
  "https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects/kovent-docs/domains" \
  -H "Authorization: Bearer ${CF_API_TOKEN}" \
  -H "Content-Type: application/json" \
  --data '{"name": "docs.kovent.fordsworth.com"}'

wrangler pages is not used here. It provisions direct-upload projects and cannot bind a Git repository, which is the whole point of this setup.

DNS

The subdomain is a CNAME to the Pages project, configured in the cPanel Zone Editor. Nameservers stay with the host; only this subdomain points at Cloudflare.

Record Name Target
CNAME docs.kovent <project>.pages.dev

Add docs.kovent.fordsworth.com as a custom domain in the Pages project first, then create the CNAME. Cloudflare provisions the certificate in 5–30 minutes.

Order matters. Creating the CNAME before the custom domain is registered on the project gets a Cloudflare error page rather than the site, because Pages routes by the Host header and will not recognise a hostname it has not been told to serve.

The site is marked noindex, nofollow in mkdocs.yml because it is internal and contains unconfirmed regulatory positions. For access control beyond that, put Cloudflare Zero Trust Access in front of the subdomain.

Adding a page

  1. Create the markdown file in the right section directory
  2. Add it to nav: in mkdocs.yml — strict mode fails if a page exists outside the nav
  3. Open with an h1, then a kovent-eyebrow div naming the category
  4. Add a status blockquote if the page carries doctrine or a specification:
> **Status:** Drafted | Specified | Locked | Living document
> **Version:** 0.1
> **Date:** YYYY-MM-DD
  1. Close with a ## Related section linking outward
  2. Run ./scripts/docs.sh build before pushing

For return specifications, follow how to write a return spec rather than inventing a structure.

House style

Rule Reason
Status markers ✅ 🟡 ⬜ mean prototyped, designed, not started Consistent across every engine and spec page
Nothing is marked proven There is no runtime yet, and overclaiming is the fastest way to lose a reader's trust
Every rule and threshold carries a citation Same law the platform enforces
Open questions are named, not hidden A guess in a spec is a future defect
Diagrams are ASCII in fenced blocks Diffable in review, no toolchain, no stale exported images
Tables for enumerable facts, prose for reasoning A table of arguments is unreadable