Architecture
This page explains the purpose of every part of the system. If you're trying to understand why a particular file exists or what would break if you removed it, start here.
The trust layer
Three things are load-bearing across the entire stack. Don't break them:
- The Zod schema is the only source of truth.
schemas/source.tsdefines every front-matter contract.pnpm codegenderives the CMS config (static/admin/config.yml) and the JSON Schema (schemas/frontmatter.schema.json) from it. The runtime validator (scripts/validate-frontmatter.ts) uses the same source. CI fails if codegen drifts. - Slugs are immutable. The build runs with
onBrokenLinks: 'throw',onBrokenAnchors: 'throw', andonBrokenMarkdownLinks: 'throw'. A renamed slug = broken citation = build failure. Renames require an explicit redirect or tombstone. - The parser is one-shot (Strategy A).
scripts/parse-source.tsran once over_source/to producedocs/. After that,_source/is gitignored and the parser is archived. Re-running it would overwrite editorial work. Documented in CONTRIBUTING.md as the #1 footgun.
Build-time pipeline
_source/ ──(one-shot parser, archived)──> docs/ ──(Docusaurus build)──> build/
│
├── plugins/generate-aom-json → build/aom.json
├── @signalwire/...-llms-txt → build/llms.txt + llms-full.txt + .md mirrors
├── plugins/inject-defined-term → schema.org JSON-LD per page (postBuild HTML rewrite)
└── search-local + axe-core CI → search index + WCAG gate
Each layer:
schemas/source.ts— Zod source of truth. Five content types (Concept, Hat, MethodologyStep, MindsetPrinciple, Foundation) plus a discriminated union (FrontmatterSchema). The slug regex is strict kebab-case (FoundationSchema permits/as a special-case for the docs-root homepage).scripts/codegen.ts— derives the CMS YAML + JSON Schema from the Zod source. Re-runs on everypnpm buildindirectly (viapnpm schema:checkin CI).scripts/parse-source.ts+parse-source.lib.ts— the one-shot parser. Now archived; refuses to run unlessAOM_FORCE_REPARSE=1(Strategy A guard). Kept in tree as an emergency manual probe.scripts/validate-frontmatter.ts— Zod validator that walksdocs/**/*.mdon every PR. Build fails if a page's front-matter doesn't validate.docusaurus.config.ts— site config. Plugin slate: preset-classic, theme-mermaid, plugin-ideal-image, search-local, llms-txt, generate-aom-json, inject-defined-term. Top-levelheadTagsfor site-wide JSON-LD; top-levelscriptsfor Plausible.sidebars.ts— hand-rolled hierarchy. The 6-hat order is canonical so we don't autogenerate the top level; per-hat concept lists are autogenerated.src/css/custom.css— theme tokens (hat colors, dark/light variants). HatBadge styling. WCAG fixes for the search-local plugin's chips and dark-mode primary.src/data/hats.ts— UI-side duplicate of the parser's HAT_TABLE. Decoupled so theme tweaks don't drag content hashes.src/components/— three React components:HatBadge(was: now CSS-only via remark-emitted markup),OpenInAiButtons(AI-06/07),CiteThisPage(EDU-01/02).src/theme/DocItem/Footer/index.tsx— wrap-not-eject swizzle that injects the OpenInAi + Cite buttons into every doc footer.src/pages/admin.tsx— Sveltia CMS mount point at/admin.plugins/generate-aom-json/— custom Docusaurus plugin.loadContentwalksdocs/**front-matter,postBuildwritesbuild/aom.json(60 concepts + 6 hats + 6 methodology + 8 mindset + 13 foundations).plugins/inject-defined-term/— custom plugin.postBuildparses each built HTML with JSDOM and appends aDefinedTerm(per concept) orDefinedTermSet(per hat) JSON-LD<script>tag.@signalwire/docusaurus-plugin-llms-txt— third-party plugin. Emits/llms.txt(hierarchical index),/llms-full.txt(full content), and per-page.mdsiblings.
Edge layer
After the build artifact lands at Cloudflare Pages:
functions/_middleware.ts— Cloudflare Pages Functions middleware. Intercepts every GET/HEAD; ifAccept: text/markdown→ rewrites the URL to the.mdsibling. Static asset paths (.json,.txt,.xml, anything with an extension) bypass the middleware.static/robots.txt— explicit Allow for GPTBot, ClaudeBot, Google-Extended, PerplexityBot, etc. Sitemap directive.
Editorial layer
static/admin/config.yml— Sveltia CMS config. Generated bypnpm codegenfrom the Zod source. Five collections (Concept, Hat, MethodologyStep, MindsetPrinciple, Foundation) — slugs marked immutable per CMS-04.src/pages/admin.tsx— Sveltia mount. Loads the bundle from CDN at runtime. Decap fallback documented inline.sveltia-cms-authWorker (Cloudflare, pending OAuth setup) — handles the GitHub OAuth dance. Saves create PRs againstmain; never direct push (CMS-05).
CI/CD
Two GitHub Actions workflows:
validate.yml— runs on every PR + push. 6 steps: install, typecheck, test (vitest), validate (front-matter), schema:check (codegen drift), build (Docusaurus production build withonBrokenLinks: 'throw').axe.yml— runs on every PR + push. Builds the site, serves it on127.0.0.1:3010, runspnpm a11y(Playwright + @axe-core/playwright over 4 sample pages × 2 colour modes + a 360px responsive smoke). Fails on any WCAG 2.1 AA violation.
Phase 6 will add deploy.yml (push to main → Cloudflare Pages) and release.yml (tag → version cut + GitHub Release).
Sibling layer
the-kizz/aom-mcp— separate repo. The MCP server. Read-only HTTPS-fetch; serves 8 tools (list_hats, get_hat, search_concepts, get_concept, list_concepts_by_hat, get_methodology_step, get_mindset_principle, get_spec_version) + 2 resources (aom://graph,aom://hats). Not published to npm.
What's NOT here
- No backend database. The site is fully static; the CMS auth Worker is the only stateful edge service (and it stores nothing — just OAuth dance).
- No analytics dashboard inside the wiki. Plausible is external.
- No real-time features. The methodology is a reference, not a chat product.
- No SaaS lock-in. Content is
CC BY 4.0Markdown in a public Git repo. The whole stack could move to any host that serves static files + supports a simple OAuth proxy.
Continue in: