Seven gates run in order on every push. Three are ordinary software checks. Four exist because this dataset makes claims about a tradition, and those claims have to stay attached to their sources — and stay true.
A normal test suite proves the code does what its author said. It cannot prove that what the author said is true — a dataset can be internally consistent, fully tested, and still wrong about the world.
So four gates check something tests can't: that every one of the 16 principal figures still carries a citation to a primary source, that no generated file has drifted from the data it was generated from, and that the numbers stated in the prose are still true. Those are marked below with a double stroke — the same single-and-double distinction the Odù figures themselves use.
Each gate halts the run. Nothing downstream executes once one fails, so the first red step is always the real cause rather than a symptom.
Regenerates the 256-figure dataset, the knowledge base, the site, the contact sheet and the TypeScript package from source — on a clean checkout, with none of the local state a laptop accumulates.
fails → the build is not reproducible from a fresh clone
190 tests. Every assertion about the 256 figures checks all 256 rather than sampling — the mapping is small enough to test exhaustively, so it is.
fails → the mapping, encoder or knowledge base is broken
29 tests, including a parity fixture generated from the Python side. The two implementations cannot silently disagree about a name, a seniority rank or a checksum.
fails → the bindings have drifted apart
Exits non-zero unless all 16 principal figures are marked verified, each with a source, a date and a named checker. Currently 16 of 16, against Bascom (1969), Table 1 p. 4 and Table 3 col. B p. 48.
fails → a figure lost its citation; the foundation is unsourced
Parses the corpus without writing anything. Every verse and note must name a source; text from a rights-reserved source is refused rather than stored; a figure reference that doesn't resolve fails rather than landing on the wrong Odù.
fails → unattributed or unlicensed content reached the corpus
Compares numbers stated in the documentation against live values, and fails on phrases that were true once. Added after an audit found eleven stale claims — a release guide announcing the project was unpublished days after release, a roadmap counting 221 unnamed figures against the actual 202.
fails → a sentence stopped being true and nothing noticed
After make has run, a generated file that differs from what the source produces means someone edited an artifact by hand — or committed a source change without regenerating.
fails → a derived file was hand-edited, or is stale
Everything in the project descends from one hand-curated file. That is the point of the architecture — but it means a change at the top has to flow all the way down, and a missed dependency leaves two surfaces disagreeing while every test still passes.
graph TD
A["principal_odu.json
the verified 16"] --> G["generate.py"]
B["compound_names.json
attested names"] --> G
G --> D["odu_256.json"]
D --> T["TypeScript package"]
D --> W["mnemonic demo"]
D --> P["parity fixture"]
C["kb/content/
the corpus"] --> I["ingest.py"]
I --> K[("odu.db")]
K --> S["site + contact sheet"]
D --> S
This gate has already earned its place. The Makefile once omitted compound_names.json as a prerequisite of odu_256.json. Renaming the attested figures updated the site but not the TypeScript package — the two surfaces served different names, and every test stayed green. The staleness check caught it on the first run afterwards.
CI is the backstop, not the first line. A local hook runs the same checks in well under a second, so a bad commit is stopped before it exists rather than reported minutes later.
Enabled once with git config core.hooksPath .githooks, and bypassable with --no-verify when you genuinely mean to.