Checkwright v0.23.0
2026-08-14
Checkwright is the verification layer under agent orchestration, and this release is the one where most of the battery stops being shell. Thirty-seven of its gates move to the compiled binary, which gives this upgrade exactly one hard requirement: re-run the installer immediately after you replace the kit directories. It batches eleven iterations behind a deferred tag, so it is wide.
In brief
- Replace the kit directories and re-run the installer in the same sitting. Not optional and not conditional this time — the previous release could scope the instruction to one of two narrow CI gates, and this one cannot. Sync without reinstalling and most of your battery refuses to certify rather than quietly passing.
- Most of the battery is now a compiled binary. Two of its members dispatched that way at the last release; forty-two do now, and thirty-seven of those had their shell implementation deleted in the same commit that landed the replacement. Same checks, same verdicts, different thing running.
- Twenty-six of them are declared new or changed. Five of those are genuinely new, a handful got stricter, and the rest moved substrate under an unchanged rule — the list below says which is which for every one, because “your battery went red” has a very different remedy in each case.
- Nothing you have configured needs to move. Nine knobs are added and none is renamed or removed.
- One shared library fixed a bug that was silently widening what several gates scanned. If a check has been quiet about a surface you expected it to cover, this release is where that stops.
Tightened gates
This section is the release’s allowed-red set: the gates a clean upgrade may turn red, each with the intent behind the move. That includes gates whose rule did not change but whose implementation moved to the compiled binary — a registered member resolving to a descriptor with no binary present exits 2, so it can red on an upgrade and belongs here. Each bullet says plainly which kind it is.
check-gap-inbox-neutrality— a new gate. The committed gap inbox records observations, not verdicts: every bullet below the contract header must be a dated line with non-empty prose, and none may open by declaring the finding a recurrence of a named slug. The intent is that whether a gap re-occurred is the drain’s judgment to make, and a capture surface that can arrive pre-concluded hands that judgment to whoever wrote fastest. An absent inbox is clean, so it costs nothing until you file one. Clear a red by fixing the bullet’s shape or rephrasing the verdict out of its opening. Not auto-registered.check-install-disposition— a new gate, and the one most likely to reach you if you author your own gate scripts. Every file under a kit’schecks/must carry exactly one# install:line naming one of three dispositions, every auto-registering member must appear in its kit’s smoke install roster, and the installer’s recipe may carry no literal gate name of its own. The intent is that whether a gate reaches an adopter was a fact derivable from the gate itself but was hand-maintained somewhere else, which is how an auto-registering gate the installer never learned about shipped unregistered and silent. Clear a red by adding the header, adding the member to the smoke roster, or deleting the hardcoded name from the recipe. This one auto-registers, so it runs from the first install.check-gate-output— stricter, and only where you vendor the crate. It has always required every registered gate’s source to carry a machine-keyable clean emission and ahelp:remedy line. What changed is where it looks when a member dispatches to a compiled binary: the corpus follows the substrate to the Rust module rather than grepping the descriptor, which structurally cannot hold those strings. The intent is that checking an output contract against a corpus that cannot contain it is not a weaker check but a vacuous one — it finds nothing and prints clean. Clear a red by giving the module both strings. Dormant unless the native crate is present, which a normal vendored consumer never has.check-scratch-citation— a new gate. No permanent surface may carry a retrieval pointer — a markdown link target, or a bare path directly after a colon — into the boundary-truncated scratch set; a bare mention in prose is fine. The intent is that a citation into a file truncated by contract at every boundary reset is a pointer to nothing the moment the reset runs, and the fix is to inline the finding rather than fight the file’s lifetime. Clear a red by inlining the citation, or tag the line exempt if it must quote a dead one verbatim. Not auto-registered, and inert besides unless your permanent-surface globs contain such a pointer.check-doctrine-registration— stricter. It has held your agent file’s doctrine digest in lockstep with the doctrine’s own rules; what changed is that every methodology rule must now carry exactly one non-empty digest trailer, because that trailer is the surface the installer derives its bullet from. Previously the bullets were a hand-maintained list checked only by name agreement, and it had already skewed — one rule reached the doctrine and the agent file but never the installer, so every fresh install vendored a digest one rule short. The intent is that a hand-maintained roster of what a generated thing contains is exactly the derivation the doctrine itself bans. Clear a red by adding the trailer, or declaring a trim. Live in any tree that installed the doctrine.check-task-conservation— stricter, narrowly and probably invisibly. Live queue slugs must survive into the working tree and a done entry must be a bare slug line; none of that moved. What changed is that agitsubprocess which fails to spawn is no longer folded into the same clean branch as git running and reporting no repository. The intent is that a successful spawn call says the child started, never that it answered. Genuine no-repository and no-baseline cases are unaffected and still pass; there is nothing to fix in your queue, because it fires only ifgitcannot be executed in a tree that has a.git.check-crate-arms— a new gate, and it cannot fire in your tree. It runs the native crate’s lint and test arms as a battery member, both arms even when the first fails. The intent is that a near-identical CI command that never ran at commit time is one flag narrower than not gating at all — and a commit did land with both red, silently dropping a unit test another gate depended on. Its disposition is never-install, and its subject is not vendored, so it would report clean even if you added the name by hand.check-measured-claim— a new gate, and the first written directly against the compiled substrate rather than ported into it. Ameasured:marker above a prose claim binds that claim to an oracle you configure; it reds when the oracle disagrees, exits 2 on an unknown key or malformed marker, and reds when the marker’s number does not appear in the sentence it is bound to. The intent is that a sibling’s ban on bare cardinals cannot see an extent claim — “the specs came back clean” carries no numeral to trigger a scanner at all — so the costliest claims to leave stale are the ones nothing watches unless the author marks them. Silent until you set an oracle and write a marker.check-install-claim— stricter by its move to the compiled substrate. The rule is unchanged: one governed doc declares the primary install transport, and the earliest matching line in any install section must be that transport. What changed is that the regexes you supply now compile through a strict POSIX matcher instead of being handed toawk. A pattern outside POSIX grammar used to be tolerated silently, degrading the match with no visible sign; it now exits 2 naming the pattern and the knob. The intent is that a gate whose own configured input can quietly misbehave produces the false clean it exists to prevent. Skips clean until you configure it.check-payload-claim— stricter, and yes, it appeared in the last note too. It shipped there as a new gate; it earns a line again because its implementation moved to the compiled substrate in this window and its failure surface moved with it. The rule is unchanged: one governed doc declares what a vendored payload discloses, and no other doc’s line may match a different disclosure class. What changed is the same strict-matcher move as its sibling above. The intent is that this gate is the only surface watching disclosure-class drift, so a pattern that can silently under-match is worse than no gate at all. Skips clean until you configure it.check-manifest-temporal— stricter, and this one is live for everyone. The ban on temporal narration in governed manifest prose is unchanged; what changed is the same strict-matcher move, and here it has a worked example. A marker knob written with a GNU-awk word-boundary escape used to make awk warn to stderr, degrade the match, and report clean over prose that did contain the banned marker. It now fails at commit time naming the pattern and the knob. The intent is that a lexical ban is worth exactly what its match step is worth — once the matcher can misread your own pattern, green stops meaning “no marker” and starts meaning “no marker this quirk still caught”. Unlike its two siblings this ships a non-empty default marker list, so it fires in any tree with governed manifest docs.check-stage-skill-coverage— moved to the compiled binary; the rule did not change. Every configured stage still needs its skill file, and every skill invoking the stage-entry script still has to name a live stage. Parity was proven across the cohort’s comparison runs before the shell original was deleted. Nothing to reconcile in your content — reinstall so the binary is present. Not auto-registered.check-lifecycle-registration— moved to the compiled binary; the rule did not change. Your agent file’s lifecycle marker block must still byte-match the block regenerated from the live stage machine, with the same fail-closed cases and the same remedy. Clear a stale block the way you always did, by re-running the lifecycle installer. Not auto-registered, and it cannot fire unless you have opted into the stage machine, since without it there is no block to check.check-skill-binding— moved to the compiled binary; the rule did not change. A binding shim must name an existing template and bind exactly that template’s slot set. Reinstall so the binary is present; nothing else to reconcile. Not auto-registered, and inert until you author binding shims of your own.check-merge-attrs— moved to the compiled binary, and this port fixed something. The stated invariant is unchanged — set parity between the derived iteration-scoped supersede set and the merge attributes declaring it. What changed is that the old form computed its set difference by piping sorted streams throughcomm, which depends on the invoking shell’s collation agreeing withcomm’s sortedness assumption; the compiled form implements the difference directly. The intent is that a parity check whose own correctness depends on locale is not reliably enforcing its invariant in the environments you control least. A smuggled attribute could previously survive on a machine whose locale made that pipeline miscompute. Clear a red by regenerating the block, or removing a hand-added attribute outside the supersede set.check-stage-evidence— moved to the compiled binary, and this port closed a fail-closed gap. The assertions are unchanged: well-formed stamps, iterations matching the header, four fields per line, no session id shared by different stages within one iteration. What changed is that the old form’s stamp-parsing subprocess had no fail-closed guard, unlike the identical call in its sibling — so a failure there would have read as “no stamps” rather than refusing. The compiled form has no subprocess at that site at all, so the failure mode is gone by construction rather than patched. The intent is that a fail-closed doctrine applied everywhere except one call site is an exception nobody decided on. Clear a red by running the stage skill, which stamps first.check-lesson-disposition— moved to the compiled binary; the rule did not change. A lesson present at HEAD and absent from the worktree must leave a well-formed disposition stamp. Reinstall so the binary is present. Not auto-registered, and inert until you clear a lesson.check-survey-record— moved to the compiled binary; the rule did not change, and it was announced when it landed at the last release. It is named here because its substrate moved in this window, which is what can red it on an upgrade — not because the four required keys or the commit-resolving revision changed. Reinstall so the binary is present. Still inert until you file a survey.check-shim-restatement— moved to the compiled binary; the rule did not change. A binding shim may not share a long verbatim run with the deduplication corpus. It runs on every commit rather than skipping, but only flags when a shim exists and restates the corpus verbatim past the configured floor. Clear a red by citing the surface instead of restating it.check-comment-tier— moved to the compiled binary, and the only rule change is a loosening. Every full-line comment on a governed source must still be a directive, a bounded reason, an exemption tag, or a positional line. The gate’s own blessed-keyword vocabulary gained the new install-disposition header so that header does not self-red. Its trigger declaration was also widened to name the descriptor and Rust file types explicitly — a catch-up to a corpus it already scanned, not a new one. Clear a red by leading with a directive, deleting restated prose, or tagging the line exempt. Auto-registered and runs on every commit.check-spec-pointer— moved to the compiled binary; the rule did not change, and its version-marker handling was announced at the last release. Named here for the substrate move alone. Clear a red as before: fix or track the target path, or correct the heading.check-todo-task-liveness— moved to the compiled binary; the rule did not change. Every task marker on a governed source must resolve to a live queue task, and a done-tier slug reds like an absent one. Its trigger declaration was corrected to name the tier its corpus actually takes. Clear a red by resolving, removing, or reopening the task the marker names. Auto-registered.check-deprecation-task— moved to the compiled binary, carrying one narrow repair worth knowing. The rule is unchanged: a deprecation marker must bind a task slug resolving to a live queue task. What changed is that an unbound-marker finding used to report the offending marker as an empty string, because reading a tab-separated record collapsed an empty middle field. The intent is that a finding which mis-names the very marker it points you at defeats its only job. This changes what the finding says, not which cases red. Dormant until you configure a marker roster.check-settings-paths— a new gate, and read its dormancy carefully because it does not skip the way most do. Every literal repo-relative shell path granted in your committed permission allow-list must resolve to a real file; a pattern-shaped grant is out of scope but does not exempt a literal twin of itself listed beside it. The intent is that porting a gate deletes its shell file and strands any grant still naming that path — inert, but forcing a manual re-verification of the allow-list every time, and this project had already accumulated dead entries before the gate existed. It has no absent-surface skip: a tree with no settings file at all gets a hard refusal at exit 2, by design and matching its sibling. A tree that completed the documented install has the file and passes clean with nothing to check. Clear a red by repointing or dropping the stale grant.check-spec-dod-singleton— moved to the compiled binary; the rule did not change. No canonical spec may carry the configured completion heading more than once, and under the strict mode zero of them reds too. Named here for the substrate move. The shared corpus helper it calls was fixed in the same commit, but both the old and new forms default to a root that never exercised the bug. Reinstall so the binary is present — a binary placed by an earlier install predates this subcommand and does not cover it.check-spec-derivable-section— moved to the compiled binary; the rule did not change. A canonical-spec section under a code-derivable heading may not exceed the configured fenced-code density, and one that sheds to a one-line pointer is exempt; a section exactly at budget still passes. Named here for the substrate move, with the same reinstall requirement as the entry above.
Renamed knobs
None. Nine knobs are added — the measured-claim oracle and its surface globs, the install-transport and payload-claim derivations, an extra temporal-marker union, the crate’s build-output location, the assumed-program floor, an additive prune-directory knob, and the permanent-surface globs the scratch-citation rule reads — and not one existing knob is renamed or removed, so nothing you have set needs to move. Two defaults change what they point at, which is a behavior change rather than a rename and is stated below.
Behavior changes
- The installer places a binary that thirty-seven more of the gates now need. At the last release exactly two of them resolved to a compiled descriptor; forty-two do now, and thirty-seven of the new ones had their shell implementation deleted in the same commit that landed the descriptor. The resolution rule is unchanged and it is strict: a registered member resolving to a descriptor with no binary present exits 2 — a refusal to certify, never a quiet pass. Reconcile: run the installer immediately after replacing the kit directories, unconditionally. This is not the narrow two-gate case the last release described; the cohort includes gates a standard roster registers as a matter of course, so an upgrade that syncs and stops will find most of its battery refusing. The old shell files are gone and there is nothing else for those names to resolve to.
doctrine-kit/bin/install-doctrine.sh— the installed digest is derived from the doctrine’s own rule text rather than a hand-maintained list. Each rule now carries a digest trailer and the installer emits one bullet per rule read from it, so the bullet roster is the rule roster by construction. The list had already skewed one rule short. Reconcile: re-run the doctrine installer, or the main installer, after the sync. Until you do, an already-installed block is missing that rule and the paired gate asserts parity against the full set.canon-kit/lib/spec.sh— a scan root spelled with a parent component now prunes vendored kit roots, where before it pruned nothing at all. The comparison was against unnormalised path strings, so such a root never matched its own candidates and silently widened the corpus every caller scanned. Reconcile: nothing for a standard battery run, which roots at the current directory. If your own tooling calls the spec finders against a relative root with a parent component, expect a narrower corpus — the one always intended.canon-kit/lib/spec.sh— the derived manifest set’s README finder prunes vendored kit roots too. It pruned canonical specs and not READMEs, so any consumer on the derived default swept every vendored kit’s own README into their governed manifest set. The measured consequence was a red on a bare install tree with no adopter action at all, on a kit README’s own cross-kit link. Reconcile: nothing — this removes false reds rather than creating them.gate-sdk/lib/gate.sh— the default prune set gains agent worktrees. A live isolated-agent worktree is a second full copy of your repository, and every tree-walking gate descended into it. Reconcile: nothing if you are on the default and have no directory of that name you wanted scanned. A new additive knob extends the prune set without copying the whole default.guard-kit/templates/bash-guard.sh— every rule’s lexical view comes from one context-aware normalizer. Several independently written stripping expressions collapse onto a single primitive that masks quoted and heredoc regions before any rule matches, and existing verdicts were re-derived rather than assumed. Reconcile: nothing — this is hook-time session friction, not a battery red. A command shape that previously slipped through because of a quoting bug in one rule’s private stripping may now correctly steer or block.lifecycle-kit/bin/— the tools validate argument shape, and the stage writer gains a rename verb. A help flag prints usage at exit 0 where a tool used to answer with a domain error, an unrecognized leading dash is a usage error at exit 2, and a bare double dash ends option processing. Separately, renaming an iteration now has one sanctioned command that rewrites the queue header and every stamp atomically and refuses rather than half-landing. Reconcile: if you wrapped one of these tools and depended on its old error text for a flag-shaped argument, you now get usage text and exit 2.gate-sdk/bin/port-blockers.sh— the scanner is repaired and the tool gains a grouping arm. Its tokenizer read a here-string as a heredoc and stopped scanning the rest of the file, so both arms under-reported for the tool’s whole life; a second fault left a substitution frame unpopped inside double brackets. The default arm’s output moves deliberately as a result. Reconcile: nothing — this is advisory tooling for a kit author sequencing their own port, and its own contract states that nothing parses its output. If you were diffing that output for your own tooling, re-baseline it.delegation-kit/templates/statusline-usage.sh— the status line can carry queue section counts. A new counting tool emits one count per configured task section and the template appends a compact group when it is present and succeeds; absent or failing, the group simply does not render. Reconcile: nothing, unless you parse that template’s output.
Upgrading
Replace the vendored kit directories wholesale at v0.23.0, then re-run init
before anything else. That order is not advisory this release: thirty-seven of
the gates lost their shell implementation in this window, and a registered member
resolving to a descriptor with no binary present refuses to certify rather than
skipping. Re-running the installer is also what refreshes the doctrine digest
block, which a tightened gate now holds against the full rule set. Then
regenerate the generated artifacts — the pre-commit hook and the graph projection
— and run the full battery.
The allowed reds. Most of the list above cannot red a synced-and-reinstalled
tree at all: the ports assert exactly what they asserted before, and the five new
ones are inert until you register them or configure their input. The ones that
can reach you are these. check-install-disposition reds on a check file with no
install header, a mismatched auto-register roster, or a hardcoded gate name in
your recipe — it auto-registers, so it arrives on its own.
check-doctrine-registration reds on a methodology rule with no digest trailer,
and on an installed block still missing the rule the old hand-maintained list
dropped. check-manifest-temporal reds where a custom marker pattern is not
strict POSIX, and it ships a non-empty default so it fires without configuration.
check-comment-tier and check-todo-task-liveness auto-register and run every
commit, though neither rule moved. check-settings-paths is the one to read
twice: it refuses at exit 2 on a tree with no settings file at all rather than
skipping, and reds on a literal shell path in your allow-list that no longer
resolves — which a port that deleted a shell file is exactly how you get.
check-install-claim and check-payload-claim red only where you configured
them, and then only on a pattern outside strict POSIX grammar.
check-merge-attrs and check-stage-evidence can newly red where a locale or a
silent subprocess failure previously produced a false clean. check-gate-output
and check-crate-arms need the native crate in your tree and cannot fire without
it. Separately, and not a gate: the shared spec-finder fixes above change which
files several checks scan, so a check that was quiet about a surface may now have
something to say about it.
The behavior changes above are declared for reading, not a mechanical scan. If a gate reds that this note does not name, the upgrade smoke was supposed to catch it first — open an issue, because that is a defect in the release rather than work for you.