Canopus, the build standard

How anything non-trivial gets built into HEADING OS. Canopus answers one

How anything non-trivial gets built into HEADING OS. Canopus answers one governance question: how do you let an AI build real capability into a system you depend on, without ever having to take its word for it?

It is a named, numbered, repeatable procedure. Every non-trivial change to the engine goes through it. Typo fixes and configuration edits do not, deliberately, because a process everybody skips is worse than no process.

Two of its seven steps belong to the operator. The assistant is forbidden from taking either on their behalf, because a standard whose approvals the assistant can grant itself is a standard with zero approvals.

The steps are defined as data in scripts/utils/canopus_steps.py, which is their only definition. This page and the /canopus skill both summarise that module; neither may renumber it.

The one thing Canopus exists to prevent

A test too weak to decide anything, passing as a test that decided something. Across 62 recorded slices, a test was never once weakened after the fact to make a failure go away. It was born too weak thirty times. The green light to distrust is the one that no wrong version of the code was ever made to fail.

The four acts

Act Steps The point of it
1. Decide 1 to 4 Nothing is built yet
2. Build 5 The implementer is not the planner
3. Check 6 Green is not the same as right
4. Production 7 The undo is named before it is needed

The seven steps

1. Define the Value (Act 1)

One sentence saying what would be worth having, in business terms. Not “add a module”, but “a weekly digest that tells me which suppliers slipped this week, so I stop finding out at the quarterly review”. It goes into the slice record verbatim, and step 6 measures the built thing against it.

What it buys: a stated purpose you can hold the finished thing against. Half the failures later in this list turn out to be a good build of the wrong idea.

2. Brainstorm the scope (Act 1)

superpowers:brainstorming, until a scope document says what should be built rather than how to build what was asked. What is in, what is deliberately out, what the alternatives were, and why they were rejected. The rejected options are written down, not discarded, so nobody re-litigates them in three months.

What it buys: the argument happens while changing your mind is still free.

3. Write the plan (Act 1)

superpowers:writing-plans. The plan states three to five success criteria, each written so that a single test can decide it, in the form when this happens, the system shall do that. The criteria are not brainstormed from what came to mind: they come from a partition of the input domain, one row per value class with the awkward edges included, and the name of the test that decides each.

The criteria then become real test files rather than a description of them, written before a single line of the implementation exists. Those files are the contract. They fail, correctly, because there is nothing yet to make them pass.

The plan carries a byte budget: 16 KiB warn, 24 KiB hard, because a plan nobody finishes reading is a plan whose decisive paragraph nobody read. Both numbers are a proposal with operator override, never a gate; nothing refuses a commit over them. They were set from 99 real plans (median 23,704 bytes), and 51 of the 99 would pass the hard number unchanged.

What it buys: the definition of “done” is fixed in writing, in executable form, before anybody is invested in the work.

4. Scrutinize the plan, apply every finding (Act 1, the operator’s)

An adversarial review runs against the plan, and every finding is applied rather than triaged. No code exists yet, so this is the cheapest moment to change anything. Then the operator reads it and commits.

That commit is the approval. It captures the plan and the RED contract together at one point in time, and that sha is the freeze.

What it buys: a fixed reference point. Anyone can later ask, mechanically, whether the delivered work matches what was approved.

5. Build it, under separation (Act 2)

superpowers:subagent-driven-development dispatches a fresh implementer per task plus a reviewer who did not write the code. The session that wrote the plan does not implement it. This is not ceremony: an author reviewing their own work reproduces their own assumptions with the same weights and reaches the same conclusions. Every commit descends from the approval sha.

What it buys: the entity that decides what “done” means is never the entity that declares it done.

6. Scrutinize the built thing, apply every finding (Act 3)

Relentless, bounded, and every finding is applied. Anything still open at the end goes to the operator rather than being quietly closed. Each finding carries an origin, and the origin decides where it goes:

What it buys: a weak test cannot survive its own slice. Naming the origin is what forces it back to step 3 instead of being smoothed over.

7. Production (Act 4, the operator’s)

The operator ships, on the evidence rather than on a summary, including what the contract does NOT cover. Then the slice writes one committed record under records/slices/: the value, the approval it descends from, what the reviews found, what was fixed, and, in plain words, how to undo it. Which commit to revert, which baseline to restore, what to re-run.

What it buys: an audit trail per change, and a rollback path that exists before the emergency.

What the machine can see, and what it cannot

Two of the seven steps leave a durable trace in this repository: step 4 (the approval commit and the contract files it carries) and step 7 (the record under records/slices/). The other five are human and agent work that no file here records. The scope document and the plan live in the operator’s private overlay and are referenced by digest, never by path, because this repository is public.

canopus_steps.py carries a machine_visible flag per step so a reader of the agenda can tell the two steps this repository can evidence from the five it cannot.

The approval is a commit, not a lock file

git show <sha>:<path> reads the frozen bytes. git diff answers whether the contract moved. git merge-base --is-ancestor answers whether the implementation descends from the approval. Nothing on the machine holds those bytes down, and this page does not claim otherwise.

Three instruments

note — what actually happened here

python scripts/canopus.py note <slug> --value "<one sentence>" \
    --approval-sha <sha> --contract tests/contract/<date>-<slug>/ \
    --plan-digest sha256:<...> --scrutinize-plan "<step 4 findings, all applied>" \
    --scrutinize-built "<step 6 findings, all applied>" \
    --undo "revert <sha>, restore <baseline>, re-run <cmd>"

Every flag is required by the record’s schema, which refuses an incomplete note rather than writing a half-formed one, so the archive cannot fill with entries that look complete and say nothing. A slice whose contract has been retired into the ordinary suite adds --retired-sha and --promoted-to, and the schema refuses the first without the second: a retirement pointing nowhere cannot be told apart from a contract that was simply dropped.

