Admin and engineering
Static HTML, hand-written, with three build tools that own the parts a human should not be maintaining by hand. Every push to dev is a release: the gate runs, the commit is tagged, and GitHub Pages publishes. A failed gate means no tag and no publish.
1. The pipeline
| Stage | What it does | On failure |
|---|---|---|
| 1. validate | node admin/build/validate.js: structure, internal links, version agreement, canonical and CNAME agreement, licence stamps, the name-watch, and a key-leak tripwire. Runs on pull requests too, so branch work is gated before it reaches dev | Stops the release. No tag, no publish |
| 2. tag-release | Every push to dev is a minor release, tagged v{release}.{major}.{minor}. The version is owned by admin/build/version.txt and must also appear in the release commit's subject as site vX.Y.Z: .... CI verifies the two agree and that the bump is the next minor or a deliberate major, then tags. The first run backfills tags for any historical release from the commit subjects | Stops the release if the file and the subject disagree, or the version was not bumped |
| 3. deploy | Publishes the tagged commit to GitHub Pages. Runs on manual dispatch even without a tag, and never when validation failed or from a pull request | Skipped |
Same pipeline, same order, as SGit-AI__Website, the PKI site and the graphs site. A push to main is deploy-only, so it can serve as a deploy test while the github-pages environment restricts dev.
2. The gate, check by check
- Version agreement.
admin/build/version.txtagainst every page's version badge, the release-history table,llms.txt,llms-full.txtandindex.md. Each release appears exactly once in the history table, which catches a blanket version-bump that rewrites history rows. - Internal links. Every relative
hrefandsrcin every page resolves to a file in the tree. Fragments are stripped; external andmailto:links are skipped. - Canonical host. Every page declares where it canonically lives, and every canonical and
og:urlpoints at the host inCNAME. - Licence stamps. Every markdown document in the tree carries the CC BY 4.0 line, which is what the commissioning pack requires and what a licence audit would otherwise have to be run by hand to find.
- The name-watch. Described below.
- Key-leak tripwire. Nothing in the tree may look like a vault key: a long passphrase joined by a colon to a uuid-shaped identifier.
3. The name-watch
This site exists because a word acquired a second, contradictory meaning and somebody noticed. The ruling settles which meanings are permitted here. The gate enforces it.
On every build, the validator scans the site's own prose (not /briefs/, which is quoted verbatim) for capitalised <Word> Twin constructions. Four terms are on the allow-list, each with a stated meaning on the naming page. Anything else fails the build with the file it appeared in.
$ node admin/build/validate.js
validate: 1 error(s)
✗ name-watch: unruled "Vendor Twin" in built/index.html — add it to
ALLOWED_TWIN_TERMS with a ruling on /naming/, or rename it
The point is not to prevent new terms. It is to make adding one a deliberate act with a paragraph attached, which is exactly what did not happen the first time. It is cheap, it is this site's equivalent of the licence audit, and the collision it guards against has already happened once.
4. The build tools
The single definition of nav and footer
Every page is hand-written and editable. The chrome is not: the nav row, the version badge and the footer columns are defined once in admin/build/chrome.py and rewritten in place across the tree, with the here state set from each page's own path. It also stamps the version into llms.txt, llms-full.txt and index.md, which used to be hand-edited and silently missed.
The reader pages
One page per captured document, carrying the same apparatus and then rendering the raw markdown in-page. marked is vendored under assets/vendor/ rather than pulled from a CDN, so the site has no external runtime dependency.
The gate
Six checks, no dependencies, exits non-zero on any failure. Runs identically in CI and on a laptop.
5. Releasing
# 1. bump admin/build/version.txt (vX.Y.Z, exactly once per release) # and add a row to admin/versions.html # 2. python3 admin/build/gen_documents.py # only if a document was added # 3. python3 admin/build/chrome.py # propagates nav, footer, version # 4. node admin/build/validate.js # 5. git commit -am "site vX.Y.Z: ..." && git push origin dev
The commit subject is load-bearing: CI reads the version out of it and refuses to tag if it disagrees with version.txt.