Tests, gates and CI#
In Alpha, untested is unfinished. A new kernel, equation, numeric operation, encoder form, host command or plan arrives with a test that the checker or the build decides, in Alpha, in the same change. Scripts only drive alpha check and alpha build; they never compute the answer themselves.
There are four layers, from fastest to broadest:
| Layer | What decides it | Where it lives |
|---|---|---|
| The declared suite | alpha test | tests/alpha-tests and each package's own suite |
| Proof roots | The type checker normalizing an equation to a literal | tests/qualification/Proof/ and systems/*/tests/qualification/Proof/ |
| The negative corpus | The exact diagnostics a broken program must produce | tests/defects/ |
| Gates | ok / FAIL claims from alpha check and alpha build, recorded as receipts | scripts/ci/*.sh, wired into .github/workflows/ci.yml |
The declared suite#
alpha test runs the cases in tests/alpha-tests. The file begins format alpha-tests-v1; each case is a test NAME line followed by indented fields, and lines beginning -- are comments:
test closure-checks
kind check
test defect-corpus
kind negative
cases tests/defects
test cache-identity
kind identity
| Kind | What it requires |
|---|---|
check | The root closure (or the workspace's default root) checks clean |
negative | Every case in the defect corpus produces exactly its recorded diagnostics |
identity | Checking the root twice gives clean results and identical module digests |
evaluate | term normalizes to expect |
golden | Building root gives an artifact whose digest is expect |
alpha, protocol | A suite written in Alpha (suite PATH, report NAME); expect-verdict failed asserts that a control fails, proving the framework can go red |
fuzz | Generated or mutated sources (mode, seed, count) |
Useful forms:
alpha test # the whole declared suite
alpha test --package bob-system # one package's suite, from its alpha.package
alpha test --kind negative # only one kind
alpha test --filter cache # cases whose name matches
alpha test --list --strict # the inventory, refusing unclassified cases
alpha coverage # what the suite exercises, by dimension
A selection that matches no case is an error, not a vacuous pass. A golden mismatch never rewrites its expectation silently: --propose-golden writes a reviewable proposal, and --apply-golden PATH --explicit applies it and re-verifies.
alpha coverage reports ten dimensions separately as inventory and as executed: public contracts, language rules, executed branches, diagnostics, compiler passes, numeric operations, codecs, resource transitions, hardware support and requirements. A missing denominator is reported as missing, never as an empty success.
Proof roots#
A proof root is a module of laws that ends with def main : Nat = zero. It is run by path and never imported. Each law states that a computation normalizes to an exact value:
def lossAgrees : (equal Nat resultLoss 1087635456) = (refl Nat 1087635456)
The checker accepts this only if it can normalize resultLoss to exactly 1087635456, the IEEE-754 bit pattern of the hand-computed loss. A numerical claim is therefore checked to the bit, not to a tolerance. What a proof root covers depends on what is being tested:
| Under test | The proof |
|---|---|
| A semantic program | One hand-computed instance, refl to the bit (CheckedLinearStepSemantics) |
| A realization (a kernel) | Run it on its target's checked model under reference/target-models/checked/; memory equals the specification (CheckedWarpSum runs the emitted SM86 program on SM86.WarpModel and proves it halts, writes exactly the sum, and leaves the scoreboard clean) |
| A plan | arenaCertificate … = 1 and a successful build of its artifact root |
| A numeric operation | Known-answer rows plus a refl probe, ties and signs included (F32ModelProbe pins fused multiply-add, round-to-even at ties, negative products and cancellation) |
| A host command | A contract root that leaves fixed bytes (NativePhysical*Contract) |
Every proof says what it does not prove. CheckedWarpSum, for instance, states that it establishes nothing about whether the model equals the silicon; that is a device run's job.
The negative corpus#
tests/defects/ holds 161 cases. Each is a directory with the broken module, a Positive.alpha control and a case.json that pins the expected diagnostics:
{
"modules": ["Form.alpha"],
"expected": [{
"code": "ALPHA-TYPE-MISMATCH",
"module": "Defects.FormByteAnd",
"declaration": "broken",
"span": { "lineStart": 4, "columnStart": 24, "lineEnd": 4, "columnEnd": 28 },
"expected": "Byte",
"actual": "Nat",
"phase": "check"
}],
"reviewed": true,
"positive": "Positive.alpha"
}
The observed diagnostics must match the expected set exactly: no missing code, no extra one, the same phase and the same primary span. An unreviewed record is itself an error, and the positive control must check clean in the same run, so a case cannot pass because the whole file is broken. Systems keep their own refusals under systems/*/tests/negative/.
Gates#
A gate is a script in scripts/ci/ that builds and checks real roots and prints one claim per line:
ok check CheckedWarpSum: …
FAIL mutant hmma-fragments: the checker accepted it
Any FAIL line fails the gate. Run under the recorder, every ok or FAIL line becomes a receipt in the AOS stream:
alpha observe gate checked-warp-paths -- scripts/ci/checked-warp-paths.sh out/checked-warp-paths
A non-zero exit with no FAIL line is recorded as inconclusive, not as a pass.
Break it before calling it tested#
A test that cannot fail proves nothing, so gates carry mutants: deliberately broken copies of the code that the gate must refuse. checked-warp-paths applies nine, among them swapped matrix fragments (hmma-fragments), a barrier replaced by a plain move (argmax-race) and commuted additions (sum-commuted), and requires each to be refused with a specific diagnostic. A mutation that did not apply is itself a FAIL, so a refactor that moves the mutated text cannot silently disable the test. Representative gates:
| Gate | Establishes |
|---|---|
checked-linear-step | The linear learning step on the SM86 machine model, its artifact and its shapes; mutants overlap an output region, change the instruction count and drop scoreboard waits |
checked-warp-paths | Warp sum, matrix tile and greedy argmax on the warp and block models |
arena-certificate | Arena schedules and device certificates on small plans and on a real host |
compatibility-contract | Admission refuses a profile without unified memory, with too little memory, or on another driver branch |
CI#
.github/workflows/ci.yml runs on every push to main and every pull request:
commandsbuilds the compiler, then runs the website checks and thecli-smokegate. That gate runs the declared suite, the strict inventory, per-package suites, the negative corpus and coverage.gatesis a six-shard matrix. Each gate is its own step underalpha observe gate, so every claim lands in the run's AOS stream.- A final step lists every failing receipt with
alpha observe receipts out/aos --failing, and validates the streams. It runs even when an earlier step failed, and the streams are uploaded as artifacts.
Gates that need hardware split in two. CI runs the static half: check, build and refused mutants. The device half runs on the device when its environment variable is set, for example ALPHA_AARCH64_SSH for the DGX Spark gates. The record of a device run is committed under tests/observability/import/<device>/, so the evidence outlives the machine.
Adding a test#
- Write the semantic instance or the contract in Alpha, next to the code it tests or under
tests/qualification/Proof/. - Add a case to the right suite (
tests/alpha-testsor the package's own), or a gate step that checks or builds the root. - Add a mutant the test must refuse, and put one mutant in the CI script.
- Wire it into
.github/workflows/ci.yml. - Say what the test does not prove.
Read next#
- Diagnostics: the stable codes that negative cases pin.
- Observability: the streams and receipts gates write.
- Evidence and status: what checked, run and measured each establish.