How the site is built
A static site, served byte for byte from this repository by GitHub Pages. There is no build step at deploy time: seven small generators run before a commit and keep the parts that must agree in agreement, and a gate refuses the push when they do not.
Status, from docs/reality.md: shipped v0.1.0 One version in admin/build/version.txt, a release history, and four CI jobs: validate, tag, deploy, verify live · shipped v0.1.0 The RiskMandate chrome (head, header, breadcrumbs, footer, CSP) generated into every page from one definition · shipped v0.1.0 A markdown twin for every page, llms.txt, llms-full.txt and sitemap.xml, generated from the pages · shipped v0.1.0 The eight-check gate: version agreement, links and orphans, canonical host, the leak tripwire, no foreign runtime origin, the data holding together, reality regenerating identically, the house words · shipped v0.1.0 The review folder: the brief read as intent, each node anchored to a section of the brief, validated and rendered · proposed The derived half of the review folder: the code graph from parsers, the join with a coverage figure, every commit read upwards, the navigator as web components
The one-screen version
pip install pytest # the gate's only Python dependency; Node 22 and Python 3.11+ otherwise
python3 admin/build/gate.py --build # regenerate everything, then run the whole gate
python3 admin/build/gate.py # what CI runs: check mode only
python3 -m http.server 8000 # serve the tree locally; there is no build step
python3 admin/build/release.py --version 0.2.0 --kind feature --title "what" --notes "why" # bump, build, gate, commit, push, verify live
The generators, in order
| Generator | Reads | Writes |
|---|---|---|
gen_customers.py | data/customers.json, data/milestones.json | a page per customer, the batch table, the steps, the counts on the home page |
gen_reality.py | data/reality.json | docs/reality.md, the status lines and tables on the pages |
gen_docs.py | docs/**/*.md, team/**/*.md, review/*.md | an HTML page beside each, and an index page per folder |
review/tools/ | the brief, review/intent/stories.json | review/intent/sections.json, the review page; validates the intent files |
gen_chrome.py | chrome.py, version.txt, CNAME | the head, header, breadcrumbs and footer of every page |
gen_versions.py | data/versions.json | the release table on the release history |
gen_twins.py | every page's <main> | the markdown twin beside it |
gen_llms.py | every page and document | llms.txt, llms-full.txt, sitemap.xml |
The eight checks of the gate
In admin/build/validate.js, with no dependency beyond Node. Every problem of every check is printed before the run fails, so one run shows everything.
- Version agreement.
version.txt, every badge, the release table, the indexes and the home twin say the same version. - Internal links, fragments and no orphans. Every link resolves against the real tree; every fragment exists; a page nothing links to fails, because an orphan is unpublished.
- Canonical host. Every page's canonical URL and og:url are on the CNAME host and agree.
- The leak tripwire. sgit key shapes, write tokens, cloud key prefixes, private key blocks, append tokens, bare account numbers, against every text file. The fix for a trip is a redaction, never a wider pattern.
- No runtime foreign origin. No script, style or import from another origin; every script parses; no inline script or style, because the content security policy forbids them.
- The data holds together. Every customer record has a page and every page a record; the milestones name the ladder; no contact details; the versions file is valid.
- Reality regenerates identically.
docs/reality.mdand the status tables are whatdata/reality.jsonsays. - The house words. riskmandate.ai's rules: never "ADP", never "the policy" alone, no score on a behaviour policy, "steps" not "rungs", British spelling, customers not "beta users". The verbatim brief and memo are exempt.
Plus an advisory that never fails the build: em-dashes in prose we write. Then pytest tests/build/ (the chrome on every page, the twins, the customer data, the converters) and node --test tests/unit/ (the gate's own helpers).
The four CI jobs
| Job | When | Does | On failure |
|---|---|---|---|
validate | every push to dev or main, every pull request, manual dispatch | python3 admin/build/gate.py | nothing is tagged or deployed |
tag-release | push to dev only | asserts version.txt equals the newest site vX.Y.Z : … commit subject and that the bump is the next minor, a patch or a major; tags; backfills; pushes the tags | no deploy |
deploy | any push or dispatch, never a pull request | copies the tree minus .git, .github, tests/unit into the Pages artifact and deploys it | the previous version stays live |
verify-live | after deploy | polls https://customers.riskmandate.ai/admin/build/version.txt and the home page badge, with a cache-buster, until both equal the version, for up to ten minutes | the run is red: green does not mean live |
The whole of it is in how a release works. What only a human can do, starting with the DNS record for this host, is in what needs a human.
Where this came from
The pipeline is copied from secrets.sgit.ai, the newest gate in the sgit.ai estate, as every sgit repository says a new repository should do first; the look and the house words are riskmandate.ai's, because this is a sub-site of it. What was changed and why is in the corrections file.