check — did the work honour the approval

python scripts/canopus.py check --range origin/main..HEAD

scripts/canopus_check.py reads the records back over the repository they are committed to, in four clauses:

Clause The question
C1 Did the contract move between its approval and the end of its life?
C2 Does HEAD descend from the approval commit?
C3 Was the contract genuinely RED at the approval sha, run in a worktree checked out there?
C4 Is the target green at HEAD and does the junit report show it RAN?

C4 is written that way because collected is not run, and an all-skipped file exits 0.

No clause reads a timestamp. GIT_COMMITTER_DATE and GIT_AUTHOR_DATE are environment variables, and on 2026-08-06 two of them put an implementation commit nine hours before the approval it descends from. Order comes from lineage.

check is a passthrough to canopus_check.py, which is the module CI runs directly, so the local reading and the CI reading are the same reading.

probe — is this test worth anything

python scripts/canopus.py probe tests/contract/<date>-<slug>/
python scripts/canopus.py probe --after-build tests/test_<subject>.py

probe measures whether a contract’s redness means anything. It null-stubs the missing modules and runs the contract twice, each stub carrying different values, so a test that never FAILS under either run is vacuous. Passing, skipping and erroring all leave a test unproved; only a failure shows it read the value. It also runs three deliberately wrong implementations and prints what each one got past. The result goes in front of the operator before they approve.

A skip-family marker (skip, skipif, xfail) that states no reason refuses the contract, whether it sits on a test, on a class, or on pytestmark. A pytest.skip() inside a test body and a module-scope pytest.importorskip are not read, so they remain the reviewer’s to catch.

probe --after-build asks the same question at the other end of a slice’s life. Once a contract is retired into the ordinary suite, nothing re-asks whether the tests guarding that code would notice if it were wrong; this flag puts the same three wrong implementations in front of shipped code and names every test that stayed green under all of them. The page names which modules were actually replaced and which were not, because a name that survived a module nothing stood in for is not evidence about it. Skipped tests are named separately: a test that never ran neither survived a wrong implementation nor went red under one.

Honest limits

check reports; it does not block. A CI step in the sovereignty guards job runs canopus_check.py on every push. It reports a broken clause. It does not refuse one, because enforce_admins is false on the only push path in use. Nothing on the machine prevents a test contract from being edited by whoever is implementing against it. Do not describe this as prevention.

probe --after-build is uncalibrated. It reports and never refuses: exit 0 whether it names twenty survivors or none, exit 1 only when no reading could be made at all. Nobody has calibrated that reading, so it is not wired into a gate and must not be.

Why seven, and not thirteen

An earlier Canopus had thirteen numbered moments and a large amount of custom machinery to enforce them: file locks, freeze manifests, approval gates written in code. Measurement retired most of it. 93 percent of that hand-built prevention surface could be defeated by a single shell command, which meant it stopped the careless hand and not the determined one, while charging every honest change a heavy toll.

The thirteen were four acts of ceremony around two things git already provides. The freeze is a commit. The separation is a dispatch. Everything the retired agenda numbered around those two facts was removed on 2026-08-06.

Thirteen became seven. Enforcement now rests on tools that already exist and are already trusted: version control for the freeze, a separate reviewer for the separation, and the ordinary test suite for the verdict. Less code to maintain, less ceremony per change, and the guarantees that were real are still there.

Four rules the measurements paid for

1. Order comes from lineage, never from clocks. A timestamp is an environment variable anyone can set.

2. The verdict is the suite actually run, not a shortcut. A deletion experiment produced 586 failing tests across 28 files while all three fast shortcuts reported success.

3. Every check is broken on purpose before it is trusted. An uncalibrated check is worse than no check, because it manufactures confidence.

4. Prove a safeguard is alive, never infer it from silence. 24 broad error handlers were found wrapped around calls into this system, 10 of them invisibly. Silence is not evidence.

A worked example, end to end

The ask: “I want a Monday morning digest of which suppliers slipped last week.”

Step 1. Value: a weekly supplier slippage digest, delivered before the Monday leadership call, so a slip is discussed in the week it happens rather than at quarter end.

Step 2. Scope: reads the supplier records and the delivery log. Sends to one channel. Explicitly out of scope: chasing the supplier, changing any record, forecasting.

Step 3. Criteria, one test each. When a delivery date passes with no receipt logged, the digest shall name that supplier. When nothing slipped, the digest shall say so in one line rather than staying silent. When the delivery log cannot be read, the digest shall report the failure and shall not send an empty all clear. The input partition covers the awkward cases: a supplier with no deliveries at all, a delivery logged late but on time, a date exactly on the boundary.

Step 4. The operator reads the plan, sees that the third criterion is the one that saves them from a false all clear, and commits.

Step 5. A separate builder implements it. A separate reviewer checks it.

Step 6. The review finds that an unreadable log produced an empty digest that read as good news. That is a code-origin finding against a criterion that already existed, so it is fixed, and the test that always covered it now genuinely bites.

Step 7. Ships. The record names the undo in one line: turn off the schedule, restore the previous version, run the test suite.

Why an executive should care

Canopus property What it means for the business
Two human approvals, never delegated Nothing enters the system you depend on without you agreeing twice, once to the intent and once to the result.
Definition of done fixed in advance No moving goalposts, in either direction. You cannot be sold a smaller success than the one you approved.
Builder separated from planner The self-assessment problem is removed by construction rather than by good intentions.
Test strength measured, not assumed A green light means something. That is the entire point of the standard.
Undo written before it is needed Recovery is a one-line instruction on file, not an improvisation under pressure.
One record per change A complete, readable audit trail of every meaningful change, with the reasoning attached.

Where to go next