What it means
The neighbouring claim — GitHub milestones and issues are generated from the canonical
model — is about what scripts/github-sync renders and what it
refuses. It says nothing about whether GitHub has actually received any of it. This claim
is the other half: that the two agree now, and that a build fails when they stop agreeing.
How it works
scripts/ci/github-check reads the remote and classifies every record. It applies two
tolerances rather than one.
Refused outright, from the first run:
| state | what it means |
|---|---|
behind | a canonical record moved and nobody projected it |
edited | a person rewrote a generated region |
conflict | a person rewrote it and the record has moved since |
unmanaged | a remote issue claims a canonical id this repository does not have |
state | the remote's open/closed disagrees with the derived status |
milestone | the remote's milestone assignment disagrees |
Four of those states are what an apply writes: behind, state, milestone, and the
closed the adapter reports beside state when GitHub is the closed side. They are
reported as PENDING and not refused in two cases only: for the records a head changed
since its merge base, and on the trunk for the records that changed in a landing younger
than a declared window. That is a claim of its own,
projected-record-edit-is-landable. On every other
record they are refused, and past the window the trunk is refused for all of them.
Ratcheted against .ai/repo/ci/github-drift-baseline.txt:
| state | what it means |
|---|---|
missing | a canonical record with no counterpart on GitHub |
adopt | a counterpart carrying no identity marker |
The baseline may fall and may never rise. Writing it is a deliberate act, in the same idiom
.ai/repo/claim-proof-baseline.txt already uses
for the rules and claims that name nothing a machine can run.
A gate that cannot reach the remote — no gh, no token, no permission — exits 12 and names
which. It never exits clean.
How to see it
scripts/ci/github-check # 0 in sync, 10 findings, 12 the gate cannot run
scripts/ci/github-check --write-baseline # record today's backlog as the ceiling, deliberately
scripts/github-sync --plan # offline: what an apply would create or changeWhy it is a separate claim
Because the first claim was true and green for five days while the thing it describes had stopped happening.
--check has exited 11 on drift since the adapter was written. Nothing called it:
scripts/ci/core-check ran --plan and --render, which prove a projection can be
produced. The projection was applied once, on 2026-09-04, and then the model grew from
19 records to over 200 while GitHub kept the 19. Measured at 867f3a9: 200 drift findings.
All eight projected milestones were in sync throughout, because milestones are few and were touched by hand — so the surface a person looks at was the surface that still agreed. That is what a claim citing a script that exists, rather than a gate that runs, buys you.
What it does not cover
This gate applies nothing; it proves agreement. Since 2026-10-12 the apply is a consequence
of the landing (.github/workflows/github.yml,
project.the-projection-is-applied-at-landing), and a person's scripts/github-sync --apply
is the fallback. The gate's baseline is still lowered by a person. The baseline is a ceiling, and nothing lowers it by itself: on
2026-10-10 it read 192 missing while GitHub lacked 3 records, so a new record counts as
missing and passes until somebody writes the baseline down.
Nor does the gate judge the model. It says which of the two sides moved, not whether the
canonical records describe the right work — that is the plan's business. And it decides
nothing it cannot measure: with no gh, no token or no permission it exits 12 naming
which, rather than a green build that read nothing.
Evidence
test/cases/97_github_gate.sh drives every half over a fixture repository with a fixture
remote, offline: a record whose canonical text moved fails outright, a record added to the
model and never projected breaks the ratchet, a backlog at its baseline passes, an edited
region and an unmanaged remote issue are refused, and a gate with no baseline refuses to
guess one. test/cases/45_github_projection.sh proves the six states it reads.
test/cases/1038_a_head_is_judged_against_what_it_changes.sh proves which drift is pending
and that everything else is still refused.
The rule is project.github-projection-gated@1.