Checkwright v0.26.0

2026-09-26

Checkwright is the verification layer under agent orchestration, and this release adds a one-step CI action and a demo command, and moves every kit’s configuration onto knob files. It batches every change declared since v0.25.0, so the sections below are long; the brief says what to do first.

In brief

  • You must act on your configuration. Every kit now reads its settings from a .knobs file, and a left-behind <stem>-config.sh in your gates directory refuses the gates that read it. Rewrite what you set into the matching .knobs file and delete the shell file; a copy init seeded that you never edited is simply deleted.
  • Some knobs were renamed or removed. Renamed knobs below lists each one with what to change in your config.
  • CI now takes one step. init seeds a GitHub workflow that runs the battery at the release you installed and marks each red on the pull request; an existing install gets it on update.
  • A new demo command runs the adoption arc in a throwaway repository of its own: install, a green battery, and a task marked done with no evidence, caught and withdrawn. Your tree is never touched.
  • Many gates are new or stricter. Expect reds on your first battery after upgrading; each is named under Tightened gates with what clears it.

Tightened gates

  • check-gate-exemption-tasks — the port oracle’s exclusion corpus was narrower than its own directive; widened, so the gate can now red on a # port-until: hold it previously could not see. It is zero-config, so it reaches every vendored tree. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-survey-record — every word-bounded 7–40-character lowercase-hex run carrying a letter inside a survey record’s corpus, oracle, finding or inferred field must resolve to a real git object, catching a minted identifier that fakes a citation’s precision; and every block must now carry a non-empty - inferred: line (each claim no command established, or the literal none) in place of the retired - edges: line. A survey record mid-iteration at upgrade reds until its blocks are re-keyed to corpus/oracle/rev/finding/inferred, or until the next scope boundary truncates it. It also reads lifecycle-kit’s knobs from a knob file now, so a left-behind <gates-dir>/lifecycle-config.sh refuses it at exit 2 — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-evidence-baseline — gains a fifth arm, flip causation: a row held fail/ignore whose scenario was pass in the baseline at the iteration-start commit must carry reproduces-at=<rev> naming that commit or an ancestor of it, and every such token must resolve and sit outside the iteration. It takes the state file as a new third positional (default EVIDENCE_KIT_STATE_FILE) and is disarmed, saying so on its clean line, wherever no iteration-start commit resolves — a shallow CI clone included. If a held row of yours flipped this iteration, reproduce its red at the iteration-start commit and add the token, or fix the regression; a fixture or wrapper passing only two positionals now reads your state file. It also gains a fourth arm: every configured suite must carry at least one baseline row. It also reads evidence-kit’s knobs from a knob file now, so a left-behind <gates-dir>/evidence-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-action-permissions — new: a workflow job that consumes the GitHub token must declare its scopes rather than inherit the repository default. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-install-claim and check-payload-claim — the gates’ own rules stand still, but their input moved: the manifest finder’s two configured branches now prune, so if you set CANON_KIT_MANIFEST_FILES or CANON_KIT_PROSE_SURFACE_GLOBS and the doc carrying your primary-install-transport or payload-disclosure declaration was reached through a glob crossing a GATE_SDK_PRUNE_DIRS directory, it leaves the governed-doc set and the gate reds on a zero count where it was green. Both are the only members of this corpus that red on a count rather than on a violation, so no other reader of it can move this way. Remedy: name that doc’s path in the knob outright rather than reaching it through a **.

  • check-docs-liquid-parse — new in site-kit, on-surface, with the knob SITE_KIT_LIQUID_PARSER defaulting to a ruby Liquid parse; where you register it, it needs ruby with the liquid gem. Every Liquid-processed docs file must parse, so a page carrying an unbalanced output delimiter or an unclosed tag reds — wrap text a page means literally in a raw block, or remove the token.

  • check-action-job-ref — new, opt-in: a backticked span matching one of your GATE_SDK_JOB_REF_PATTERNS must name a job some workflow in the tree defines. The knob defaults empty, so the gate is a clean skip until you list the job-name patterns your docs cite.

  • check-action-run-shell — resolves an absent shell: key’s dialect from runs-on instead of assuming bash (a Windows runner defaults to pwsh), and refuses a defaults.run subtree whose dialect it cannot resolve. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-template-registry-parity — the population predicate widens so a native-dispatch registry stays gated after its sibling shell directory is deleted. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-path-dialect — new: a path value reaching a dialect-sensitive consumer (a shell cd, or a Rust path API) must be cleared for that substrate at the occurrence. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-gate-substrate-parity — assertion G widens to the union of the declaration set and the tracked shell tree, so a malformed port declaration on an unregistered shell script is caught rather than counted as owed. It also reads canon-kit’s, evidence-kit’s, lifecycle-kit’s, and queue-kit’s knobs from knob files now, so a left-behind <gates-dir>/canon-config.sh, evidence-config.sh, lifecycle-config.sh, or queue-config.sh refuses it at exit 2 — rewrite what you set into the matching .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-stage-skill-coverage — gains a third direction: every configured stage’s executed surface must cite the stage-journal closing step. It also reads lifecycle-kit’s knobs from a knob file now, so a left-behind <gates-dir>/lifecycle-config.sh refuses it at exit 2 — rewrite what you set into the .knobs file and delete the shell config.

  • check-skill-binding — the lead template gains an open-authorization-channel binding slot, and an unbound slot reds a vendoring consumer until it is bound. It also reads lifecycle-kit’s knobs from a knob file now, so a left-behind <gates-dir>/lifecycle-config.sh refuses it at exit 2 — rewrite what you set into the .knobs file and delete the shell config.

  • check-portability-floor — new: no file on the configured adopter-install-path corpus may use a banned non-portable shell construct without a # portability-declared: valve; absent config disables it. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-amendment-retired-spelling — new: every amendment carries a ## Retired spellings block, and each declared spelling survives only at a path the amendment’s update-target roster names. It also reads canon-kit’s knobs from a knob file now, so a left-behind <gates-dir>/canon-config.sh refuses it at exit 2 — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-doctrine-registration — gains assertion F: an installed digest bullet’s text must match its doctrine rule’s Digest: trailer verbatim. It also reads doctrine-kit’s knobs from a knob file now, so a left-behind <gates-dir>/doctrine-config.sh refuses it at exit 2 — rewrite what you set into the .knobs file and delete the shell config.

  • check-tag-lead-line — the class table gains [observed-by: <producer>] as a governed tag. It also reads queue-kit’s knobs from a knob file now, so a left-behind <gates-dir>/queue-config.sh refuses it at exit 2 — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-deferred-board-tags — new, opt-in: every top-level deferred queue entry carries exactly one valid [cost:] and one valid [surface:] tag, and no active entry carries either. It also reads queue-kit’s knobs from a knob file now, so a left-behind <gates-dir>/queue-config.sh refuses it at exit 2 — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-surface-ratchet — new: every governed always-loaded surface is held at or below its committed line-count ceiling. It also reads context-kit’s knobs from a knob file now, so a left-behind <gates-dir>/context-config.sh refuses it at exit 2 — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-brevity — governs a configurable section set and every top-level bullet, not only bold-named ones. It also reads context-kit’s knobs from a knob file now, so a left-behind <gates-dir>/context-config.sh refuses it at exit 2 — rewrite what you set into the .knobs file and delete the shell config.

  • check-docs-cmd — gains assertion C: a path cited in an inline code span in the governed doc set reds when the repository has retired it (deleted in reachable history, or deleted in the index) and it resolves under neither the doc’s directory, the repo root nor any kit root, unless check-manifest-temporal’s valves mark the site as history. A doc citing a script your tree deleted now reds until the citation is re-pointed or the line carries <!-- manifest-temporal-exempt: <reason> -->. Assertion C also reads a bare amendment basename in a code span (one segment matching CANON_KIT_AMENDMENT_GLOB, SPEC-x.md by default) and reds it when no tracked file carries that basename and a deleted one did; cite the owning SPEC section in place of a merged amendment’s name, or valve the line as history. It also reads canon-kit’s knobs from a knob file now, so a left-behind <gates-dir>/canon-config.sh refuses it at exit 2 — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-agent-tier-explicit — reads delegation-kit’s knobs from a knob file now, so a left-behind <gates-dir>/delegation-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-amendment-queue — reads canon-kit’s knobs from a knob file now, so a left-behind <gates-dir>/canon-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-amendment-update-target — reads canon-kit’s knobs from a knob file now, so a left-behind <gates-dir>/canon-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-close-surfaces — reads lifecycle-kit’s knobs from a knob file now, so a left-behind <gates-dir>/lifecycle-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-comment-tier — reads canon-kit’s knobs from a knob file now, so a left-behind <gates-dir>/canon-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-deprecation-task — reads canon-kit’s knobs from a knob file now, so a left-behind <gates-dir>/canon-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-docs-cname-parity — reads site-kit’s knobs from a knob file now, so a left-behind <gates-dir>/site-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-docs-link-convention — reads canon-kit’s knobs from a knob file now, so a left-behind <gates-dir>/canon-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config.

  • check-docs-render-fidelity — reads site-kit’s knobs from a knob file now, so a left-behind <gates-dir>/site-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-enforcement-fresh — reads canon-kit’s, context-kit’s, drift-kit’s, evidence-kit’s, and lifecycle-kit’s knobs from knob files now, so a left-behind <gates-dir>/canon-config.sh, context-config.sh, drift-config.sh, evidence-config.sh, or lifecycle-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the matching .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-footprint-fresh — reads context-kit’s knobs from a knob file now, so a left-behind <gates-dir>/context-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. Its descriptor also couples */SPEC.md, the kit roster its emitter derives, so a kit’s SPEC.md arriving, leaving or changing now fires it: regenerate your hooks.

  • check-gap-inbox-neutrality — reads lifecycle-kit’s knobs from a knob file now, so a left-behind <gates-dir>/lifecycle-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-gate-tamper — reads delegation-kit’s knobs from a knob file now, so a left-behind <gates-dir>/delegation-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-graph — reads canon-kit’s, evidence-kit’s, lifecycle-kit’s, and queue-kit’s knobs from knob files now, so a left-behind <gates-dir>/canon-config.sh, evidence-config.sh, lifecycle-config.sh, or queue-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the matching .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config. It also reds a knob: token in a descriptor’s couples= or trigger=, or in an amendment’s # graph: manifest, that names a word-list knob (a row declared .words(), such as GATE_SDK_KIT_DIRS): such a token is inert when the knob is unset and refused when it is set — drop the token and keep the descriptor’s literal globs.

  • check-install-claim — reads canon-kit’s knobs from a knob file now, so a left-behind <gates-dir>/canon-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-knob-citation — reads canon-kit’s knobs from a knob file now, so a left-behind <gates-dir>/canon-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-knob-default-coupling — reads canon-kit’s knobs from a knob file now, so a left-behind <gates-dir>/canon-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-lesson-disposition — reads lifecycle-kit’s knobs from a knob file now, so a left-behind <gates-dir>/lifecycle-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-lifecycle-registration — reads lifecycle-kit’s knobs from a knob file now, so a left-behind <gates-dir>/lifecycle-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-manifest-count — reads canon-kit’s knobs from a knob file now, so a left-behind <gates-dir>/canon-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-manifest-temporal — reads canon-kit’s knobs from a knob file now, so a left-behind <gates-dir>/canon-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-md-refs — reads canon-kit’s knobs from a knob file now, so a left-behind <gates-dir>/canon-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-measured-claim — reads canon-kit’s knobs from a knob file now, so a left-behind <gates-dir>/canon-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also reads an inline <!-- measured: <key>=<value> --> written inside a prose line, binding the one sentence it follows, and a full-line marker’s claim loses any inline-bound sentence: a mid-line marker outside inline code that it used to ignore is now resolved against your oracle and can red — backtick a specimen, or fix the key and the sentence.

  • check-memory-off — reads context-kit’s knobs from a knob file now, so a left-behind <gates-dir>/context-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config.

  • check-merge-attrs — reads lifecycle-kit’s knobs from a knob file now, so a left-behind <gates-dir>/lifecycle-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-payload-claim — reads canon-kit’s knobs from a knob file now, so a left-behind <gates-dir>/canon-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-prose-enum — the bundled --emit enum-sets arm now also declares kpi-builtin, the binary’s bundled KPI ids, so a consumer on the bundled sets command whose prose hand-lists two or more bundled KPI ids in a delimited run reds until the list names all of them or cites the set; a consumer-configured sets command is unaffected. Two members separated by whitespace alone no longer chain into a hand list, which only removes findings — a prose-enum-exempt: marker you added for two ordinary words side by side can be deleted. Its chaining test is also no longer length-capped: a separator carrying no letter, holding a delimiter or a single and/or, chains two members whatever its length, where a gap over 8 bytes (16 with and/or) did not, and a digit or other non-letter beside the delimiter no longer breaks the chain — a hand list the caps hid now reds until it names every member or cites the set. It also reads canon-kit’s and queue-kit’s knobs from knob files now, so a left-behind <gates-dir>/canon-config.sh or queue-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the matching .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-prose-tells — reads canon-kit’s knobs from a knob file now, so a left-behind <gates-dir>/canon-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config.

  • check-queue-entry-budget — gains assertion (E): every top-level deferred entry must resolve a defer date, a Surfaced <date> or Filed <date> mark with the ISO date directly after it on one body line; an entry whose mark is lowercase, split from its date by a reflow, or separated from it by a word reds until respelled (the shipped queue template gains a Filed line for this reason). It also reads queue-kit’s knobs from a knob file now, so a left-behind <gates-dir>/queue-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-queue-hygiene — reads queue-kit’s knobs from a knob file now, so a left-behind <gates-dir>/queue-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-queue-prose-precondition — reads queue-kit’s knobs from a knob file now, so a left-behind <gates-dir>/queue-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-queue-sections — reads queue-kit’s knobs from a knob file now, so a left-behind <gates-dir>/queue-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-queue-slug-liveness — gains assertion B: on the surfaces a new knob, QUEUE_KIT_CITATION_SURFACE_GLOBS (default empty, so nothing changes until you set it), names, a single-backtick token naming a retired queue slug that is no tracked file’s stem reds; replace the slug with the owning SPEC section or the defect’s own description, or mark a history line <!-- retired-citation-exempt: <reason> -->. It also gains assertion C: in the queue file, a cited slug followed by (deferred), (icebox), (done) (your configured single-word section names, lowercased) or (retired) reds when the slug is not there; correct or drop the parenthetical. The gate now runs git and reads QUEUE_KIT_DONE_SECTION, and its clean line’s wording changed, so re-pin a log-matching tool. It also reads queue-kit’s knobs from a knob file now, so a left-behind <gates-dir>/queue-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-queue-wrap — reads queue-kit’s knobs from a knob file now, so a left-behind <gates-dir>/queue-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-reads-couples — reads canon-kit’s, evidence-kit’s, lifecycle-kit’s, and queue-kit’s knobs from knob files now, so a left-behind <gates-dir>/canon-config.sh, evidence-config.sh, lifecycle-config.sh, or queue-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the matching .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config. It also asserts the coverage of canon-kit’s kit-literal fallback walks, which it skipped as ?, on a tree that leaves CANON_KIT_MANIFEST_FILES or CANON_KIT_COMMENT_SURFACE empty; the shipped descriptors cover them, so it reds only where you shadow one of those members’ descriptors with narrower couples — widen them. It also asserts the coverage of check-footprint-fresh’s kit-roster read, every */SPEC.md, which that member declared as no walk at all; the shipped descriptor covers it, so it reds only where you shadow that descriptor with narrower couples — add */SPEC.md.

  • check-roadmap-fresh — reads queue-kit’s knobs from a knob file now, so a left-behind <gates-dir>/queue-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-scratch-citation — reads lifecycle-kit’s knobs from a knob file now, so a left-behind <gates-dir>/lifecycle-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-settings-paths — reads context-kit’s knobs from a knob file now, so a left-behind <gates-dir>/context-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config.

  • check-settings-pins — reads context-kit’s knobs from a knob file now, so a left-behind <gates-dir>/context-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config.

  • check-shim-restatement — reads lifecycle-kit’s knobs from a knob file now, so a left-behind <gates-dir>/lifecycle-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-spec-derivable-section — reads canon-kit’s knobs from a knob file now, so a left-behind <gates-dir>/canon-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-spec-dod-singleton — reads canon-kit’s knobs from a knob file now, so a left-behind <gates-dir>/canon-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-spec-embedded-source — reads canon-kit’s knobs from a knob file now, so a left-behind <gates-dir>/canon-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-spec-fence-balance — reads canon-kit’s knobs from a knob file now, so a left-behind <gates-dir>/canon-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-spec-pointer — the prose pass now reads a § citation with no path, resolving it against the headings of your whole manifest set, and a markdown link followed by §, resolving it against the link target; it also reds a manifest file carrying one qualifier-stripped heading title twice. A heading may be cited by its lead clause, the text before its first comma, em dash or colon. Fix each new red by citing a heading that exists (a bold paragraph lead-in is not a heading, so cite its enclosing heading), by writing a grammar placeholder as §<heading>, or by renaming one of two same-titled headings apart and re-reading its inbound pointers; the clean line’s wording changed, so re-pin a log-matching tool. It also reads canon-kit’s knobs from a knob file now, so a left-behind <gates-dir>/canon-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-stage-entry — reads lifecycle-kit’s knobs from a knob file now, so a left-behind <gates-dir>/lifecycle-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-stage-evidence — reads lifecycle-kit’s knobs from a knob file now, so a left-behind <gates-dir>/lifecycle-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-surface-duplication — reads canon-kit’s knobs from a knob file now, so a left-behind <gates-dir>/canon-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-task-conservation — reads queue-kit’s knobs from a knob file now, so a left-behind <gates-dir>/queue-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-task-names — reads queue-kit’s knobs from a knob file now, so a left-behind <gates-dir>/queue-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-todo-task-liveness — reads canon-kit’s knobs from a knob file now, so a left-behind <gates-dir>/canon-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-tracking-claim — reads canon-kit’s knobs from a knob file now, so a left-behind <gates-dir>/canon-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-unmarked-claim — reads canon-kit’s knobs from a knob file now, so a left-behind <gates-dir>/canon-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also takes an inline measured: marker as discharging a class match that starts inside the sentence it binds, and no other; nothing that passed before reds. A full-line marker now discharges only the claim it binds, where it discharged its whole paragraph: under CANON_KIT_MEASURED_SPAN=sentence that is the marker’s first sentence, and at every span a class match above a mid-paragraph marker is unmarked — mark the later sentence inline, move the marker above the claim, or rewrite the sentence out of the class.

  • check-battery-roster — reads evidence-kit’s knobs from a knob file now, so a left-behind <gates-dir>/evidence-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-evidence-manifest — reads evidence-kit’s knobs from a knob file now, so a left-behind <gates-dir>/evidence-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-producer-liveness — reads evidence-kit’s knobs from a knob file now, so a left-behind <gates-dir>/evidence-config.sh refuses it at exit 2, including the copy an earlier init seeded that you never edited — rewrite what you set into the .knobs file and delete the shell config. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-action-gh-repo — reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-action-pinning — reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-assertion-strength — reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-commit-msg — reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-commit-subject — reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-core-files — reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-crate-arms — reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-docs-kit-parity — reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-exec-bit — reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-gate-assertions — discovery reads the count-word through canon-kit’s spelled-cardinal grammar, so a section counting ten or more assertions (twelve assertions) is now discovered and coupled to its markers, where the hand list stopping at nine skipped it; such a section reds until its gate code carries a marker per label. It also reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-gate-binary-fresh — reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-gate-fail-closed — reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-gate-fixture-coverage — reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-gate-output — reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-hook-exec-bit — reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-identity — reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-install-disposition — reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-install-platforms — reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-kit-enum — reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-kit-ref-liveness — reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-kit-registration — reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-readme-roster — reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-root-tiering — reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-shellcheck — reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-smoke-entry-guard — reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-test-hermetic — reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-trajectory-fresh — reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-tree-terms — reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-value-rollup-fresh — reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-workflow-tiering — reads gate-sdk’s knobs from a knob file now, so a left-behind <gates-dir>/gate-sdk-config.sh refuses it at exit 2, including the one an earlier init wrote beside the gate binary — rewrite what you set into gate-sdk-config.knobs and delete the shell config.

  • check-audit-roster — new: every block of the audit roster LIFECYCLE_KIT_AUDIT_ROSTER_FILE names carries class:/scope:/due:/last: and, once swept, corpus:/hits:/declined:, one line each under LIFECYCLE_KIT_AUDIT_ROSTER_LINE_CAP bytes, each class once, with a current-iteration last: naming a stage the state file stamped. Both knobs are new; the file knob defaults empty, so the gate is inert until you set it — set it only after rewriting your roster into blocks (lifecycle-kit/SPEC.md §The audit roster), and declare the roster close-surface: <path> advisory in the binding that owns it.

  • check-provenance-seam — new, canon-kit: no kit SPEC carries a dated attribution, a section or possessive pointer into your agent file, a mention of a private surface you configure, or a live queue slug. It scans only when CANON_KIT_SCAN_KIT_ROOTS is 1, so a tree that vendors kits it does not author passes untouched. Four knobs are new: CANON_KIT_SEAM_AUTHORITY_MARKERS (with _EXTRA), CANON_KIT_SEAM_AGENT_FILES, CANON_KIT_SEAM_PRIVATE_SURFACES (default empty; set your ruling record and local-only brief) and CANON_KIT_SEAM_SLUG_MIN_LEN. If you author kits, register it, set the private surfaces, and sweep what it reds; a queue slug that appears in a kit SPEC reds the commit filing it, so rename the fresh slug. It also gains a consumer-roster arm: a kit SPEC paragraph quoting two or more elements of one indexed or keyed knob your tree configured — each as a whole inline-code span, absent from the knob’s default, carrying a / or = (a keyed pair spelled <key>=<value>) — reds, and a knob-file edit now fires it. It reaches only a tree that authors kits; name the knob rather than quote its configured roster. It also gains a hex-reference arm: a 7–40-character lowercase-hex run carrying a digit and a letter, bare or backticked, reds as a commit or digest reference — delete it and state the fact it labelled, or fence a specimen. And a new knob, CANON_KIT_SEAM_SURFACE_GLOBS (default empty), holds the published documents you declare your own to the provenance arms, never the consumer-roster arm, whatever CANON_KIT_SCAN_KIT_ROOTS says; its clean line now names the seam-surface count and says when that set is off.

  • check-stage-entry — new assertion D: at entry to LIFECYCLE_KIT_AUDIT_ENTRY_STAGE it refuses any on-disk amendment carrying a line that opens with **Inferred, not run:**, or with **Inferred, cannot run before build:** and no reason after a spaced em dash, and a bold marker spelling standing mid-line or split across a line break, refused as a misplaced marker; write a mention in a code span. Audit stamp or not; inert where that knob is empty. Before upgrading mid-iteration, run each such marker’s command and correct its passage, or give it the cannot-run form with a reason.

  • check-amendment-queue — the [design-pending] tag is retired: section membership is the design-pending state, so the three tag arms are gone and a new arm reds any line in a feature, active, deferred or icebox section that still carries the literal [design-pending], lead line or body. check-tag-lead-line no longer governs the tag. Before upgrading, delete the [design-pending] tag and the space before it from every lead line of your queue, rename a Why [design-pending] body field to Why design-pending, and un-bracket any other body mention; if you copied the close or scope stage template or the queue template out, re-take their deferred-entry sentences.

  • check-amendment-queue — new arm: every amendment-glob filename an on-disk amendment cites outside a fence or HTML comment must resolve to a file, a path as a file and a bare basename against the amendment set, so an amendment citing a merged sibling by filename now reds. Before upgrading, repoint each such citation to the canonical section the cited amendment merged into, or move an illustrative filename into a fence.

  • check-stage-entry — assertion D now also reads every line inside a top-level entry of a LIFECYCLE_KIT_ACTIVE_SECTIONS section of LIFECYCLE_KIT_QUEUE_FILE, so an active queue entry carrying an **Inferred, not run:** marker, or a cannot-run marker with no reason, refuses the audit-entry stage just as an amendment does; the deferred, icebox and done sections stay unread, and the clean count sums both corpora. Before upgrading mid-iteration, run each such marker on your active entries and correct the entry, or give the marker the cannot-run form with a reason.

  • check-prose-tells — a generated-region marker now counts only when it is its line’s whole trimmed content, so prose that mentions a <!-- name:begin --> marker inline, backticked or not, no longer opens a held-out region. Prose after such a mention was silently exempt to the next end marker or the end of the file, and it is read now. Before upgrading, run the gate and rewrite the tells it surfaces there.

  • check-stamp-subject — new, lifecycle-kit, commit-msg tier: a commit that adds a stamp line to LIFECYCLE_KIT_STATE_FILE must carry the last added stamp’s stage as its subject scope, <type>(<stage>): …. --enter-stage now prints that subject on a subject: line after its next: commit line; commit your stamps with it. Register the gate and regenerate your hooks with --emit git-hooks --write; a merge commit reds only on a stamp none of its parents carries; stamps a merged branch brought in were checked at their own commits.

  • check-front-end-fail-open — new, gate-sdk, zero-config: each vendored front-end stub, gate-sdk/bin/run-gates.sh and run-gates.ps1, must carry one declaration line naming exactly the gate binary’s fail-open set (--hook --statusline), the arms a stub exits 0 for when the binary is absent. The shipped stubs now spell that set as FAIL_OPEN_ARMS='…' and $FailOpenArms = @(…), with unchanged behavior; an absent stub is reported, not red. Register it and regenerate your hooks with --emit git-hooks --write; if you hand-edited a stub’s two-name test, take the shipped stub.

  • check-gate-substrate-parity — assertion F’s one-producer-per-digest check now reads a run: body’s commands rather than every line naming the hasher: sha256sum or shasum counts only in command position and without -c/--check, the alternatives of one if/else chain count once, and a script a step calls as bash <path> or sh <path> is read one hop deep at the call. A presence-check loop naming sha256sum stops counting, while a step re-adding a digest beside a called build script that already emits it, or a shasum -a 256 computation, now reds. Before upgrading, run the gate and move each digest it reports to the one step that produces its bytes.

  • check-door-binding — new, guard-kit, zero-config, and guard-kit’s first registered gate: no kit-shipped surface outside the fail-open set may name gate-sdk/bin/run-gates.sh or run-gates.ps1 as a command to run, and no kit template may name a path to the gate binary. The corpus is each kit root’s README.md, templates/, lib/ and bin/, minus gate-tests/ and smoke/. A mention that names the program as its subject rather than invoking it is a file, not a door, and passes; so does any command carrying --hook or --statusline, the fail-open arms that keep a shell door by construction. Register it (check-door-binding) and regenerate your hooks with --emit git-hooks --write. If you vendored a kit and re-pointed its prose at your own copy of the front end, this gate now reds on it — name the binary through GATE_SDK_NATIVE_BIN instead.

  • check-packed-links — new, gate-sdk, zero-config: the README the packer would put in the payload carries no link to a path GATE_SDK_PAYLOAD_WITHHOLD withholds. The gate packs its own corpus by calling the packer’s rewrite rather than spawning --pack-installer, so it needs no clean worktree and no npm. Two assertions per kit root: under a non-empty base no packed README links a withheld path (a smoke/ link and a ../<kit>/SPEC.md link both red); under an empty base the packed README is its tracked source byte for byte. Where your base is empty the gate packs under its own probe base, so the first assertion is never vacuous. Register it (check-packed-links) and regenerate your hooks with --emit git-hooks --write. If a kit README of yours links into smoke/ or at a sibling kit’s SPEC.md, this gate now reds on it.

  • check-kit-roots-dialect — new, gate-sdk, zero-config: one kit tree vendored at the repository root and under a subdirectory must yield one kit-root-derived corpus. It vendors a shipped fixture tree both ways under GATE_SDK_TMP_DIR, makes each a git repository, and re-runs --emit fixture-suites and --emit enum-sets through the binary from inside each, comparing row counts. Register it (check-kit-roots-dialect) and regenerate your hooks with --emit git-hooks --write. If you vendored the kits under a subdirectory — anything but gate-sdk as a direct child of your toplevel — upgrade first: the readers that join a kit root onto a filesystem path or a git pathspec were on the kit-parent spelling, so your --emit enum-sets, --emit close-surfaces and --emit fixture-suites have been emitting a corpus that shrank toward empty while exiting 0, and check-kit-registration and check-knob-citation have been printing clean lines with a smaller number in them. Nothing reds to tell you; the counts are the only symptom. And if you hand-list GATE_SDK_KIT_DIRS, spell its members for the layout you actually vendored — a flat hand-list under a nested vendoring is the same silent emptying reached through config, and this gate now reds on it.

  • check-template-copy-parity — the gate is unchanged; what moved is a template it reads. context-kit/templates/session-context.sh now resolves the gate binary through gate-sdk’s gate_native_bin_spelled accessor instead of building its own RUN_GATES path, so the template’s declared surface gained lib:gate_native_bin_spelled and an existing <gates-dir>/session-context.sh copy that has not been re-taken reds assertion B, naming that one token. Propagate the call into your copy — source gate-sdk/lib/gate.sh and read gate_native_bin_spelled where your copy built a front-end path — or re-take the copy from the template and re-apply your [EDIT ME] retargeting. The same release’s drift-kit/templates/kpi-deprecated-surface.sh moved the same way; it is unpaired in this tree and so outside this gate’s derivation, but a consumer that copied it into its gates directory owes it the same propagation.

  • check-path-dialect — a second assertion, over the crate half of its corpus only (GATE_SDK_NATIVE_SRC), holding what §The path-dialect contract’s clause two asserted and nothing enforced: a text-level path primitive — a leading-separator absoluteness test, std::path’s own is_absolute, a bare format!("{}/", …) prefix composition, or a format! with a separator in its template handed straight to starts_with/strip_prefix — is spelled in the crate’s speller (native/src/walk.rs) and nowhere else. A site whose /-separated value is not a filesystem location declares so with a new comment directive, // path-dialect-exempt: <reason>, on its own line or in the comment run above it; the reason is mandatory and an empty one reds. A recorded spec: verdict deliberately does not clear this arm. The directive joins check-comment-tier’s built-in roster, so it needs no CANON_KIT_COMMENT_* entry. This reaches only a consumer that vendors a Rust crate at GATE_SDK_NATIVE_SRC — the default is this repo’s own native/src, so a consumer with no such tree scans nothing and the arm is silent. Where you do carry one, route each site through walk::path_root, walk::abs_against, or the three containment helpers this release adds — walk::under, walk::at_or_under, walk::rel_under — which compare by path component and so answer the same across both dialects.

  • check-door-binding — a third assertion, C, reaching beyond the kit roots: over the surfaces a new knob GUARD_KIT_DOOR_ROOTS names — each entry a tracked file or a directory yielding its tracked members under assertion A’s own prune — every door reds unless its site declares itself contributor-facing with <!-- door-contributor: <reason> -->, the reason mandatory and an empty one malformed. There is deliberately no door-adopter: twin: the unmarked case is adopter-facing, so a page added tomorrow reds the day it lands rather than needing a roster entry. A declaration standing alone on its line covers the fenced block it immediately precedes, or the whole file when it stands before the first # heading; sharing its line with anything else it covers that line and the line below. A door inside a generated region takes its declaration from the emitter, never from the page, which the next regeneration erases. Assertions A and B are untouched and the fail-open arm exemption is shared rather than copied, so the knob ships empty and no consumer’s verdict moves on upgrade — the clean line is one count longer (configured surfaces swept) and that count is a report, never a floor. A misconfigured entry, one resolving to neither a tracked file nor a directory, is exit 2. door-contributor: also joins check-comment-tier’s built-in directive roster. To use it, name your own adopter-facing surfaces in GUARD_KIT_DOOR_ROOTS and declare the contributor-facing doors among them; leave it unset and the assertion is inert.

  • check-commit-msg — the gate is unchanged; what moved is gate-sdk/templates/msg-patterns.list, the pattern list init seeds into your gates directory, which gains an account-identification pattern: a handle-shaped backticked token within a few characters of one of four account nouns (account, login, username or handle), singular or plural, in either order; a handle-shaped token carries at least one letter or digit and is not a short all-lowercase CLI name. If you never edited your seeded copy, init rewrites it on upgrade and a commit message naming an account that way now reds; cite the role instead of the account.

  • check-tree-terms — reads the same seeded msg-patterns.list, so the account-identification pattern above now also scans your tracked tree: a tracked line pairing an account noun with a handle-shaped backticked token reds until it is reworded to cite the role, or the token is un-backticked where it is not an account. A verb reading of accounts or handles beside such a token reds too; reword the sentence, or edit your own copy of the list, which init then keeps.

  • check-gate-tamper — every registered gate’s declaration and the gate-sdk library are gate files whatever DELEGATION_KIT_GATE_FILES says, so a commit staging a vendored kit’s .gate, or lib/gate.sh, beside product code now reds; split it. The default’s unvendored <gates-dir>/lib/gate.sh element is dropped. The clean line’s wording changed, so re-pin a log-matching tool.

  • check-fence-command-head — new, canon-kit: every command in a bash, sh or shell fence of your governed doc set starts with a word that can run when the fence is pasted as written — a builtin, a configured program, a function the fence defines or sources, an expansion, a placeholder or a tracked path — and a word beginning with - never can. Two knobs are new: CANON_KIT_FENCE_PROGRAMS, a bundled generic utility set, and CANON_KIT_FENCE_PROGRAMS_EXTRA, default empty. Register it, add the toolchain your recipes name (a compiler, a fetch tool, a package manager) to the extra, and relabel a fence meant to be read rather than pasted as text or console. The kits’ own READMEs no longer open a fence line on a bare arm flag: each fence now sources gate-sdk/lib/gate.sh from ${GATE_SDK_ROOT:-gate-sdk} and runs the binary through gate_native_bin_spelled, so a copied block runs from your repository root.

  • check-docs-restatement-parity — new, canon-kit, zero-config: every --arm token and kit-prefixed knob token in the code of a page matching the new CANON_KIT_RESTATEMENT_PAGES globs (default empty) must occur as a whole token in the README.md in that page’s directory. A declared page with no README beside it is exit 2. Because the default is empty, the gate registers on upgrade and scans nothing. To use it, list your pages that restate a README’s runnable line (for example a site’s per-kit landing pages) in the knob, and edit any page whose code names an arm or knob its README no longer carries.

  • check-fence-run — new, canon-kit, zero-config, align-only (full battery, not the pre-commit hook): a shell fence your governed docs mark with <!-- fence-runnable --> (or <!-- fence-runnable: exit=<n> -->) immediately above it must pass a narrowed static pass and then run, under bash -euo pipefail in a scratch copy of your tracked tree with a fixed environment, exiting as declared. A fence’s heads are held to builtins, sourced or fence-defined functions, programs in the new knob CANON_KIT_FENCE_RUN_PROGRAMS (default git), and the gate binary under a fence-safe arm; exec, eval, trap and command other than command -v red. A misspelled or orphaned marker reds. With no marker it runs nothing and spawns nothing, so it registers on upgrade clean. Marking a fence makes bash owed for canon-kit from the next doctor or env-probe read; a host without bash then exits 2. To use it, mark the fences a reader can paste and run as written, and name any further program they need in the knob.

  • check-projection-roster — new, gate-sdk, zero-config, armed by the new knob GATE_SDK_PROJECTION_ROSTER (default empty; GATE_SDK_PROJECTION_ROSTER_SECTION names the heading, default the whole file). Gates may now carry a # projection: <globs> header line declaring that they byte-compare those tracked outputs against an emitter; the shipped freshness gates (check-enforcement-fresh, check-footprint-fresh, check-roadmap-fresh, check-graph) carry it. Once armed, each declaring gate needs exactly one top-level bullet in that section with <!-- projection: <gate> --> on its first line, and each key must name a declaring gate. Unarmed it asserts nothing and doctor names it disarmed, so it registers on upgrade clean. To use it, point the knob at the page where you list your generated files and key one row per declaring gate. check-graph’s trigger now follows GATE_SDK_GRAPH_ARTIFACT, the generated commit-msg hook, scripts/graph-theme/* and scripts/*-config.knobs (the default gates dir), so an edit to your graph theme or kit config re-runs it at commit; nothing to change. projection: joins check-comment-tier’s built-in directive roster.

  • check-docs-highlight-coverage — new, site-kit, zero-config, armed by the new knob SITE_KIT_HIGHLIGHT_TOKENS (default empty; SITE_KIT_HIGHLIGHT_OVERRIDES lists the override files, default docs/_layouts/*.html, and SITE_KIT_HIGHLIGHT_SCOPE names the highlight scope, default .highlight). Once armed, every class in the named snapshot must have an override rule whose selector, split on commas, ends in <scope> .<class>. Unarmed it asserts nothing and doctor names it disarmed, so it registers on upgrade clean. templates/site-health.yml gains an optional theme-drift arm, HIGHLIGHT_CSS_PATH and HIGHLIGHT_TOKENS_FILE, shipped empty and so skipped, that holds the snapshot to the live theme stylesheet in both directions. To use it, if your layout restyles code highlighting, add both env values to your copy of the template along with the arm’s block, track the class list it prints as the snapshot, and point the knob at it.

  • check-path-dialect — a third assertion over the tracked shell tree: a shell file that binds two or more roots from cd … && pwd captures, at least one derived from BASH_SOURCE, and composes any of them by string arithmetic (a $root/ join or a ${root%…}/${root#…} strip) reds unless its cwd is anchored first. Nothing but the anchor clears it — no spec: verdict or exempt token does. For each file it names, add cd "$(pwd -P)" as a line of its own before the file’s first root binding.

  • check-pipe-membership — new, gate-sdk, zero-config: in a tracked shell file that sets pipefail, a pipeline whose first stage streams a set (printf/echo with a [@] or [*] expansion, or a loop’s done) into a short-circuiting reader (head, or grep with -q/--quiet/--silent/-m/--max-count) reds, because the writer’s SIGPIPE can make a present member read as absent. It registers on upgrade; rewrite each site it names as a for loop over the set or a [[ ]] test.

  • check-kit-ref-liveness — two changes. First, the gap inbox (LIFECYCLE_KIT_GAP_INBOX_FILE, default .workflow/gap-inbox.md) joins the queue in the design-ahead valve, so a filed gap naming an unminted knob or path by its full prefixed name no longer reds; this only removes findings. Second, the member-to-stem direction now resolves only through a declared family — a static kit’s knob table naming the stem exactly (as evidence-kit’s table names EVIDENCE_KIT_RUN_), or, for a prefix no static table owns, a stem kit source spells. A stem that was only a wildcard mention in a help line or a comment no longer licenses every misspelled member under it; if such a reference exists in your tree, it now reds until corrected or the stem is declared a family in its owning kit’s table.

  • check-prose-tells — its five thresholds (CANON_KIT_PROSE_TELL_EMDASH_MAX, _CONTRAST_MAX, _TRICOLON_MAX, _RHYTHM_MIN_SENTENCES, _RHYTHM_CV_MIN) are now validated by canon-kit’s knob table instead of silently coerced: a malformed value (not a non-negative integer for the three maxes, not an integer of at least 2 for the rhythm floor, not a non-negative decimal for the CV floor) now refuses every canon-kit gate at exit 2 naming the knob and the value, where it previously read as zero and either fired on everything or never. Every tree’s defaults already meet their shape, so nothing changes unless you set one of the five to a malformed value.

  • check-queue-hygiene — two new axes. A <path>:<n> or <path>:<n>-<m> token in your queue body, whose path ends in a dot-extension, now reds: cite the other file by §Section, a symbol beside its path, or a quoted literal instead. An inline code span holding a git log command with -S and your queue file as its path now reds too: -S is blind to an eviction that keeps the slug, so use -G (queue-kit/SPEC.md §The icebox tier); -S stays correct over any other file. Rewrite any live line-number cite or -S pickaxe over your queue file before upgrading.

  • check-md-unwrapped — new canon-kit gate: no paragraph in the markdown set CANON_KIT_UNWRAP_GLOBS names (less CANON_KIT_UNWRAP_EXCLUDE) may be broken across lines. Both knobs default empty, so it is a clean skip until you opt in; it is the alternative to check-queue-wrap, so register the one matching your convention. To adopt it, set the globs, run --emit md-unwrap --write over the files it names, and register the gate.

  • check-queue-entry-budget — the per-entry cap now takes a unit, QUEUE_KIT_ENTRY_CAP, defaulting to 4300cp (code points, so a reflow moves nothing): an entry the old fifty-line default admitted may now measure over or under it, so read the clean path’s headroom rows after upgrading. It also reds a [cap-credit:] tag that is malformed, in another unit than the cap, over QUEUE_KIT_ENTRY_CREDIT_MAX (default 2150cp), set while credits or the cap are off, or stale (the entry fits the cap without it). To keep measuring lines, set QUEUE_KIT_ENTRY_CAP = 50lines and put check-queue-wrap beside it; to drop the cap, set off.

  • check-tag-lead-line — [cap-credit:] joins the governed set, so a credit written on a continuation line now reds. Move it onto the entry’s lead line.

  • check-surface-ratchet — sizes are now code points, and a ceiling row without the cp suffix exits 2 as a line-unit row. Re-stamp with bash gate-sdk/bin/run-gates.sh --emit always-loaded --ceiling and commit the file.

  • check-brevity — the per-bullet budget is now CONTEXT_KIT_BREVITY_CAP in code points (default 330), so a bullet joined onto one line no longer passes on its line count. A bullet near the old four-line budget may land either side; the finding names its size.

  • check-lifecycle-registration — the block --install-lifecycle writes is now one line per paragraph, so an installed block reds as stale after upgrading. Re-run --install-lifecycle on the gate binary and commit the agent file.

  • check-task-conservation — gains assertion B, the slug ratchet: a slug entering the live set (new, or renamed) longer than QUEUE_KIT_SLUG_MAX code points (default 30) now reds; slugs already live are grandfathered. Shorten the new slug before it lands, or set the knob to off.

  • check-task-names — reads the heading grammar: every task entry is a ### <slug> heading (a sub-task #### <slug>), a task-section heading that is no slug reds, and a bullet entry left in the old - **<slug>** — … shape reds. Gains assertion R: a same-file link [<slug>] (#<slug>) naming no live entry reds, and a single-backticked live slug in an entry body reds until it is that link. Run --emit queue-migrate --write <queue-file> on the gate binary once; it converts the queue and links the live citations.

  • check-tag-lead-line — every governed tag sits on the entry’s tag line, the first line under its heading, and [recurrence:], [roadmap-summary:] and [not-icebox-eligible:] join the governed set; the queue migration writes them there.

  • check-roadmap-fresh — reads [roadmap:] and its [roadmap-summary:] tag off the tag line, and the generated block’s bullets link to the queue entry; regenerate the page with --emit roadmap --write after migrating the queue.

  • check-evidence-baseline — a live slug is an entry heading, so a queue still in the bullet shape resolves no live slug and every held row naming one reds; migrate the queue.

  • check-gate-exemption-tasks — a live slug is an entry heading, so a queue still in the bullet shape resolves no # until: or # port-until: slug; migrate the queue.

  • check-deprecation-task — a live slug is an entry heading, so a queue still in the bullet shape resolves no marker’s task; migrate the queue.

  • check-todo-task-liveness — a live slug is an entry heading, so a queue still in the bullet shape resolves no TODO(task:) slug; migrate the queue.

  • check-close-surfaces — assertion C now reaches every declared path git reports gitignored, not only one under GATE_SDK_WORKFLOW_DIR, reading the roster’s new <tracking> column. Before upgrading, give each gitignored declaration outside your workflow directory a reclaim= command.

  • check-dispatch-entry — new, lifecycle-kit, commit-msg tier: while a lead-dispatched stage session has not entered, a commit that adds no stamp reds (a commit staging the gap inbox alone excepted). Register it, regenerate your hooks with --emit git-hooks --write, and have your lead dispatch through --enter-stage --dispatch <stage>; without a lead nothing declares a dispatch and the gate is inert.

  • check-path-dialect — gains a shell locality half: a [[ … == /* ]] glob test or a case alternative that is exactly /* reds in any tracked shell file outside gate_path_rooted’s own body, because it reads a drive-rooted path as relative. Route the test through gate_path_rooted "$x" from gate-sdk/lib/gate.sh, or, where the value is not a filesystem path, mark the line # path-dialect-exempt: <reason>.

  • check-portability-floor — gains an ASCII arm: a non-ASCII byte outside a full-line # comment in any .ps1 on your configured install-path corpus now reds, even with an empty construct roster, because Windows PowerShell 5.1 reads a BOM-less script in the ANSI code page. Spell the character by code point ([char]0x2014), or move a trailing comment onto its own line; there is no valve.

  • check-install-disposition — assertion D accepts and verifies the content form # armed-by: content <KNOB>…: a content with no knob after it reds, and so does any knob in its list that no static kit declares or that another kit owns. Give each content declaration at least one knob of the gate’s own kit.

  • check-gate-substrate-parity — gains assertion J, in a publishing tree only (one tracking the crate source): each kit’s smoke/install.sh registry heredoc must register or declare # unregistered: every subcommand that kit owns, and register none it does not. A vendored tree is out of scope and unaffected; a kit author adds each new owned gate to the heredoc.

  • check-pipe-membership — now also reads each tests dir’s tracked *.test.sh suites (a direct child of a gate-tests/ directory, or of the directory GATE_SDK_TESTS_DIR resolves to where you relocated it, whose path above is unpruned), so a set-fed membership pipe in a suite reds; fixture case trees stay out. Its clean line now counts suites apart (N shell file(s) and S suite(s) scanned). Fix a red with the loop form the finding names.

  • check-stage-entry — new assertion E: it refuses a malformed or misplaced inferred-claim marker at every commit and every stage entry, not only at the audit-entry stage, and a not-run marker with no command after a spaced em dash is now malformed. Assertion D keeps only the well-formed not-run residue at audit-entry. Before upgrading, run the gate and write the missing command or reason on each marker it names, or move a misplaced spelling to open its line or into a code span.

  • check-graph — a token repeated within one couples= or trigger= field of a gate’s # graph: manifest, or of an amendment-body manifest, now reds. Drop the duplicate token, then regenerate your hooks with --emit git-hooks --write.

  • check-gate-assertions — discovery skips a lone descriptor line opening a ### <gate> section (a backticked .gate or .sh path and its parenthetical), so an enumerated contract in the paragraph after it is now discovered and coupled. Add a # assertion <label>: marker per label it names, or fix the enumeration.

  • check-settings-paths — now also resolves every type: command hook under hooks, whatever its event: a hook whose command token is a repo-relative path (a leading ${CLAUDE_PROJECT_DIR}/ stripped) must resolve in the tree, and a hook through run-gates.sh/run-gates.ps1 --hook <member> must name a member the gate binary carries. Before upgrading, repoint or drop each hook the gate names; a $-rooted plugin path is skipped and counted.

  • check-action-run-path — new: a script a shell run: step invokes must exist in the tree. Every repo-relative .sh path in invocation position in a workflow’s run: body, single-line values included, must resolve under the scan root, or a step’s literal working-directory:. It is on-surface, so init does not register it: add it to gates.list where your tree carries workflows, after repointing or deleting each step the gate names.

  • check-evidence-baseline — the suite-coverage arm now also reads the reverse direction: under a non-empty EVIDENCE_KIT_SUITES, a baseline row whose suite is not in the roster reds, once per such suite, since nothing runs a suite that left the roster and its rows assert nothing. An empty roster still disarms the arm, both directions. Before upgrading, delete the rows of every suite you dropped from the roster.

  • check-docs-mirror-fresh — the docs-mirror arm now wraps each mirrored page’s body in a Liquid raw block, its tags inside HTML comments, so a Jekyll site renders a SPEC’s quoted template-looking text verbatim instead of blanking it or failing the build; the arm refuses a source carrying the raw block’s closing tag. A mirror you committed before upgrading reads stale: regenerate it with --emit docs-mirror --write.

  • check-prose-bounds — new in canon-kit, on-surface: a sentence over CANON_KIT_PROSE_BOUND_SENTENCE_MAX words (default 45), a unit over CANON_KIT_PROSE_BOUND_PARAGRAPH_MAX (default 200), or a CANON_KIT_PROSE_BOUND_REPEAT_WORDS-word phrase (default 6) recurring in CANON_KIT_PROSE_BOUND_REPEAT_MIN units of one section (default 3) reds, over the files CANON_KIT_PROSE_BOUND_GLOBS names. That knob defaults empty, so nothing is scanned until you set it. Adopting it over existing prose, set CANON_KIT_PROSE_BOUND_CEILING_FILE and write one <n> <path> row per file from the run with that knob emptied; each row must then equal its file’s count.

  • check-provenance-seam — gains a dated arm: any ISO date outside a fence in a scanned kit SPEC or declared seam surface now reds, attributed or not. Before upgrading, state each measurement undated as its rule’s ground, spell a specimen’s date YYYY-MM-DD, or fence it.

Renamed knobs

  • CONTEXT_KIT_BREVITY_SECTION → CONTEXT_KIT_BREVITY_SECTIONS — the scalar becomes an array, and the knob-file reader refuses the retired name. Rename it in your config.

  • DELEGATION_KIT_VERDICT_BIN → ∅ — removed with the shell binary it named, the verdict rule having ported to the compiled arm. Delete it from your config.

  • SITE_KIT_CONFIG_FILE → SITE_KIT_KNOB_FILE, and DOCTRINE_KIT_CONFIG_FILE → DOCTRINE_KIT_KNOB_FILE — the file they name changes grammar, and a set old name naming a non-empty file is refused at exit 2. Point the new name at your rewritten knob file.

  • QUEUE_KIT_CONFIG_FILE → QUEUE_KIT_KNOB_FILE, and LIFECYCLE_KIT_CONFIG_FILE → LIFECYCLE_KIT_KNOB_FILE — the file they name changes grammar, and a set old name naming a non-empty file is refused at exit 2. Point the new name at your rewritten knob file.

  • CANON_KIT_CONFIG_FILE → CANON_KIT_KNOB_FILE, CONTEXT_KIT_CONFIG_FILE → CONTEXT_KIT_KNOB_FILE, DELEGATION_KIT_CONFIG_FILE → DELEGATION_KIT_KNOB_FILE, and DRIFT_KIT_CONFIG_FILE → DRIFT_KIT_KNOB_FILE — the file they name changes grammar, and a set old name naming a non-empty file is refused at exit 2. Point the new name at your rewritten knob file.

  • CANON_KIT_ENUM_SET_NAMES, CANON_KIT_ENUM_SET_MEMBERS, CANON_KIT_MEASURED_KEYS, CANON_KIT_MEASURED_VALUES, CANON_KIT_INSTALL_TRANSPORT_IDS, CANON_KIT_INSTALL_TRANSPORT_PATTERNS, CANON_KIT_PAYLOAD_CLAIM_IDS, CANON_KIT_PAYLOAD_CLAIM_PATTERNS, CANON_KIT_CLAIM_CLASS_IDS, CANON_KIT_CLAIM_CLASS_PATTERNS → ∅ — never set by a consumer and read by nothing; delete any reference.

  • EVIDENCE_KIT_CONFIG_FILE → EVIDENCE_KIT_KNOB_FILE — the file it names changes grammar, and a set old name naming a non-empty file is refused at exit 2. Point the new name at your rewritten knob file.

  • GUARD_KIT_CONFIG_FILE → GUARD_KIT_KNOB_FILE — the file it names changes grammar, and a set old name naming a non-empty file is refused at exit 2. Point the new name at your rewritten knob file.

  • GATE_SDK_NATIVE_ARTIFACT_NAMES and GATE_KIT_ROOTS_HERE → ∅ — never set by a consumer: --pack-installer discovers each target’s artifact name, and the gate binary derives the kit roots itself. Delete any reference.

  • GATE_SDK_CONFIG_FILE → GATE_SDK_KNOB_FILE — the file it names changes grammar, and a set old name naming a non-empty file is refused at exit 2. Point the new name at your rewritten knob file.

  • GATE_PRUNE_DIRS, GATE_EXEC_GLOBS, GATE_EXEC_PRUNE, GATE_LINT_EXTRA_DIRS, GATE_GRAPH_EXTERNAL_REFS, GATE_MSG_PATTERN_FILES, GATE_MSG_PATTERN_FILES_LOCAL, GATE_PORTABILITY_PATTERNS, GATE_PORTABILITY_PATHS, GATE_KIT_ROOTS_REL and GATE_SDK_ROOT_HERE → ∅ — resolved arrays gate-sdk/lib/gate.sh set after sourcing, never consumer-authored; set the GATE_SDK_* knob each was split from. A shell gate of yours that spliced GATE_GREP_EXCLUDES calls gate_prune_load first.

  • QUEUE_KIT_ENTRY_LINE_CAP → QUEUE_KIT_ENTRY_CAP — the value gains a unit (<n>cp, <n>lines or off), and the knob-file reader refuses the retired name at exit 2. Rename it, writing 50lines for the old 50, or drop it to take the 4300cp default.

  • CONTEXT_KIT_BREVITY_BUDGET → CONTEXT_KIT_BREVITY_CAP — lines per bullet become code points per bullet, and the knob-file reader refuses the retired name at exit 2. Rename it and restate the value in code points, or drop it to take the 330 default.

Behavior changes

  • CANON_KIT_UNWRAP_DECLARATION_LEADS — new canon-kit knob, an array of line leads, default empty: a line whose content starts with a member opens a block, so check-md-unwrapped never reds it and --emit md-unwrap never joins it into the paragraph below. Nothing changes at the default. Act only if your tree writes machine-read declaration lines inside prose — a ruling:, a discharge:, a trailing-key record — which an unwrap has been folding into the paragraph that follows them, silently taking the declaration out of its reader’s reach: list those leads in the knob and re-run --emit md-unwrap --write over the affected files. A member that is empty is malformed config and refuses at load.

  • lifecycle-kit/templates/lead.md — the “Relay, never assert” list gains one rule: operator answers that seem to conflict go back to the operator as that question, never reconciled by inference and relayed as their words. Act only if your lead binding restates that list rather than pointing at the template — take the template’s copy. Declared here by the close that discovered the omission, the landing session having ended; the gate’s mechanical half reads removed kit bin/ paths and changed template init claims, and a template’s prose rules sit outside it by construction.

  • CANON_KIT_MANIFEST_FILES / CANON_KIT_PROSE_SURFACE_GLOBS — both knobs’ globs now expand through the pruning walk, as CANON_KIT_COMMENT_SURFACE already did: a ** member no longer descends into a GATE_SDK_PRUNE_DIRS directory, and a glob naming one outright yields nothing. Two things get better without action — a value spelling any depth as ** stops governing vendored gate-tests/ bodies as manifest content, and no manifest reader stats a build directory a concurrent build is rewriting, which was a fail-closed exit 2 on a clean tree. Act only if a doc you meant to govern sits under a pruned directory: name its path in the knob rather than reaching it through a ** (the count-reading gates this can red are declared above).

  • check-docs-link-convention — the report is rebalanced toward the remedy: each of the three rules now carries its own help: line, printed only when that rule fired (the valve’s prints on any red), and each finding line states the violation alone where it used to append that rule’s remedy. The verdict set is unchanged, so nothing new reds; act only if you grep this gate’s output, in which case match on the rule phrases (directory-target link, off-root relative link, anchorless back-link), which are unmoved.

  • --emit close-surfaces — each row gains a sixth tab-separated column, <tracking>, reading tracked, ignored or untracked; a reader splitting a row on five fields folds it into <state>, so split on six. lifecycle-kit/templates/stages/close.md step 4 now reads a row that is not tracked in the closing session, never through an isolated agent.

  • --enter-stage --dispatch <stage> and --enter-stage --dispatch-withdraw <stage> — two new forms, with the knob LIFECYCLE_KIT_DISPATCH_MARKER_FILE (default stage-dispatch.txt, inside GATE_SDK_TMP_DIR): --dispatch runs the --simulate pre-flight and, when it would proceed, appends the stage to that scratch marker; every stamp removes one line naming its stage; --dispatch-withdraw removes one for a session that ended without entering. lifecycle-kit/templates/lead.md now gates each stage-session dispatch with --dispatch instead of --simulate; a lead binding that restates the old step should take the template’s.

  • check-crate-arms — gains a third arm: whenever the crate’s source stamp misses, after clippy and the tests, it runs --run-gate-tests over every fixture suite --emit fixture-suites lists, through the binary GATE_SDK_NATIVE_BIN names, and a failing suite reds the gate. The gate is install: never, so this reaches you only if you registered it by hand over a crate of your own: expect the added wall-clock of your suites on a commit that touches the crate, rebuild the binary before reading a suite’s red, and if a fixture case of yours runs this gate, pin GATE_SDK_KIT_DIRS in that case’s knob file to a root with no gate-tests/.

  • lifecycle-kit/templates/stages/close.md — gains step 8, Review the audit roster, where LIFECYCLE_KIT_AUDIT_ROSTER_FILE names one; the old steps 8–12 are now 9–13. A binding that cites a close step by number (the brevity pass is now step 11) must move its reference, and a binding that ran its own audit-roster review sub-step should delete it in favour of the template step and declare the roster close-surface: <path> advisory.

  • .workflow/release-declarations.md — replaces <workflow-dir>/tightened-gates.txt as the accumulating declaration surface, and carries all three declaration-bearing sections as release-note bullets rather than bare gate names; --upgrade-smoke at an untagged TO reads its Tightened-gates section. If your tree keeps the old file, move each name to a - `check-name` bullet under ## Tightened gates in the new file, keep a # contract: first line, and delete the old file.

  • lifecycle-kit/templates/stages/build.md — the declaration instruction widens from appending a tightened gate’s bare name to appending a release-note bullet, remedy included, for any kit-shipped gate, knob or behavior change. If you copied the template out, your copy teaches the retired file and grammar.

  • context-kit/bin/always-loaded.sh — deleted, reachable as a bridged run-gates.sh arm; an unrecognized operand now refuses at exit 2 where it exited 0, every operand is validated, and an --update-baseline write failure refuses naming the path.

  • guard-kit/bin/compare-settings-allow.sh — deleted, reachable as a bridged run-gates.sh arm with the same tightened operand handling.

  • gate-sdk/bin/run-gates.sh — the battery dispatches through the binary’s --run arm on a worker pool sized by the new GATE_SDK_JOBS knob, with per-member TMPDIR isolation.

  • installer/lib/ — deleted; its verbs moved behind a checkwright-gates --install <verb> invoke, the omit-and-declare degraded install retired, and the bootstrap has one success path and refuses rather than completing degraded.

  • queue-kit/bin/ — deleted (lesson-sink.sh, queue-counts.sh, queue-edges.sh), each moved to a bridged arm. Re-point any script that called them.

  • couples= manifest field — gains a knob:<NAME> token, widening a regenerated hook’s trigger set; regenerate your hooks.

  • --reads declaration — gains a third field, a prune set, held to executed behavior by check-reads-couples.

  • lifecycle-kit/SPEC.md §The committed gap inbox — the recurrence-stamp obligation’s reach half is now answered rather than left open: the rule binds any session, so it is carried on the consumer’s always-loaded surface once and the stage templates stay silent (context-kit/SPEC.md §The consumer footprint). If your always-loaded file does not carry a recurrence-stamping line, a build, align, spec or validate session of yours meets the obligation nowhere — add one; the shipped stage templates will not, by ruling. lifecycle-kit/templates/lead.md keeps the lead’s differing discharge (file a gap bullet, since a lead writes no queue) and drops the general obligation it used to restate.

  • lifecycle-kit/templates/lead.md — the journal-disposal step no longer lists a knowledge-friction line among the channels a lead disposes findings into, and says so explicitly: that log is capture-in-the-moment, and a line written at disposal time is the deferred capture its own channel counts as none. Three grounds passages move to lifecycle-kit/SPEC.md, which now owns them — the prompt-answered-signal trap’s generality-and-salience argument and the lead-stamp ban’s enforcement reading (§The state machine), and the no-sibling-dispatch clause’s nested-supervisor ground (§templates/lead.md); the template keeps each instruction with a citation. If you copied the template out, re-take the disposal step at minimum: a copy that still lists the friction log teaches the deferred capture.

  • context-kit/SPEC.md §The consumer footprint — the resident-ask budget is now one pointer line per obligation a kit owns whose bound actor fires no trigger that loads it, rather than one per kit; a resident block is sanctioned above that budget only where it is generated and held in lockstep by a gate; and an obligation binding any session is carried resident once and is not also restated in the kit templates those sessions load. The roster gains a lifecycle-kit row — its generated registration block plus the gap-inbox and survey-record capture lines — so a consumer costing adoption now reads that ask instead of the retired claim that every kit but three asks nothing. If a template you copied out restates an obligation your always-loaded file already carries, delete the restatement; nothing reds either way, the footprint roster being prose contract.

  • DELEGATION_KIT_LIVENESS_CMD — lost its shipped default; without an authored reader every firing reports verdict=unavailable rather than red.

  • turn-end liveness hook — the refusal set gains a third verdict, unstarted, logged with a spawn field for a resolved reader that could not start (previously logged unavailable).

  • --emit queue-counts — gains --by <tag>, one <section>/<value><TAB><count> line per partition: a field tag yields its first lead-line value, a bare tag its own name, an entry carrying neither (none), and a section with no entries emits nothing. Bare invocation is byte-identical to before, so the in-process statusline caller is unaffected and nothing needs re-pointing.

  • --emit queue-edges — a retired target whose slug is the basename, less its final extension, of a file the tracked tree carries now renders as <slug> (<N> inbound, retired — name live at <path>), a further match noted as (+<M>). Evidence only: nothing is filtered, no verdict is added, and an unmarked row is byte-identical to before. If you parse the retired block, widen the pattern past the closing ) of retired. lifecycle-kit’s scope and close templates now route their readers to the mark; re-vendor them to pick that up.

  • --emit entry-history <slug> — the addressable domain widens from live slugs to live plus retired ones, so an entry that has left the queue is reported rather than refused: its header names the departure commit and that commit’s subject, and the fall rows are unchanged. A slug in neither set still refuses at exit 2, now naming that class. If you matched the old refusal text the arm reads a live entry, match the new not a live or retired slug instead.

  • --icebox-candidates — a standing cause now renders [standing] <date> — <grounds>, dropping the declaration’s own not-icebox-eligible token now that the ✗ mark and the [standing] class token already say it; and every cause’s character cap binds only its variable tail (the grounds, or a named live slug) rather than the whole cause string, so a class prefix prints whole regardless of its own width against the cap. If you parse the worklist’s standing rows, match the shortened prefix.

  • lifecycle-kit/bin/ — deleted, enter-stage.sh included; reachable via run-gates.sh --enter-stage.

  • --emit always-loaded — the meter, its baseline row and --growth measure code points, not lines: the row reads <total>cp <surface>cp <commit>, and a row without the suffix prints baseline in retired line unit — re-stamp with --update-baseline in place of a delta until re-stamped. Run --update-baseline once after upgrading; kpi-always-loaded’s trend fragment reads loaded <n>cp, so a trend series crossing the upgrade changes unit there.

  • --emit footprint — each cell reads <n>cp · ~<t>t in place of <n>l · ~<t>t, and the page’s prose paragraphs are emitted one line each. Regenerate your footprint page.

  • --emit queue-index --icebox-candidates and --emit entry-history — an entry’s size prints in the cap’s unit with its suffix (4211cp, 48lines), code points under off, where it printed a bare line count. If you parse either, match the suffix.

  • context-kit/templates/close-brevity.md — step 1’s growth list is net code points. If you copied the template out, re-take the step’s wording.

  • Every kit’s markdown — each kit’s SPEC, README and shipped templates, and doctrine-kit/DOCTRINE.md, now carry one line per paragraph, and DOCTRINE.md’s *Stages:* and *Digest:* trailers each stand as their own paragraph inside the rule. Nothing reds; a template you copied out differs from the shipped one in line breaks only, so re-take it or leave it.

  • check-knob-citation — the “default” marker binds only a knob token it follows within a reach, 100 code points unless you set the new reach knob below, so a knob no longer reds for an unrelated default elsewhere on its line; it fires less, never more.

  • gate-sdk/templates/msg-patterns.list — the account-identification pattern needs the account noun and the handle-shaped token inside one clause (no . or ; between them). If you copied the template, re-take that line.

  • --emit md-unwrap [--write] <file>… — new canon-kit arm joining every paragraph broken across lines, the remedy check-md-unwrapped prints.

  • guard-kit rule 17 — now auto-allows a create-redirect or quoted-delimiter heredoc body to a gitignored target, where it prompted.

  • drift-kit/bin/drift-report.sh — deleted with drift-kit/kpis/*; reachable via run-gates.sh --emit-drift-report.

  • init digest repair — init now rewrites a gate binary that fails its recorded digest, so the remedy doctor prints for a mismatch works.

  • context-kit/bin/md-index.sh — deleted with md-section.sh, pub-index.sh, lib/pub-lang/rust.sh, lib/pub-lang/ts.sh, lifecycle-kit/bin/cite-survey.sh and lifecycle-kit/bin/file-survey.sh, each moved to a bridged arm; CONTEXT_KIT_PUB_LANG_DIR shadowing is unchanged.

  • guard-kit command classification — an unrecorded backgrounded launch blocks where it advised (rule 22); rules 4 and 7 withhold a rewrite’s allow unless bounded; a path-slot block and a git rm force-flag block are new; read-only roster members are withheld on declared write or execute forms; wrapper stripping widens. GUARD_KIT_RO_FORMS and GUARD_KIT_SEARCH_TOOLS are new knobs.

  • guard-kit/bin/scan-prompts.sh — deleted; reachable via --emit scan-prompts.

  • lifecycle-kit/bin/session-id.sh — removed with context-kit/bin/env-probe.sh, both re-pointed at bridged arms.

  • evidence-kit/bin/diff-baseline.sh — each argument group is <suite> <logfile> [<status>], and a suite on the exit-code parser named without a status refuses at exit 2; pass the suite’s own exit status.

  • install-lifecycle — no longer exits 1 with no output; it works wherever the worktree-lock pattern is configured.

  • iteration-boundary worktree check — --enter-stage refuses on agent-worktree residue, under the new LIFECYCLE_KIT_BOUNDARY_WORKTREE_CHECK knob defaulting to 1, so an upgrading consumer meets the refusal unopted.

  • gate-sdk/bin/port-blockers.sh — deleted; reachable via --emit port-blockers.

  • INSTALLER_SMOKE_ARTIFACTS_DIR — new knob: the consumer smoke adopts a producer’s built artifacts instead of always building on the host.

  • --enter-stage surplus argument — refused, where it was silently dropped.

  • gate-sdk/bin/run-gate-tests.sh — removed with guard-kit/bin/run-guard-tests.sh and context-kit/smoke/agents-md.sh, replaced by run-gates.sh arms.

  • gate-sdk/bin/upgrade-smoke.sh — deleted; reachable via run-gates.sh --upgrade-smoke, whose stdout is now reserved for the one verdict line.

  • delegation-kit/bin/run-usage-tests.sh — deleted with run-trend-tests.sh, folded into the crate’s own test lane.

  • delegation-kit/bin/wait-probe.sh — deleted; reachable via run-gates.sh --wait-probe.

  • lifecycle-kit/templates/stages/ — each of the six stage templates gains a mandatory last step appending to the stage’s resume journal.

  • lifecycle-kit/templates/lead.md — the pre-dispatch --simulate read is a mandatory step.

  • check-queue-entry-budget — a ruled: declaration line no longer counts against the wrap budget.

  • gate-sdk/templates/gates-workflow.yml — gains a least-privilege permissions: block; a copy taken earlier runs the gates job under the repository’s default token scope.

  • installer/bin/checkwright.ps1 — a PowerShell bootstrap ships beside the bash one.

  • installer/lib/init.sh — with uninstall.sh, routes every git call naming the whole vendored roster through batched argv, fixing an argv-width overflow.

  • consumer-smoke manifest arm — splits exit 2 from a finding and reports before it fails.

  • check-graph — resolves its own interpreter, changing how it spawns on a Windows host.

  • --emit ruling-staleness — a new reporting arm; its three LIFECYCLE_KIT_RULING_* knobs default empty and inert.

  • supported-platform roster — x86_64-apple-darwin and aarch64-apple-darwin joined.

  • kpi-incident-recurrence (drift-kit) — the counted series is not comparable across this release: every recurrence: date before it was matcher-derived, every date after is a session’s judgment. Compare within a segment, never across the break.

  • installer/lib/doctor.sh — doctor walks only the consumer-audience subset of the toolchain roster, so a machine with no Rust toolchain installs. Reconcile if you relied on doctor failing on a contributor-side tool.

  • context-kit/lib/toolfloor.sh — a roster element gains a fourth positional field, <name>[:<min-version>[:<impl-token>[:<audience>]]]; empty or omitted means every audience, so existing elements are unchanged.

  • .github/workflows/site-health.yml — a release-channel arm files a site-health issue naming every Release whose prerelease flag disagrees with its version line. Reconcile if you copied the workflow out.

  • --emit file-survey (with check-survey-record and --emit cite-survey) — a survey block’s fields are now corpus, oracle, rev, finding, inferred. The edges field is retired, and inferred holds each claim no command established, or none. The arm’s fourth and fifth positionals are now "<inferred>" "<finding>". An old-order call still has five arguments and is accepted: its old edges argument now files under inferred and its old finding argument still files under finding, unmoved — a safe degrade rather than a misfile, since inferred is already re-checked before a reader relies on it. Update any instruction file or script that spells the old order at your own pace, not as a fire drill. A survey record your tree has not yet truncated at its next boundary re-keys its blocks in the same motion, or check-survey-record reds.

  • --emit ruling-staleness — a paragraph of your ruling record that states the record’s contract, rather than a ruling, can carry <!-- undeclared-condition-exempt: <reason> --> anywhere inside it, and the undeclared-conditions report then skips that paragraph. The reason is mandatory: a valve without one reports under malformed declarations and excuses nothing. If your record’s framing paragraph shows up as an undeclared condition on every run, add the valve to it; nothing else changes.

  • drift-kit/bin/kfric.sh, lifecycle-kit/bin/file-gap.sh — deleted; run run-gates.sh --emit kfric and --emit file-gap instead. Neither arm has a -h/--help form: a leading flag is refused rather than captured, and the refusal prints the arm’s usage. Re-point any instruction file or script naming the old paths.

  • doctrine-kit/bin/stage-rules.sh, lifecycle-kit/bin/install-lifecycle.sh, gate-sdk/lib/declaration.sh — deleted; run run-gates.sh --emit stage-rules and --install-lifecycle instead, and the declaration holder is compiled into the gate binary. --install-lifecycle refuses at exit 2 outside a repository, where the shell tool wrote into the wrong directory and exited 0.

  • drift-kit/bin/overhead-meter.sh, drift-kit/bin/stage-economics.sh, guard-kit/bin/scratch-run.sh — deleted; run run-gates.sh --emit overhead-meter, --emit stage-economics and --scratch-run <script> [args…] instead. The scratch runner now rides the front-end’s existing grant, so withholding a separate grant no longer opts a consumer out of scratch-execution review.

  • evidence-kit/bin/run-validate.sh, evidence-kit/bin/diff-baseline.sh, delegation-kit/bin/usage-trend.sh, gate-sdk/bin/install-hooks.sh — deleted; run run-gates.sh --run-validate, --diff-baseline, --emit usage-trend and --install-hooks instead. Re-point any script or instruction file naming the old paths.

  • --hook subagent-stop-liveness — the turn-end liveness hook refuses (exit 2, blocking) on a red or unreadable liveness reading instead of only logging it; its log lines gain decision=<refuse|allow>, and verdict=error separates a broken tree from an unconfigured one. Reconcile if you relied on the hook only observing.

  • --only — naming exactly one gate now selects it even when gates.list omits it, provided a configured check dir declares it; two or more names still intersect the registry, and an unresolvable name still refuses at exit 2.

  • harness hook templates — eight shipped templates are deleted: delegation-kit/templates/{agent-budget-guard,agent-dispatch-guard,subagent-stop-liveness}.sh, guard-kit/templates/{escalation-guard,wakeup-guard}.sh and lifecycle-kit/templates/workflow-state-guard.sh, each now run-gates.sh --hook <name>, plus delegation-kit/templates/statusline-usage.sh (now --statusline) and usage-poller.sh (now --usage-poll). Re-point every hook command in your .claude/settings.json.

  • installer/bin/checkwright.sh, checkwright.ps1 — host detection reads the host’s libc and refuses on musl or an unidentifiable libc, naming what it detected, where it used to hand a musl host a glibc-linked binary that failed in the dynamic loader.

  • the gate binary on Windows — every spawn of bash refuses to resolve it by bare name, because the System32 WSL launcher shadows Git for Windows’ bash.exe on PATH; a bare-name spawn that used to launch WSL silently now fails with a named refusal.

  • context-kit/bin/run-index-tests.sh — deleted; run run-gates.sh --run-index-tests instead, and drop any permission grant naming the old path.

  • --emit parse-gates-log, --emit parse-smoke-log, --emit enum-sets — new bundled arms you can name in EVIDENCE_KIT_PARSER_<suite> and CANON_KIT_ENUM_SETS_CMD instead of writing a parser or enum-set script. Both knobs still take any command, so nothing changes unless you adopt them.

  • <gates-dir>/site-config.sh, <gates-dir>/doctrine-config.sh — with their .local.sh overlays, replaced by site-config.knobs / doctrine-config.knobs (and .local.knobs) in the knob-file line grammar (gate-sdk/SPEC.md §The knob file); site-kit/lib/site.sh and doctrine-kit/lib/doctrine.sh are deleted, the defaults living in the gate binary and printed by run-gates.sh --emit knob-roster. A left-behind shell config is refused at exit 2. For both kits an exported scalar knob now outranks the file, where a bare assignment in the shell config used to beat it, and the .local overlay always sits in the gates dir, never beside a relocated file. Rewrite each NAME=(a b) as one NAME[] = a line per element, and each NAME=v as NAME = v.

  • --emit drift-report — the iteration-start commit is read from the state file’s first stamp (DRIFT_KIT_STATE_FILE) instead of a pickaxe on the iteration name, so since-iteration KPIs include scope’s own commits and stop anchoring on the oldest sentinel commit. A shallow clone without the stamped commit reports n/a (no iteration baseline).

  • --emit always-loaded — --growth measures from the iteration-start commit where the new CONTEXT_KIT_STATE_FILE resolves one, and the bare line adds the surfaces’ delta since it and a (baseline stale) mark. Without a lifecycle state file the output is unchanged.

  • --emit overhead-meter — a bare invocation inside a delegated session now prints a notice and logs nothing instead of measuring whichever transcript was newest. A top-level session measures its own transcript exactly. The operand also accepts a stamp’s eight-character session id. Pass your session’s transcript or stamp id wherever you meter from a dispatched session.

  • run-gates.sh --only — resolves only the selected gates’ knobs, not every registered gate’s. A misconfigured knob belonging to a gate you did not name no longer fails a --only run; a bare run, and a --only naming that gate, still refuse on it.

  • check-template-copy-parity — a template and its copy that carry no ${NAME:-…} knob idiom are now compared like any other pair, where the whole gate refused at exit 2. A vendored knob-less template that had turned every run red now passes or reports its real findings; nothing to change on your side.

  • run-gates.sh --enter-stage — gains --open-lead-journal, which opens the lead journal under a heading keyed on the stage cursor; the boundary’s undisposed-journal advisory now reads the journal per segment and exempts the one the live lead opened with it. If you copied lifecycle-kit/templates/lead.md out, re-take its first step (the opener command), or the advisory keeps firing on your live lead.

  • <gates-dir>/queue-config.sh, <gates-dir>/lifecycle-config.sh — with queue-kit’s .local.sh overlay, replaced by .knobs files in the knob-file line grammar, and queue-kit/lib/queue.sh and lifecycle-kit/lib/stages.sh are deleted, the defaults printed by run-gates.sh --emit knob-roster; the binary’s --queue-parity and --stages-lib-parity arms go with them. A left-behind shell config is refused at exit 2, including the copy an earlier init seeded that you never edited: delete it, or rewrite what you set into the .knobs file init now seeds. For both kits an exported scalar knob now outranks the file. queue-kit’s .local overlay now sits in the gates directory even when the tracked file is relocated. A <gates-dir>/drift-config.sh (or any bridged config) that sources lifecycle-config.sh to copy a value — the wiring drift-kit’s SPEC documented for DRIFT_KIT_STAGES — breaks at upgrade: replace the source line and the copy with gate_static_knob LIFECYCLE_KIT_STAGES DRIFT_KIT_STAGES. gate_static_knob is transitional and is removed when the shell config bridge is.

  • Generated pre-commit hook — a gate reading a static kit’s knob file now fires on an edit to <gates-dir>/<stem>-config.knobs with no couple on its descriptor, and run-gates.sh --for selects it the same way; regenerate your hooks. A knob file you relocated with <KIT>_KNOB_FILE fires nothing unless your own descriptor couples it.

  • <gates-dir>/canon-config.sh, context-config.sh, delegation-config.sh, drift-config.sh — replaced by .knobs files (and .local.knobs overlays) in the knob-file line grammar, and canon-kit/lib/spec.sh, context-kit/lib/context.sh, delegation-kit/lib/delegation.sh and drift-kit/lib/drift.sh are deleted; run-gates.sh --emit knob-roster prints every default. A left-behind shell config is refused at exit 2, including the copy an earlier init seeded that you never edited: delete it, or rewrite what you set into the .knobs file init now seeds. An exported scalar knob of these kits now outranks the file. Every *_CMD knob is an argv, one NAME[] = element line per word, and runs with no shell: CANON_KIT_*_CMD, CONTEXT_KIT_HOOK_CMD and DELEGATION_KIT_REFRESH_CMD values that relied on shell syntax need a bash, -c and command-string argv, and none takes an environment override. DELEGATION_KIT_LIVENESS_CMD may now name an interpreter. A drift-config.sh that called gate_static_knob LIFECYCLE_KIT_STAGES DRIFT_KIT_STAGES writes DRIFT_KIT_STAGES[] <- LIFECYCLE_KIT_STAGES instead.

  • drift-kit/templates/kpi-deprecated-surface.sh — reads canon-kit’s knobs through run-gates.sh --emit knob-values, finding the front-end in the DRIFT_KIT_KIT_ROOTS handoff, so a copy you adapted from the old template reads nothing after the upgrade: re-copy the template, and pass DRIFT_KIT_KIT_ROOTS when you run it outside the drift report.

  • run-gates.sh --upgrade-smoke — the FROM scratch consumer is now seeded with every config template init copies before the baseline runs, and phase A deletes each shell config a knob-file template replaced at TO before it regenerates. If you drive the suite from your own tree, a gate that reds in phase B once those files are gone must still be named under Tightened gates.

  • lifecycle-kit/templates/upgrade.md — phase A gains a middle step: after the kit sync and before the regen, retire each <gates-dir>/<stem>-config.sh or .local.sh whose kit now ships a .knobs template, rewriting what you set into the .knobs file first. Without it the regen refuses at exit 2. If you copied the template out, re-take step 1.

  • <gates-dir>/evidence-config.sh — with its .local.sh overlay, replaced by evidence-config.knobs (and .local.knobs) in the knob-file line grammar, and evidence-kit/lib/evidence.sh is deleted; run-gates.sh --emit knob-roster prints every default. A left-behind shell config is refused at exit 2, including the copy an earlier init seeded that you never edited: delete it, or rewrite what you set into the .knobs file init now seeds. An exported scalar knob of this kit, and an exported EVIDENCE_KIT_RUN_<suite> or EVIDENCE_KIT_PARSER_<suite> member, now outranks the file. A config that built EVIDENCE_KIT_SUITES and EVIDENCE_KIT_RUN_<suite> in a loop over gate_fixture_suites writes EVIDENCE_KIT_SUITES[] <- EVIDENCE_KIT_FIXTURE_SUITES instead, and the run members for those suites are derived.

  • gate_fixture_suites — removed from gate-sdk/lib/gate.sh; run-gates.sh --emit fixture-suites prints the same rows. A workflow copied from gate-sdk/templates/gates-workflow.yml captures that arm’s output and loops over it instead, as the template now shows.

  • <gates-dir>/guard-config.sh — with its .local.sh overlay, replaced by guard-config.knobs (and .local.knobs) in the knob-file line grammar: an array is one NAME[] = element line per element, a declare -A map one NAME[key] = value line per pair, and NAME = empties a collection; run-gates.sh --emit knob-roster prints every default. A left-behind shell config is refused at exit 2, including the copy an earlier init seeded that you never edited, and the bash guard then blocks every command with the refusal: delete it, or rewrite what you set into the .knobs file init now seeds, using the Edit tool, which the guard does not intercept. An exported scalar knob of this kit now outranks the file. guard-kit/lib/guard.sh reads its knobs through the gate binary, so a guard running where the binary is absent steers nothing and says so on every call: build or install the binary beside gate-sdk/lib/gate.sh. A consumer rule in your bash-guard.sh copy that read a GUARD_KIT_* variable still reads it after the library is sourced.

  • guard-kit/lib/guard.sh — new generic rules block: a cat, printf or echo write rule 17 would grant alone but compounded with another statement (issue the write as its own call), and a lone emitter write to a target git does not ignore (use the Write or Edit tool, or the capture arm that owns the surface); and a bash -c or sh -c wrapper (run the payload as the command itself, or a scratch script through --scratch-run). Fall-through logging is no longer a numbered rule: it closes every call after the ruleset, as the template guard’s own guard_log_fallthrough call. A consumer rule placed before the generic ruleset is unaffected.

  • GATE_SDK_ROOT — the gate binary derives the kit roots from this locator: run-gates.sh exports it on every call, and a binary call made without the front-end, such as a generated git hook, reads it from the environment with default gate-sdk relative to the working directory. If you vendor gate-sdk anywhere else, export GATE_SDK_ROOT for those calls. A script of yours that reads GATE_SDK_ROOT as a path, is reached through run-gates.sh and changes directory first now meets the front-end’s relative spelling: absolutize it before the cd. run-gates.sh --emit kit-roots prints the derived set.

  • run-gates.sh --pack-installer — takes each roster target’s artifact name by discovery: the target’s artifact directory must hold exactly one regular file not named *.sha256 beside its sidecar, and zero or several refuses at exit 2 naming the directory. Remove stray files from an artifact directory before packing.

  • <gates-dir>/gate-sdk-config.sh, <gates-dir>/graph-vocab.sh — replaced by gate-sdk-config.knobs (and .local.knobs) and graph-vocab.knobs, in the knob-file line grammar; run-gates.sh --emit knob-roster prints every gate-sdk default. A left-behind gate-sdk-config.sh refuses the whole battery at exit 2, including the one an earlier init wrote beside the gate binary: rewrite what you set into gate-sdk-config.knobs, which init now writes the binary’s path into, and delete the shell file. A left-behind graph-vocab.sh refuses check-graph and --emit graph: write one GRAPH_LAYERS[] =, GRAPH_LAYER_RULES[] =, GRAPH_VOCAB[] =, GRAPH_LEADING[] = or GRAPH_LAGGING[] = line per element and GRAPH_LAYER_DEFAULT = …. An exported gate-sdk scalar now outranks the file, where a bare assignment in the shell config used to win — so an exported GATE_SDK_NATIVE_BIN overrides the binary path init wrote, and a relative one resolves inside whichever tree the call runs in: unset it, or export an absolute path, around any call into a consumer tree. GATE_SDK_GATES_DIR, GATE_SDK_ROOT, GATE_SDK_JOBS and GATE_SDK_VERBOSE are read from the environment only, and a knob-file line naming one is refused. A whitespace-list knob such as GATE_SDK_PORTABILITY_PATHS no longer expands a glob in its value: list the paths.

  • check-knob-default-coupling — on a Windows host it compares a table-derived default with the host’s .exe suffix removed, so the GATE_SDK_NATIVE_BIN default no longer reds against gate-sdk’s suffix-less SPEC literal in a freshly initialised consumer; nothing to change on your side.

  • the shell config bridge — removed. gate_knob_env, gate_knob_env_set, gate_knob_env_one, gate_static_knob, gate_kit_roots_rel, gate_commit_types, the binary’s --knobs arm and every GATE_SDK_KNOB_<NAME> variable no longer exist; gate_command returns <binary> <name> with no env prefix, and the binary reads each knob itself from the environment and the knob files. A script of yours that read a GATE_SDK_KNOB_<NAME> variable reads the bare name, or asks run-gates.sh --emit knob-values <NAME>.

  • lifecycle-kit/templates/stages/build.md — gains a paragraph treating an amendment’s roster as a floor: before a merge counts as complete the build session re-derives the roster against the tree, lands every missed site and names it in the commit, and reports a rostered target with nothing to update as a finding rather than editing it or dropping it. Nothing to change on your side; if you copied the template out, re-take the paragraph.

  • canon-kit/templates/SPEC-amendment.md — the ## Existing sections updated comment instructs an amendment’s rosters to name the probe that produced them and never to claim completeness. Nothing to change on your side; if you copied the skeleton out, re-take the comment.

  • gate-sdk/bin/gen-pre-commit.sh — deleted; the hooks regenerate with bash gate-sdk/bin/run-gates.sh --emit git-hooks --write, and the pre-commit or commit-msg operand prints one hook. A script or CI step of yours calling the old path must switch. Regenerate your hooks: their headers name the new command, and a trigger that repeated a knob: token’s members once per descriptor naming it now lists them once.

  • gate-sdk/lib/gate.sh — no longer defines gate_manifest_field, gate_expand_couples_var or gate_expand_couples, and the binary’s --knob-files flag is gone. A shell gate or script of yours calling one must stop; run-gates.sh --for <path> answers what a path triggers.

  • every kit README’s install block — the hook-regeneration line now reads bash gate-sdk/bin/run-gates.sh --emit git-hooks --write. If you copied the block into your own docs, re-take the line.

  • gate-sdk/bin/run-consumer-smoke.sh, gate-sdk/lib/consumer-smoke.sh — deleted; the harness is bash gate-sdk/bin/run-gates.sh --run-consumer-smoke [--keep] [kit-root...], with the same operands, output and exit codes, so a validate suite, script or permission grant naming the old path must switch. A shell script of yours sourcing the library, or calling a csmoke_ function, must stop. Its registration accounting now ends at exit 2 when a probed tree holds no gate binary for a .gate declaration, where it used to read that as the gate’s own exit 2.

  • every kit’s smoke/install.sh and smoke/violation.sh — the entry-point guard’s hint now reads run via run-gates.sh --run-consumer-smoke. Nothing to change on your side.

  • run-gates.sh --rewrite — a new guard-kit arm replaces a literal, or a line-scoped POSIX ERE, with fixed text in files inside the repository, printing every changed span, and refuses symlinks, .git paths and the lifecycle state file. A front-end grant now reaches these bounded rewrites of tracked files, as it already reached unbounded ones through --scratch-run. Nothing to change on your side.

  • guard-kit/lib/guard.sh rule 8 — now blocks every in-place perl rewrite on a file operand (perl -pi, -0pi, -i.bak), as it already blocked sed -i, and both steer to the --rewrite arm instead of the Edit tool. Nothing to change on your side.

  • guard-kit/lib/guard.sh rule 25 — now also blocks a cat, printf or echo write to a target git does not ignore when it is compounded with another statement, and steers it to the Write or Edit tool as its own call with the rest issued separately; a device target still passes. Nothing to change on your side.

  • run-gates.sh --emit scan-prompts — now counts a logged call carrying a write redirect or an expansion as prompting even where a committed or overlay * glob matched it, so its headline count and the kpi-prompt-friction reading step up once. Nothing to change on your side.

  • run-gates.sh --emit compare-settings-allow, guard-kit/templates/close-triage.md — the advisory now prints a dead-path section listing each local-overlay allow entry whose literal .sh command token names a script that does not exist, and the close-triage step takes it as a third disposition, pruning those entries; --count is unchanged. If you spliced the template into your close skill, re-splice it.

  • guard-kit/templates/settings-allow.json — a new template recommending allow entries for the front-end, git rm -q and the read-only banner and diagnostic tools an agent chains, each with its ground and reach in guard-kit/SPEC.md §The recommended allowlist. Nothing merges it for you: review the entries and union the ones you accept into .claude/settings.json’s permissions.allow.

  • couples= manifest field — has one semantics: every expanded token is a bash string pattern over the repo-relative path (* and ? cross /), for triggers and for check-reads-couples’ coverage alike, so that gate stops reporting a read below a couple’s glob depth; and each knob: member expands to its covering pattern (**/ collapsed to *, a leading * added), widening a regenerated hook’s trigger set. Regenerate your hooks; a per-depth sibling glob added only to satisfy check-reads-couples can be dropped.

  • canon-kit’s comment-surface and manifest readers’ couples= — check-comment-tier, check-todo-task-liveness, check-deprecation-task and check-spec-pointer couple *.sh where they coupled scripts/*.sh and kit:*.sh, and the eleven spec::manifest_files readers couple *CLAUDE.md where they coupled CLAUDE.md, so a .sh outside your kit roots or a nested CLAUDE.md now fires them. Their --reads declares each kit-literal fallback walk under an else: guard instead of ?, and --emit reads-census lists 14 fewer members. Regenerate your hooks.

  • couples= and --reads knob references — a knob whose row declares an element packing is addressed by field, knob:<NAME>.<field>, and a bare reference to it is refused at exit 2; CANON_KIT_EMBED_LANGS is the one packed knob, with fields kind, fence-langs and file-globs. check-spec-embedded-source couples knob:CANON_KIT_EMBED_LANGS.file-globs where it transcribed the default globs, so a language you add to that knob now fires the gate and is covered by check-reads-couples. Regenerate your hooks.

  • canon-kit/SPEC.md §The causal-completeness check, §Merging an amendment, canon-kit/templates/SPEC-amendment.md — the checklist gains a sixth point (a delta obliging every member of an enumerable corpus names each member’s satisfying value, or narrows past a member with none), point 2 now names every roster-holding reader of the surface a minted name lands on, and merge step 2 integrates by re-phrasing rather than appending. Nothing to change on your side; if you copied the skeleton out, re-take its ## Producers and consumers comment and its Causal-completeness and Merged-with-no-information-lost items.

  • lifecycle-kit/templates/stages/spec.md, lifecycle-kit/templates/stages/scope.md — the causal-completeness paragraph points at canon-kit’s checklist instead of restating four of its points, and the spec template gains a paragraph telling replacement text to re-phrase rather than append. Nothing to change on your side; if you copied either template out, re-take those paragraphs.

  • context-kit/templates/close-brevity.md — the pass reads the growth list against a file not being expected to grow each iteration, and its goal admits growth only where re-phrasing could not carry the change. If you spliced the template into your close skill, re-splice it.

  • delegation-kit/templates/agent-execution.md — three obligations a session owes when it backgrounds a child or dispatches into isolation now state their composition rather than each half alone. The record obligation splits producer from observer, so a wait loop or a read-only pipeline writes no .run record at all and a wait that must take one owes a self-deadlock check before it starts; the worktree reap moves from the iteration boundary to the dispatching session’s own turn end, scoped to the trees it minted, with the boundary refusal left as the backstop rather than the schedule; and isolation cost (3) gains the D2 composition — a sweep whose corpus includes an untracked or gitignored surface is not delegable at all to a type DELEGATION_KIT_READONLY_TYPES holds to isolation — plus a child-side clause reporting such a target as a blindness rather than a finding of absence. The header’s propagate obligation now names three passages instead of two. If you copied the template out, re-take those passages; and if your own agent definitions restate the waiting rule, they carry the retired unconditional record obligation until you propagate it.

  • GATE_SDK_PAYLOAD_WITHHOLD — new, default SPEC.md smoke: the customer payload --pack-installer builds now excludes each packed kit root’s SPEC.md and its smoke/ directory. An adopter installing from a payload therefore receives no kit SPEC.md and no kit smoke/ — the specification section behind each shipped # spec: pointer is published rather than packed, and --run-consumer-smoke refuses on such a tree, now naming that cause. Set the knob to a different member list if your spec file is named otherwise, or to an explicit empty value to pack each root whole. Hand-vendoring a kit directory still gives you both.

  • GATE_SDK_SPEC_BASE_URL — new, default empty: where a publisher publishes the SPEC sections its shipped # spec: pointers name. Empty means resolve in the tree, which is unchanged behavior for any tree holding its kit SPECs. Set it in your own knob file to have --pack-installer stamp it into the payload and init write it into a vendored tree’s gate-sdk-config.knobs. The pointer grammar is untouched — a directive still names a repo-relative path, never a URL.

  • run-gates.sh’s red output, and init’s knob seam — a failing gate now prints its descriptor’s # spec: invariant on a line of its own beneath the FAIL: line, prefixed by four spaces and spec:, resolved through GATE_SDK_SPEC_BASE_URL where that is set. A tool of yours that parses the run log by the two-space-indented PASS: / FAIL: prefixes, or by content, is unaffected; one that reads a reddened gate’s block by order and stops at the verdict now stops one line short of the remedy. And init now writes GATE_SDK_KIT_DIRS into <gates-dir>/gate-sdk-config.knobs beside GATE_SDK_NATIVE_BIN, declaring the vendored kit set rather than leaving it to the on-disk checks/-or-smoke/ predicate, which the withholding above would otherwise narrow silently. Your own lines in that file survive, and an edited seam is still reported and left alone. The --install place-artifact op takes --kits and --spec-base-url for the same two lines.

  • check-gate-assertions — its install disposition moves from zero-config to on-surface, so a fresh install no longer registers it. Its corpus in a vendored tree is your own <gates-dir>/SPEC.md and your own gates, a surface you author later; the payload withholds every kit SPEC.md, so at install there is nothing for it to read and its empty-corpus branch is exit 2. Register it in your gates.list when you author a SPEC of your own. The rule and the exit-2 wrong-directory guard are unchanged.

  • check-install-disposition assertion B — a kit shipping no smoke/install.sh is now skipped and counted on the clean line rather than reported as a finding for each of its zero-config gates; there is no roster there to be a superset of. This is what stops a vendored tree, where the payload withholds smoke/, from reddening on every kit. Its clean line gains the skip count, so a log-matching tool of yours that pinned the old wording needs re-pinning.

  • check-knob-default-coupling — a kit whose SPEC file is absent is now skipped and counted on the clean line instead of reporting one finding per knob. The assertion is drift between a source default and the SPEC that states it; a file that is not there is not a second site to drift from, and a kit whose SPEC is PRESENT but states no default still reds. This is what stops a vendored tree, where the payload withholds every kit SPEC.md, from reddening once per knob per kit. Its clean line gains the skip count, so a log-matching tool of yours that pinned the old wording needs re-pinning.

  • profiles.list’s derived full profile, and the manifest’s kits — the payload’s reserved artifact/ directory is no longer counted as a kit. full stops vendoring it into <root>/artifact/, the manifest stops recording it under kits, and the GATE_SDK_KIT_DIRS line init writes stops naming it. If an earlier init vendored it, the files stay on your manifest’s files roster and uninstall still removes them — nothing is orphaned — but a re-run at full will no longer rewrite them. Named profiles never carried it.

  • check-spec-pointer — a spec:/contract: pointer whose target is a kit root’s own SPEC file is now reported as withheld and counted on the clean line, rather than as a dangling pointer, when that kit root resolves as a directory and only the SPEC file is absent. A pointer naming a kit root that does not exist still reds, and a target that is present but missing the named §heading still reds. This is what stops a vendored tree from reddening on the # contract: headers init seeds into your workflow directory, each of which names the kit SPEC that rules its file’s format — those pointers are correct, and the document they name is published rather than packed. Its clean line gains the withheld count, so a log-matching tool of yours that pinned the old wording needs re-pinning.

  • lifecycle-kit/templates/stages/close.md and scope.md — every filing of a new deferred entry (a drain or intake →promote, and each finding close files itself) now runs an owner lookup over the queue first and records the terms searched and the result in the filing commit message; a match is stamped as a recurrence on its owner rather than filed again. A binding that restated its own duplicate check can drop it in favour of the template sentence.

  • check-queue-prose-precondition — the default QUEUE_KIT_PRECONDITION_REGEX left-bounds its phrases on a non-letter, so a trigger embedded in a longer word (appending close, delegated on) no longer reds an active entry. A trigger set you configured yourself is unchanged; wrap it in (^|[^a-z])(...) to take the same bound.

  • --emit stage-economics — a trend-log row’s date is now the latest stamp date of the stage it prices (a supervision row its iteration’s latest), never the day of the run, and every line the run writes back is re-dated, so your first run rewrites the dates of an existing log once; a re-measured row is replaced where it stands rather than moved to the end, so an unchanged re-run is byte-identical. A row no dated stamp names keeps its date and is counted on stdout as dated by measurement. If you read date as the day a row was measured, read the run header’s date instead.

  • kpi-stage-economics-lag (drift-kit) — a new bundled Lead KPI counting closed iterations stamped after the newest one your stage-economics log prices (econ <N> on the trend line), and templates/kpis.list registers it. Add it to your kpis.list copy, and if your decisions read the log, run --emit stage-economics at your terminal stage (drift-kit/SPEC.md §The stage-economics meter, Feeding).

  • run-gates.sh --enter-stage (boundary scratch wipe) — the spare test reads the scratch root’s immediate children only, and a spared directory is kept whole instead of emptied: a nested .gitkeep, or a nested file whose basename equals a LIFECYCLE_KIT_BOUNDARY_PRESERVE entry, is now deleted with its ancestors. A failed removal is reported on its own boundary-wipe could not remove from note instead of counting as wiped, and a keep-list entry containing / is named as sparing nothing. If you track a .gitkeep below the scratch root, or relied on a nested basename surviving, name the root child that holds it on LIFECYCLE_KIT_BOUNDARY_PRESERVE.

  • --emit close-surfaces, lifecycle-kit/templates/stages/close.md — each roster row gains a fifth tab-separated column, <state>: absent, empty (nothing past the # contract: header run) or non-empty for a file row, and absent or - for a <file>#<section> row. check-close-surfaces ignores it and never reds on absent. Close’s triage-sweep step now reads it, stating empty as a clean read and taking absent as a finding. A parser of yours that split rows into exactly four fields now folds the state into the owner: split on the first four tabs. If you copied the close template out, re-take step 4.

  • lifecycle-kit/templates/stages/close.md — step 2 gains a first read before the gap drain’s first bullet, and a new named slot, drain-inputs, naming the capture surfaces that read covers. A binding shim of yours reds check-skill-binding until it binds the slot: add a **drain-inputs** entry naming your knowledge-friction capture or similar (never a raw machine log), or a line saying you have none. If you copied the template out, re-take step 2’s opening.

  • lifecycle-kit/templates/stages/spec.md — gains a paragraph requiring a Definition-of-Done item that moves a queue entry to name a stage before the drain stage (or the drain stage itself, for a [drain-exempt:] entry), by its relation to the drain stage. Nothing to change on your side; if you copied the template out, re-take the paragraph.

  • guard-kit/lib/guard.sh (guard_log_fallthrough, rule 8), run-gates.sh --emit scan-prompts — the friction log now records a call as one encoded line (\, newline and tab written as \\, \n, \t) cut at 10,001 characters, and scan-prompts decodes it: a heredoc body is data to the grant test, the command after its terminator is shell, an unquoted-delimiter body expands, and a call past the harness’s 10,000-character analysis bound ranks allowlist-unreachable. A line logged before the upgrade may mis-decode until the next close clears the log. Rule 8 now blocks an inline python body that is a literal rewrite (-c or a heredoc on stdin that writes a file, calls .replace( and computes no text) and steers it to --rewrite or the Edit tool, which a committed Bash(python3 -*) grant previously let through. No action is required.

  • lifecycle-kit/templates/stages/validate.md, close.md — validate’s red triage reads a filed entry matching a red as a possible cause: a red on an item that passed at the iteration start is reproduced at the iteration-start commit before it is held, and the hold records that commit (evidence-kit’s reproduces-at=<rev>); the valve’s reason payload gains the reproducing commit, and close’s valve-disposition step carries it onto the row it lands. A binding that describes a legitimate baseline edit should name the token; if you copied either template out, re-take validate’s triage, completion and valve paragraphs and close’s step 3.

  • lifecycle-kit/templates/lead.md — the Tier each batch to its work class rule of lifecycle-kit/templates/lead.md §Economics gains an escalate-on-discovery transition: a session dispatched on a cheaper tier that finds a fix it must author journals the diagnosis, authors none of it, and escalates a re-tier, and the lead re-dispatches the rest as a same-stage session on the judgment tier rather than resuming the paused one. Name the class in your ruling-config’s escalation roster so a dispatched session recognizes it; if you copied the template out, re-take the bullet.

  • lifecycle-kit/templates/stages/close.md, lifecycle-kit/SPEC.md §The state machine and §The audit roster — close step 8 now derives an audit’s corpus from the range its scope: names, this iteration’s range unless scope: names another base, and the SPEC defines the previous-close commit (the last close stamp’s <head> in the state file as it stood at the iteration-start commit) as that other base. A roster class that must also see the previous close’s own later commits may name it in its scope:; nothing changes for a class that names no base. If you copied the close template out, re-take step 8’s range clause.

  • lifecycle-kit/templates/stages/spec.md, align.md, build.md — the authoring template gains an authoring-exit pass that runs each delta’s own predicate and load-bearing premises before commit and marks an unrunnable premise with an inferred marker on its passage (lifecycle-kit/SPEC.md §templates/stages/); align runs every not-run marker’s command, and build runs a cannot-run one as soon as its subject exists. If you copied any of the three templates out, re-take the new paragraph and the two added sentences.

  • lifecycle-kit/templates/stages/scope.md, spec.md, align.md, close.md, build.md, validate.md — a queue entry now carries the inferred marker: every filing of a new deferred entry (close’s filings and drain →promote, validate’s filed findings) marks each claim it did not run on a body line of its own with no list-item lead; scope runs the markers on each entry it promotes as debt, the authoring stage on the entry it pairs, align runs them in active queue entries as in amendments, and build runs a cannot-run one on an active entry as on a passage. If you copied any of these templates out, re-take the changed sentences.

  • kpi-deferred-age (drift-kit) — each deferred entry now contributes one defer date, its first Surfaced <date> mark else its first Filed <date> mark read off its body lines, as queue-kit defines it; a date in the section preamble, a later repeat, or an older Filed date on an entry that also carries Surfaced no longer counts. The reported oldest defer date can move later on upgrade; nothing to reconcile.

  • drift-kit gains the install-observation channel — two arms and one knob. --emit file-install [--] <kind> <field>... appends one dated line per observed install, per red a non-author hit, and per retention check-in to DRIFT_KIT_INSTALL_RECORD (new; default ${DRIFT_KIT_METRIC_DIR}/install-observations.log, so keep your metric dir gitignored — the record keys to people); --emit install-evidence emits the aggregate-only projection over it, every figure with its denominator and <id>, <profile> and <floor> never published. Nothing is owed unless you capture: the record is created by its first line, and the projection over an absent one is a counted zero rather than a failure. Closed-set fields are enforced at capture, so a mistyped verdict, disposition, behaviour, horizon or retained value is exit 2 with nothing written; a - time-to-first-green files through the -- separator. To pin the projection, commit it and add a byte-comparing freshness gate of your own — the gate is consumer-owned, drift-kit registering none.

  • guard_allow_match (guard-kit, and its compiled twin) — a closing :* in an allow rule is now read word-bounded, as the harness grants it: Bash(printf:*) matches printf alone or printf then a space, and no longer printfx or printf-x. --emit compare-settings-allow stops calling a local entry redundant against a :* grant that does not in fact cover it, and rule 20 and the --emit scan-prompts ranker stop reading a glued continuation as granted. Nothing to change in your config; re-read a redundancy or breadth report you pruned by.

  • guard_split_compound (guard-kit, and its compiled twin) — |& is now one statement separator, taken whole, so the statement after it is its own segment to every per-segment guard rule and to the --emit scan-prompts grant test rather than a segment headed by &. Nothing to change in your config.

  • guard_allow_match (guard-kit, and its compiled twin) — a closing space-led * that is an allow rule’s only * now also matches the bare command, as the harness grants it: Bash(git status *) covers git status. --emit compare-settings-allow may now report a local bare entry beside such a glob as redundant, and rule 20 stops treating that bare command as ungranted. Nothing to change in your config.

  • guard-kit grant tests and rule 15 — a segment a backgrounding & ends is never read as granted, as the harness grants no backgrounded call: rule 20 counts it as the unmatched segment (blocking where it used to decline), rules 4 and 7 withhold a rewrite’s allow, and --emit scan-prompts sections such a call allowlist-unreachable. Rule 15 no longer reads the |& separator as a backgrounded launch, so make |& tail falls through where it blocked. Nothing to change in your config.

  • LIFECYCLE_KIT_WORKTREE_LOCK_PID_RE (lifecycle-kit) — the lock-reason pattern is now compiled and captured by the gate binary’s own POSIX ERE engine rather than by bash’s =~, and the knob validator refuses at exit 2, naming the knob, the value and the rule broken, any pattern bash accepted that the engine does not admit: a GNU extension (\w, \b, \<, a backreference); two or more groups; a quantified group or one inside an alternation; and a group whose body contains an alternation. Write exactly one unquantified group in the pattern’s top-level concatenation with no | inside it, e.g. ^held by pid ([0-9]+)$. A pattern already in that shape captures the same pid as before; nothing to change.

  • checkwright doctor (and init, whose last precondition it is) — the toolchain roster’s awk member is now plain awk, no longer awk::GNU: every awk program that ships is POSIX, so a host carrying mawk, BusyBox or BSD awk no longer fails the contract with wrong-impl. Nothing to do; a host that passed before still passes.

  • doctrine-kit/DOCTRINE.md rule 3, De-literalization — gains a reach test that says which source identifiers prose may name, checked before the citation test. Reach is judged at the file: a kit’s SPEC may name any function of the library that kit ships, another kit’s underscore-prefixed helper is internal and naming it is a finding, and a native item may be named when it is visible past its module (a private one belongs to the component its nearest spec: pointer names). The digest line is unchanged, so no installed block moves. Re-vendor the doctrine. If your audit roster’s internal-identifier-restatement scope line decides internal-ness by some other test, point it at this one.

  • check-producer-liveness (evidence-kit, and the --run-validate and --enter-stage arms that share its pid predicate) — on unix the predicate now asks kill(2) directly and reads a process that exists but is not yours as alive, so it spawns neither bash nor ps: the absent-ps refusal can no longer fire there, and --needs check-producer-liveness declares nothing on unix. A native Windows build keeps the kill -0-then-ps -p route unchanged. Nothing to do.

  • drift-kit date KPIs, the overhead meter, the usage poller and --emit queue-index’s icebox cutoff — on Linux and macOS the gate binary reads civil dates in-process instead of spawning GNU date -d: an ISO day or an ISO datetime with no written zone is local time, a written Z or ±HH[:MM] applies as written, and any other shape (the free-form phrases GNU date -d also took) is no reading. Nothing to do; a host without GNU date now gets these readings too.

  • --emit overhead-meter (drift-kit) — the runner’s per-FAIL invariant line (the indented spec: line) now scores as gate rather than task or govdoc, so a session with red battery runs logs a slightly higher gate= and pct= from the first measurement after upgrade. The log’s date field partitions the series at that point; nothing to do.

  • checkwright doctor (and init, whose last precondition it is) — the toolchain roster gains curl, adopter-side and unconstrained, because the usage poller fetches its source with it: a host with no curl on PATH now fails the contract and init refuses rather than half-installing, as for a missing jq or shellcheck. GATE_SDK_PROGRAM_FLOOR’s default gains ps, so --emit port-blockers stops counting it off-floor; a consumer who set the knob keeps its own value. A consumer command’s spawn failure now names the knob or gate declaration that named it (cannot run <name> (named by <ground>): …), and a payload spawn names the program rather than its resolved path — re-pin a log-matching tool. Install curl if your host lacks it.

  • lifecycle-kit/templates/stages/build.md — the roster-floor paragraph’s declination test is now by obligation, not edit site: a build session declines a rostered update target only by naming, in the commit message, the need its citing delta places on it and the passage that already meets it; a surface with no such passage is a missed site landed in the same unit. If you copied the template out, re-take the paragraph’s closing sentences.

  • --run-consumer-smoke — a run vendoring more than gate-sdk and one other kit root now also runs each kit alone: gate-sdk by itself, then gate-sdk plus each root, each in a fresh scratch tree, printing CONSUMER-SMOKE: alone — <n> kit root(s) green alone in <ms>ms before the clean line and exiting 1 (installer failure included) on a kit that is not green alone. The default run costs roughly one more union pass. A kit of yours whose smoke/install.sh leans on a sibling kit’s install now reds; make it establish every surface its own registered gates read. gate-sdk’s own smoke/install.sh now installs templates/gates-workflow.yml and registers the check-action-* gates, which site-kit’s no longer registers.

  • gate-sdk/bin/run-gates.ps1, gate-sdk/bin/run-gates.sh — gate-sdk ships a new file: a PowerShell twin of the front-end for a native-Windows host with no bash on PATH, run as pwsh -File gate-sdk/bin/run-gates.ps1 <args> with the stub’s arguments, exits and messages. A new binary arm, --run-front-end-parity, compares the two halves over a fixed corpus and needs both bash and pwsh. The stub’s outside-a-repository refusal is now its own line alone, without the shell’s cd diagnostic before it. Nothing to do; the bash stub stays the front-end every existing caller uses.

  • --run-front-end-parity — on a Windows host the arm now runs the PowerShell twin under Windows PowerShell 5.1 (powershell) as well as pwsh, and exits 2 when either does not resolve; a divergence names the host it diverged under, and the clean line names every host that ran. Elsewhere it runs pwsh alone, as before. Nothing to do unless you run the arm on a Windows host that lacks one of the two.

  • checkwright init — on a native Windows host the follow-up block now spells both commands as powershell -NoProfile -ExecutionPolicy Bypass -File gate-sdk/bin/run-gates.ps1 …, which runs from PowerShell and Git Bash alike; every other host keeps the two bash gate-sdk/bin/run-gates.sh … lines byte for byte. --run-consumer-smoke now asserts a follow-up target executable only where the line spells no interpreter before it. A tool of yours that parsed a Windows init transcript for bash should read the target under installer/SPEC.md §init’s grammar instead.

  • checkwright doctor (and init, whose last precondition it is) and --emit env-probe — the toolchain roster drops awk and sort::coreutils: the floor predicate compares versions in-process instead of spawning sort -V, so a host without awk or a GNU sort now passes doctor, and a sort lacking -V can no longer render uncomparable. init’s downgrade refusal uses the same comparison, so a recorded or packed version carrying a prerelease suffix is no longer ordered and never refuses as a downgrade. Nothing to do.

  • context-kit/templates/session-context.sh, drift-kit/templates/kpi-deprecated-surface.sh, gate-sdk/templates/check-skeleton.sh, installer/bin/checkwright.sh — the shipped awk programs are gone: the stage read and dirty-component list use bash builtins, the KPI counts with wc -l, the bootstrap reads its digest with read, and the skeleton scans with grep -Hn (exit 1 is a verdict, 2 or more fails closed through fail_closed … grep), so its finding lines carry the matched line rather than : finding. A copy you made of the session-context or KPI template keeps working; re-copy it to shed its awk.

  • check-measured-claim, check-unmarked-claim, check-prose-tells, check-queue-slug-liveness, check-scratch-citation, check-close-surfaces and --emit ruling-staleness — a ** in the surface glob you configure no longer descends into a directory the prune set (GATE_SDK_PRUNE_DIRS plus GATE_SDK_PRUNE_EXTRA_DIRS) names, so a build rewriting target/ beside the battery can no longer fail these at exit 2 on a clean tree; a glob that names a pruned directory outright still reaches it. If one of your ** globs meant to scan inside gate-tests/, worktrees/ or another pruned directory, name that directory in the glob. check-evidence-baseline’s scenario globs are unchanged.

  • drift-kit gains --emit queue-flow [<n>], and lifecycle-kit/templates/stages/scope.md reads it — the arm prints the design-pending pool’s filed and drained counts per trailing closed iteration (default five), keyed on iteration-start commits in the state file’s committed history, plus a mean-filed row; it degrades to one n/a (<reason>) line and exits 0. Scope’s Composition: line now ends ; drain <k> against inflow <m> with <m> that figure, and the third ranking tier fills past the lead unit’s surface, one whole surface at a time, while the set drains no more than inflow. Nothing to configure: it reads the knobs kpi-queue-net-delta already does, plus DRIFT_KIT_STATE_FILE; without drift-kit the line states the figure is unmeasured.

  • lifecycle-kit/templates/stages/scope.md and lifecycle-kit/templates/lead.md — a decline of a threshold-proposed deferred entry is now written on the entry with its date and who ruled it; an entry already declined twice is escalated as operator-routed, and the lead relays it rather than declining a third time. Nothing to configure; declines recorded only in commit history before the upgrade are not counted.

  • lifecycle-kit/templates/stages/close.md — step 13 gains a new named slot, push-budget: how many pushes an iteration may spend, how many hotfix pushes a red push earns before the operator is asked, and the run a push is watched to. A binding shim of yours reds check-skill-binding until it binds the slot: add a **push-budget** entry with your numbers, or a “no remote — nothing is pushed” line. templates/stages/build.md and templates/lead.md now name the pusher: the stage session whose next act reads the run (for an [observed-by:] entry, the session landing its work, before its Done move), never the lead.

  • lifecycle-kit/templates/lead.md, templates/stages/build.md and delegation-kit/templates/agent-execution.md — a stage session’s journal path now has one source, its own --enter-stage report: the lead names no journal path in a dispatch prompt, and a batch session enters even when the cursor already names its stage. If your stage-session agent definition tells the session “the dispatch names the journal path”, replace it with “your journal is the path your own --enter-stage printed”, and decline a dispatch naming another path or suppressing the entry.

  • lifecycle-kit/templates/lead.md — the “Relay, never assert” rule widens: a relayed claim carries its tier (measured, inferred or expected) and a grant whether it is spent, a fix travels as the constraint rather than the mechanism, and a rule travels only to a role its owning surface binds. Consider giving your stage-session agent definition the receiving half: an untiered claim is read as inferred, a relayed mechanism as a proposal, a relayed rule as binding only where its owner names the receiver’s role.

  • checkwright doctor (and init, whose last precondition it is) — the toolchain floor is now conditional on the selection: jq is owed only where guard-kit is selected, curl only where delegation-kit is, and shellcheck only where a registered gate needs it (check-shellcheck, check-action-run-shell). init resolves the profile’s kit set before calling doctor; bare doctor inside an install reads the manifest’s kits and recorded registry, and outside one renders those three as not probed. A starter or prose install no longer needs jq, curl or shellcheck. Reconcile if a CI step relied on doctor refusing a host without them.

  • gate-sdk/checks/check-shellcheck.gate — its install disposition is now on-surface, so no profile’s init registers it. If you author gate scripts under your gates directory, register it by hand; doctor then owes shellcheck.

  • docs/install.md §Requirements — the macOS remedy block is now brew install bash plus the PATH ordering of Homebrew’s bin; coreutils and shellcheck left it. The Windows remedy block drops choco install shellcheck jq -y and keeps its PATH lines. No shipped adopter path needs GNU coreutils; the binary spawns date and ps only off unix.

  • checkwright doctor — inside an install, it now names each registered gate that is disarmed: a gate whose header declares # armed-by: <KNOB> and whose knob resolves empty renders disarmed <gate> asserts nothing until <KNOB> is set. check-portability-floor declares GATE_SDK_PORTABILITY_PATHS, so a starter install shows it until you name your install-path corpus there. The line leaves the exit status and the verdict line unchanged. Set the knob to arm the gate, or leave it and the line.

  • check-install-disposition assertion D — new: a checks/ member carries at most one # armed-by: line, naming a declared knob of its own kit; a second line, an undeclared name or another kit’s knob reds. The clean line gains the count of arming declarations. A gate of your own takes an arming line only where it names a declared knob of the kit whose checks/ holds it. armed-by: also joins check-comment-tier’s built-in directive roster.

  • the generated git hooks (pre-commit, commit-msg) — now POSIX sh: #!/bin/sh, no mapfile, [[ ]] or ANSI-C quoting, and each header names the gate binary (GATE_SDK_NATIVE_BIN) as the regeneration and --install-hooks door. On Linux and macOS they run under /bin/sh and no longer need bash. Regenerate your hooks (--emit git-hooks --write). A gen=manual region now runs under sh, so rewrite any bash-only construct in it before regenerating.

  • checkwright init — it now generates the hooks and the coupling graph by running the gate binary it just placed, not bash gate-sdk/bin/run-gates.sh; init spawns only git and that binary. Nothing to do.

  • checkwright init — the follow-up block now names the gate binary it placed on every host: ./scripts/checkwright-gates --install-hooks and ./scripts/checkwright-gates --run (with .exe on native Windows), in place of the bash and PowerShell front-ends. A tool that parsed the block for gate-sdk/bin/run-gates should read the target under installer/SPEC.md §init’s grammar instead.

  • checkwright doctor (and init, whose last precondition it is) — bash (≥ 4.3) is now owed only where the selection carries context-kit, delegation-kit, drift-kit, guard-kit or lifecycle-kit; a starter or prose install no longer needs it, and doctor outside an install renders it not probed — owed where any of … is selected. The probe roster’s audience field now admits several kit names joined by +. Nothing to do.

  • canon-kit/templates/canon-config.knobs — its header now points at the gate binary’s --emit knob-roster arm rather than the bash front-end. Nothing to do.

  • guard-kit’s hook (lib/guard.sh) — it no longer needs jq: every payload read and every envelope it prints now goes through the gate binary’s new --guard-json flag, so checkwright doctor never asks for jq (it is now a contributor-only member) and a guard-kit profile installs on a machine without it. A command whose text carries a CR LF is now read verbatim on native Windows too. Nothing to do.

  • guard_input_field <path> — it now takes a path (.name, [N], ["key"] steps), not a jq filter; an argument outside that grammar, such as a pipe, // or a function, reads as an absent field and the rule fails open. If a rule in your bash-guard.sh copy passes a filter, replace it with a path and do the rest in shell.

  • kit: couples under a subdirectory vendoring — where GATE_SDK_ROOT places gate-sdk below the repository root (say tools/gate-sdk), run-gates.sh --for, check-reads-couples, check-core-files’ kit: lines, --emit port-blockers, check-gate-substrate-parity and the graph artifact now expand a kit: token to the repository path the generated hook already matched, so --for selects what the hook runs and those gates stop reporting false reds. A root-level vendoring sees no change. Regenerate the graph artifact with --emit graph.

  • GATE_SDK_NATIVE_PUBLISH_WORKFLOW — takes a space-separated list of workflows, and check-gate-substrate-parity assertion F audits each one, naming the workflow in every finding and reporting each absent member. A single value you already set reads as a one-element list. If your CI builds and hashes the release-shaped artifact in a second workflow, add it to the list.

  • installer/bin/checkwright.sh — the unix bootstrap is POSIX sh (#!/bin/sh) and no longer needs bash. Run it as sh package/bin/checkwright.sh <verb>. bash … still works, since bash runs POSIX sh.

  • GitHub Release assets — each target’s gate binary now ships as checkwright-gates-<version>-<target>.tar.gz, holding checkwright-gates (or .exe) and its .sha256, in place of the flat checkwright-gates-<target> pair. Extract the archive and run sha256sum -c checkwright-gates.sha256 inside it to cross-check the digest the installer verified.

  • checkwright init --dry-run — now plans exactly the file set the real run records. It no longer names .workflow/validate-baseline.txt, .workflow/validate-evidence.txt or .workflow/WORKFLOW-STATE.txt where your tree already holds them. It now names an agent file you already hold, which doctrine-kit writes its block into, and the generated commit-msg hook where a registered gate owes one. Nothing to do.

  • checkwright init — a seed-once surface you deleted (the queue file, the agent file, the evidence manifests, the workflow-state file) is re-seeded and recorded at the hash of what init wrote. It is no longer reported as a changed file and carried at the previous version’s hash. Nothing to do.

  • gate_native_bin_spelled — gate-sdk’s lib/gate.sh gains one accessor owning how the gate binary is spelled as a command: GATE_SDK_NATIVE_BIN’s value ./-prefixed when relative, unchanged when already anchored. bin/run-gates.sh and bin/run-gates.ps1 now name the binary by that spelling in the absent-or-not-executable diagnostic, so the path you are shown is one you can run; run-gates.ps1 gains the matching Get-NativeBinSpelled. Nothing to do unless you parse that diagnostic’s path, which now carries a leading ./.

  • guard-kit/lib/guard.sh — the steer messages of rules 8, 19 and 23 no longer tell a session to run bash <vendor-root>/gate-sdk/bin/run-gates.sh; they name the gate binary GATE_SDK_NATIVE_BIN resolves, spelled as a command and resolved once per sourcing into _guard_door. The removed internal _guard_front_end had no caller outside this file. If your harness allowlist grants only the shell front-end, add a grant for the binary so the steers name something a session may run.

  • guard-kit/templates/settings-allow.json — the recommended allowlist now grants two doors, because after this release two different programs get spawned. The two front-end grants are unchanged and stay: settings-hooks.json wires its hooks to --hook arms, which keep the front end by construction, and a clone reaches it before its first build. Beside them are two new entries for the gate binary, shipped as the placeholder @GATE_SDK_NATIVE_BIN@ — a kit file cannot carry your install location without publishing it. Replace the placeholder with the value your gate-sdk-config.knobs gives that knob, spelled as a command; left unreplaced it matches no command, so you meet a permission prompt rather than a silent grant. Nothing merges any of this for you, as before.

  • Every kit README, template and knob header — each now names the gate binary through GATE_SDK_NATIVE_BIN and the arm as the arm, with no bash and no path literal; the seven <kit>-config.knobs headers take canon-kit’s landed wording. Usage blocks list arms rather than whole commands, under a lead-in naming what runs them. Nothing you configure changes; if you scripted against a README’s copy-pasteable line, it now names an arm and you supply the door.

  • gate-sdk/templates/gates-workflow.yml — the CI starter resolves the binary once, in a new resolve the gate binary step that sources gate-sdk/lib/gate.sh and carries GATES through $GITHUB_ENV, then runs "$GATES" in the later steps. If you copied this workflow, take the new step or the later steps will not resolve.

  • guard-kit/smoke/install.sh — registers check-door-binding in the scratch consumer’s gates.list, resolves the allowlist placeholder the way an adopter does, asserts the resolved door is an executable file and runs an arm on it, and exercises every grant against the installed guard from the resolved allowlist rather than the shipped template.

  • context-kit/templates/session-context.sh, drift-kit/templates/kpi-deprecated-surface.sh — both templates now reach their arms by running the gate binary directly instead of spawning the shell front-end: the session-context hook drops its RUN_GATES variable and sources gate-sdk/lib/gate.sh in-process (the library defines functions and runs nothing, so the old bash -c subshell is gone), and the KPI example’s DRIFT_KIT_KIT_ROOTS walk now locates lib/gate.sh rather than bin/run-gates.sh. If you vendored either template and retargeted its [EDIT ME] tool paths, retarget the library source line instead; a consumer copy of the session-context hook must propagate the new gate_native_bin_spelled call or check-template-copy-parity reds.

  • the bash tool-floor audience — no longer a spelled kit list. The roster element is bash:4.3::derived, and every reader — checkwright doctor, --emit env-probe, check-install-toolchain, and the install page’s toolchain bullet — resolves it by evaluating a predicate over the kit roots in hand: a kit is on the bash floor when it ships a file your host runs with bash, meaning a file outside gate-tests/ and smoke/ that carries a bash shebang, or a settings template whose hook command is spawned with a bash word. Prose telling a reader to type a bash command is deliberately not the predicate. delegation-kit and lifecycle-kit leave the audience and no kit joins it, so a host that met the old declaration meets the new one; if you selected either kit for its bash floor, doctor now skips the bash probe where no other selected kit reaches it. A kit of your own that ships such a file joins the audience with no edit anywhere — which is the under-declaration this replaces. derived is a sentinel and never a kit name; one that resolves to no kit root is reported undecided rather than not-owed, and check-install-toolchain exits 2 on it rather than comparing a page bullet against nothing.

  • a packed kit README’s own-SPEC.md link — where the packing tree sets GATE_SDK_SPEC_BASE_URL, --pack-installer now rewrites the extracted copy of each payload/<kit>/README.md so a link whose target is exactly SPEC.md (or SPEC.md#<fragment>) points at <base>/<kit>/SPEC instead of the SPEC.md the payload withholds. The fragment passes through unchanged, the tracked README and its docs mirror are untouched, and an empty base rewrites nothing — so an unconfigured packer behaves exactly as before. The pack receipt gains a resolved-link count. Nothing to do; if you publish a payload and want the links to resolve, set the base.

  • gate-sdk/templates/msg-patterns.list — gains the account-identification pattern check-commit-msg and check-tree-terms now read (see Tightened gates). init rewrites a seeded copy you never edited; if you edited yours, it is kept and now diverges from the kit’s: add the pattern’s line to your copy to take the guard.

  • gate-sdk/templates/portability-patterns.list — gains a banned construct: cd onto a git command substitution with no fallback, which bash before 5.3 takes as a silent success when the substitution prints nothing. init does not seed this list; if you copied it out as your GATE_SDK_PORTABILITY_PATTERNS, add the construct’s line to your copy.

  • delegation-kit/bin/run-budget-guard-tests.sh, delegation-kit/bin/run-dispatch-guard-tests.sh — deleted; the guard cases they drove run as the gate binary’s own crate tests beside the --hook arms that replaced the guard templates. A script or permission grant of yours naming either path must drop it.

  • delegation-kit/bin/usage-verdict.sh — deleted; the verdict rule is compiled into the gate binary and --hook agent-budget-guard calls it in process, so nothing spawns a verdict tool. A script of yours that ran it must stop (DELEGATION_KIT_VERDICT_BIN’s removal is under Renamed knobs).

  • doctrine-kit/bin/install-doctrine.sh — deleted; run the gate binary’s --install-doctrine arm instead, which init also calls in process. Re-point any script or instruction file naming the old path.

  • lifecycle-kit/templates/stages/build.md — the declaration paragraph now names removing a kit tool beside changing one, so a build that deletes a kit bin/ tool declares it by path under Behavior changes. Nothing to change on your side; if you copied the template out, re-take the paragraph’s first sentence.

  • run-gates.sh --enter-stage (boundary scratch wipe, report shape) — the boundary-wiped from <scratch>: note now names each wholly-removed immediate child once, never the nested paths deleted below it; a child left behind by a partial failure is absent from that note, and its failing nested path (or paths) still print on the separate boundary-wipe could not remove from note. A tool that parsed the wiped note for a nested path now finds only the immediate child’s own name.

  • gate-sdk/lib/test-hermetic.sh — now unsets GATE_SDK_TMP_DIR and GATE_SDK_WORKFLOW_DIR on source, so a sourcing bespoke test resolves both to their relative kit defaults against its own sandbox cwd instead of an ambient absolute value in the invoker’s environment; the unset runs before a test’s own explicit pin of either knob, since that pin sits after this library’s source line by construction, so it overrides no test’s deliberate choice. If a bespoke test of yours relied on inheriting an ambient absolute GATE_SDK_TMP_DIR or GATE_SDK_WORKFLOW_DIR, pin the value explicitly after sourcing this library instead.

  • guard-kit rule 12 — a pgrep -f/pkill -f now blocks when its pattern, read as the ERE pgrep compiles (case-insensitively under -i), matches the command’s own text, so a lone query or a loop-headed waiter that names the pattern once blocks where it passed; a quoted pattern is read whole across its blanks. A pattern that does not match its own text passes even when repeated, so a repeated bracket-trick literal no longer blocks, and -x/-v and an invalid ERE decline. Wait on the work’s own artifact or kill -0 against a recorded PID instead; nothing to change in your config.

  • doctrine-kit’s Re-verify volatile state before a git history rewrite and guard-kit rule 21 — the doctrine rule now has a session carry the commit message in the command (-m, or -F - fed by a quoted heredoc) rather than in a scratch message file another session may have written, and read a file-borne message back with git log -1 --format=%B; guard-kit rule 21’s advisory prints the same clause, on an unchanged trigger. Nothing to change unless you copied the doctrine out; re-vendor it.

  • delegation-kit/templates/agent-execution.md — a call the harness moves to the background on its timeout is now the session’s own producer: size a foreground timeout above the command’s run, write its <key>.run record from any pid it names, and hold the turn and the tracked tree until its completion notification. If your agent definitions restate the waiting rule, propagate the sentence.

  • subagent-stop-liveness — the turn-end hook also refuses while the payload’s background_tasks shows a running shell task, a call the harness moved to the background included, whatever the liveness reader said; the log grammar is unchanged, and such a refusal reads as decision=refuse beside a non-refusing verdict. Nothing to configure; a session that ends its turn on a running shell task now meets the refusal.

  • delegation-kit/templates/agent-execution.md — an isolated read-only dispatch prompt now names its return channel (its final message or the harness hand-back, never a peer send); isolation cost (4) gains a dispatcher half, so an oracle-running sweep has its arm run in your checkout and the output handed over as a file named absolute into the main checkout; a resumed isolated child is limited to re-emitting its report, since the resume runs in the main checkout, and an isolated child confirms its linked worktree at every start and resume. The header’s propagate list now names four passages: if your agent definitions restate isolation cost (3)’s child clause, propagate the resume check.

  • lifecycle-kit/templates/lead.md — a stage session that reported and ended its journal with DONE yet still reads as active is now inspected rather than waited out: the lead checks its leftover processes (waiters, shells, worktrees) and reports them. Nothing to configure; if you copied the lead template out, re-take the paragraph after the undelivered-notification recovery.

  • checkwright demo — a new adopter verb: it runs the adoption arc (install at the full profile, a green battery, a task marked done with no validate evidence caught, the claim withdrawn) from the installed package in a scratch repository of its own, and never touches the directory you run it from. It needs nothing from you.

  • run-gates.sh --projection-witness — a new arm: for every gate carrying # projection:, it perturbs a scratch copy of your tracked tree (with its history) outside the gate’s declared trigger, one file per first-path-segment-and-extension class, and exits 1 naming any perturbation that turned the gate red, which means the gate reads a file its couples= does not declare. It also prints coupled but inert lines as a report. It spawns each gate a few times per class, so run it as a validate suite rather than in the battery: add EVIDENCE_KIT_SUITES[] = projection_witness and EVIDENCE_KIT_RUN_projection_witness = bash gate-sdk/bin/run-gates.sh --projection-witness to your evidence-kit knobs to adopt it.

  • context-kit/checks/check-surface-ratchet.gate — its pre-commit trigger now follows CONTEXT_KIT_RATCHET_PATHS: when you set that knob, a regenerated hook runs the ratchet on any commit touching those paths, so a surface regrown past its ceiling is refused at commit rather than at the next full battery. The verdict is unchanged; regenerate your hooks to take it.

  • Every kit-shipped .gate descriptor naming a consumer path — couples=/trigger= literals such as TASK-QUEUE.md, CLAUDE.md, scripts/*.sh, scripts/gates.list and .workflow/* are now knob:<NAME> tokens, or the new rooted form knob:<NAME>/<glob> under a directory knob or the gates-dir locator, so each gate’s hook trigger follows the path your knobs configure rather than this repository’s layout. Triggers only widen, since each token takes the covering *-led pattern. Regenerate your hooks after upgrading — bash gate-sdk/bin/run-gates.sh --emit git-hooks --write — or check-graph stays red.

  • canon-kit/checks/check-deprecation-task.gate — now declares # armed-by: CANON_KIT_DEPRECATION_MARKERS, so where you registered it and left that roster empty, checkwright doctor names it disarmed until you set your deprecation-marker regexes. The verdict and the exit status are unchanged.

  • check-manifest-count and check-comment-tier’s count shape — the partitive exemption now reads across one line break: a total whose partitive marker (of, out of) ends the previous line of the same paragraph or comment block, with nothing but an article between it and the cardinal, no longer reds. Nothing new reds; a manifest-count-exempt: tag you added only to get past a wrapped partitive can now be deleted.

  • --emit queue-index --icebox-candidates — a recurrence: line now holds an entry off the icebox only while its newest date is inside QUEUE_KIT_ICEBOX_AGE_DAYS; an older recurrence falls through to the live-slug test, so an entry pinned by one stale recurrence now lists •. The cause prints as [recurrence] re-filed <date> — live trigger (was [recurrence] dated re-filing — live trigger). If you parse the worklist’s recurrence rows, match the new text; no knob moves.

  • check-queue-prose-precondition — each finding now prints an indented fired on: "<clause>" row quoting the matched clause (lowercased, from the rewritten body), and the help text branches on the clause’s shape: a precondition the entry waits on takes a blocker tag, a Deferred move or a past-tense rephrase; a negated clause, or one naming the entry as what another waits on, takes [precondition-ok: <cause>]. The verdict is unchanged; if you match the finding text, allow the extra row.

  • queue-kit queue format — an entry is a ### <slug> heading, a sub-task #### <slug>, with every bracketed tag on one tag line directly under the heading and the prose after it; recurrence:, roadmap-summary: and not-icebox-eligible: body lines become the [recurrence: <date>, …] array, [roadmap-summary: <sentence>] and [not-icebox-eligible: <date> <grounds>] tags; a reference to a live entry is a same-file link and one to a retired slug stays in backticks; the deferred section’s ### subsections are retired; Done stays a bare - <slug> bullet. Every queue gate and arm reads the new shape. Run --emit queue-migrate --write <queue-file> on the gate binary once, then delete any leftover deferred ### subsection heading.

  • --emit queue-migrate — a new arm converting a bullet-shape queue to the heading shape, refusing the whole file (nothing written) when the conversion would lose a slug, a tag or a done line, or when a declaration value holds a bracket.

  • check-queue-entry-budget — the declaration-line discount is gone: an entry’s size is its whole extent, heading and tag line included; an icebox entry is its heading and one sentence, and a tag line on one reds; a retired ruled: line is now refused as a [ruled:] tag.

  • --emit roadmap — each bullet now links to its queue entry, the queue path relative to the page, in place of the bold-code slug; regenerate your page.

  • --emit queue-index --collapse-deferred — prints one (top): <n> tally, since the deferred section has no subsections.

  • queue-kit/templates/TASK-QUEUE.md — the starter queue ships in the heading shape, unwrapped, one line per paragraph and every line within the default wrap budget, so either check-md-unwrapped or check-queue-wrap holds it.

  • check-queue-slug-liveness — a membership claim on a prose surface is now a markdown link into the queue file whose fragment is a slug; a bold-code token is plain emphasis and claims nothing. Rewrite a page’s claims as links.

  • lifecycle-kit/templates/stages/scope.md, lifecycle-kit/templates/lead.md — scope now tests each shortlisted candidate for supersession, grepping the deferred pool and icebox by the names its deliverable edits, and the unit-set escalation’s Recommendation carries Supersession: lines beside its Composition: line (Supersession: none — <names searched> when nothing paired); the lead returns an escalation lacking either before routing it. A scope or lead binding that restates the escalation’s lines should take the templates’.

  • lifecycle-kit/templates/lead.md — an answer that nothing changes is now relayed as one to be landed, naming the surface that recorded the question, and the stage session lands it there with its class and grounds; a question held in the gap inbox takes a resolution bullet through --emit file-gap naming the bullet it resolves. A stage-session agent file or lead binding that restates the landing rule should add the no-change case.

  • lifecycle-kit/templates/stages/spec.md — a delta whose act depends on whether a named sibling entry lands in the same build batch now states the act for each case and names the sibling, never one case in the unconditional voice. A spec binding that restates the authoring rules should take the template’s.

  • doctrine-kit/DOCTRINE.md — gains methodology rule 13, Policy-as-choice: a kit ships a calibration as a consumer-selectable set, off among them, and each consumer binds its own. The engineering-craft rules renumber from 13–26 to 14–27. check-doctrine-registration reds your agent file until it carries the new digest bullet: re-run --install-doctrine after re-vendoring. A citation of yours naming a craft rule by number now points one rule early; name the rule instead.

  • doctrine-kit/DOCTRINE.md Probe-before-assertion — the rule now binds a probe to the corpus its claim ranges over, and its digest reads “probed over the corpus the claim ranges over before it is asserted”. check-doctrine-registration reds your agent file’s bullet until it matches the new digest: re-run --install-doctrine after re-vendoring.

  • gate-sdk/SPEC.md §The knob file — a knob that selects behavior now ships a working value (a bundled bare name, a kit --emit arm through the front-end, or off), and a consumer command extends that set rather than being its only working member; a new kit-shipped adapter is an --emit arm. Nothing to change on your side.

  • canon-kit/checks/check-measured-claim.gate, check-unmarked-claim.gate, check-prose-enum.gate, check-payload-claim.gate, check-install-claim.gate — each now declares # armed-by: its command knob (CANON_KIT_MEASURED_CLAIMS_CMD, CANON_KIT_CLAIM_CLASSES_CMD, CANON_KIT_ENUM_SETS_CMD, CANON_KIT_PAYLOAD_CLAIMS_CMD, CANON_KIT_INSTALL_TRANSPORTS_CMD), so where you registered one and left its knob empty, checkwright doctor names it disarmed until you set the command. The verdict and the exit status are unchanged.

  • CANON_KIT_MEASURED_SPAN — new canon-kit knob selecting the claim a full-line measured: marker binds, which check-measured-claim arm C reads and check-unmarked-claim discharges: the paragraph below it, its first sentence, or off, which skips arm C for full-line markers and keeps arms A and B. The default is the paragraph span the gate shipped with, so no verdict moves on upgrade; an inline marker keeps binding its own sentence whatever the knob holds. Act only if your tree keeps markdown unwrapped and marks cardinal claims with full-line markers: set sentence to bind each marker to its own sentence, then fix any marker it reds by moving the measured sentence first or marking it inline. Any other value refuses at exit 2.

  • CANON_KIT_KNOB_CITATION_REACH — new canon-kit knob: how far a “default” marker may follow the knob token check-knob-citation binds it to. The default is the code-point reach the gate shipped with, so no verdict moves on upgrade. The alternatives are any other positive count, sentence (bind only within the token’s sentence) and off (stop reading the default shape, keeping the = shape). Act only if your prose sits at a different distance; any other value refuses at exit 2.

  • CANON_KIT_KNOB_CITATION_LITERAL_SPAN — new canon-kit knob: the bytes after the word “default” within which check-knob-citation binds it to a value literal. The default is the span the gate shipped with, so no verdict moves on upgrade; it takes any positive integer and has no off or structural value, since the reach’s off already stops the leg. Any other value refuses at exit 2.

  • CANON_KIT_DEFAULT_COUPLING_WINDOW — new canon-kit knob: the code points after a knob’s name within which check-knob-default-coupling reads its owning SPEC’s default statement. The default is the window the gate shipped with, so no verdict moves on upgrade. The alternatives are any other positive count and off, which skips the SPEC comparison and keeps source self-agreement, its clean line saying the comparison was not run. Any other value refuses at exit 2.

  • GATE_SDK_ASSERTION_STRENGTH_WINDOW — new gate-sdk knob: the lines after a guarded call that check-assertion-strength reads for failure text. The default is the window the gate shipped with, so no verdict moves on upgrade; it takes any positive integer and has no off, since unregistering the gate is how a consumer wants none. Any other value refuses at exit 2.

  • gate-sdk/templates/msg-patterns.list — the session-reference pattern’s value is now bounded by the token ([^ ]*, no space) rather than by the line, so it stops redding a prose lead-in that quotes a UUID later in its sentence and now also catches a URL-valued trailer. If you copied the template’s msg-patterns.list into your gates dir, re-take the changed line.

  • gate_harness_bin — new gate-sdk/lib/gate.sh accessor: inside a linked worktree with no gate binary of its own, run-gates.sh --hook/--statusline, its run-gates.ps1 twin and guard-kit’s bash guard now dispatch to the main checkout’s binary, so an isolated agent’s hooks enforce rather than decline. Every verdict-bearing arm still resolves locally and fails closed. Nothing to do, unless the main checkout’s binary is stale against a worktree’s knob files: the bash guard then refuses there at exit 2, as it already does in a main checkout whose binary is stale.

  • gate_path_rooted — new gate-sdk/lib/gate.sh function, the shell’s one absoluteness test: a leading / or \, or a drive root (C:/, C:\). gate_native_bin_spelled now treats a GATE_SDK_NATIVE_BIN opening with \ as anchored rather than prefixing ./, and guard-kit’s rule 24 now blocks a drive-rooted path in a grant’s path slot (bash C:/elsewhere/checks/check-x.sh) as it already blocked /elsewhere/…. Nothing to do unless you relied on either old reading.

  • checkwright doctor — now names check-commit-msg and check-tree-terms disarmed when no pattern file GATE_SDK_MSG_PATTERN_FILES or GATE_SDK_MSG_PATTERN_FILES_LOCAL names carries a live line, reading their new # armed-by: content … declarations. The exit status is unchanged; act only if you emptied your pattern file on purpose, or if you parse doctor’s disarmed lines — the new form ends carries a live line rather than is set.

  • Fifteen kit .gate descriptors — their # graph: couples= triggers drop literals naming this project’s own layout for knob tokens and extension covers: the check-action-* gates couple *.yml,*.yaml; check-footprint-fresh, check-surface-ratchet, check-surface-duplication and check-close-surfaces take knob:CONTEXT_KIT_SURFACES, knob:CANON_KIT_DUP_SURFACES and knob:LIFECYCLE_KIT_CLOSE_SURFACE_GLOBS; check-install-claim, check-payload-claim, check-prose-tells, check-scratch-citation, check-graph and check-producer-liveness drop transcribed or argv-supplied paths. Your hook now fires on your own configured files. Regenerate your hooks with --emit git-hooks --write.

  • CONTEXT_KIT_FOOTPRINT_FILE, GATE_SDK_ENFORCEMENT_FILE — new knobs naming the committed pages check-footprint-fresh and check-enforcement-fresh compare, which were fixed paths. The defaults are the old paths (docs/footprint.md, docs/enforcement.md), so nothing moves on upgrade; set them if your pages live elsewhere. An explicit positional still wins.

  • check-graph — a descriptor whose couples= expands to nothing because every knob: token names a knob you left empty is no longer a couples= is empty finding; the finding now reads the authored field. check-prose-tells is the stock instance: its trigger is its knob alone, and it scans nothing while that knob is empty. Nothing to do.

  • # projection: — a projection line may name its output through a knob-path token, knob:<NAME> or knob:<NAME>/<glob>, resolved to the knob’s value verbatim, and the kit freshness gates (check-enforcement-fresh, check-footprint-fresh, check-graph, check-roadmap-fresh) now do, so relocating one of those outputs through its knob no longer reds check-projection-roster. A kit: member on the line is a finding, and a line whose every member resolves empty declares nothing: check-roadmap-fresh leaves the declaring set while QUEUE_KIT_ROADMAP_FILE is empty, so drop a roster row you keyed to it on such a tree.

  • check-enforcement-fresh — its trigger now covers every file under GATE_SDK_ENFORCE_SCAN_DIR, the monitor walk’s own root, in place of .github/workflows/*.yml; and a rooted knob:<NAME>/<glob> token over a .-valued knob now expands to the glob alone rather than to a pattern matching nothing. At the default . the gate fires on every commit. Regenerate your hooks.

  • CANON_KIT_MIRROR_ROOT — new canon-kit knob, default docs: the directory --emit docs-mirror writes under, the docs-mirror freshness comparator reads and the canonical-spec finder prunes, moved together. Nothing changes at the default. If your own SPEC.md sits at docs/<dir>/, set the knob to a directory beneath your site root (docs/ref, say), regenerate the mirror there and delete the old pages. Empty, ., absolute and ..-bearing values are malformed config.

  • CANON_KIT_PROSE_TELL_ABBR_MIN_LEN — new canon-kit knob, default 3: the shortest all-caps token check-prose-tells’ undefined-abbreviation tell reads, the length the gate shipped with, so no verdict moves on upgrade. Set an integer of at least 2 to recalibrate, or off to drop that tell alone and keep the others; any other value refuses at exit 2.

  • QUEUE_KIT_PRECONDITION_PAST_REGEX — new queue-kit knob whose default is the past-tense phrase set check-queue-prose-precondition shipped with, stripped before its trigger matches, so no verdict moves on upgrade. If you added a forward verb to QUEUE_KIT_PRECONDITION_REGEX, add its past form here; off strips nothing, an empty value is malformed config, and a pattern that does not compile exits 2.

  • LIFECYCLE_KIT_AUDIT_ROSTER_LINE_CAP — now admits off, which drops check-audit-roster’s line-length assertion and keeps the grammar, class-uniqueness and stamp assertions. The default 1500 is unchanged.

  • lifecycle-kit/templates/stages/ — every stage template now reads its successor’s entry with --enter-stage --simulate <stage> after its last commit and before its journal step, so a refusal reaches the session that caused it; lifecycle-kit/templates/lead.md’s backstop sentence names that read with the journal. If you copied a stage template out, re-take it.

  • lifecycle-kit/templates/lead.md — the lead holds a capture made while a dispatched stage session is live (a gap bullet, a survey block) in its own journal and files and commits it at the first moment none is live, so a batch’s clean-tree preconditions and git add never meet a lead write. If you copied the lead template out, re-take its capture paragraph.

  • gate-sdk/lib/gate.sh gate_verdict_bin, run-gates.sh/run-gates.ps1 — inside a linked worktree with no gate binary of its own, a verdict-bearing arm now links the main checkout’s binary into the worktree’s door path and runs it, when that path is gitignored and, where the worktree tracks the crate source, the binary’s source stamp is the worktree’s; otherwise it refuses at 2 naming why and telling the session to run it in the main checkout, never to build. check-crate-arms in a linked worktree reads the main checkout’s green record on a local miss and refuses rather than runs cargo on a miss there. Nothing to do; a worktree that relied on the old refusal now gets a verdict.

  • guard-kit ignored-target test (rules 16, 17, 19 B2, 25) — from a linked worktree it now asks the checkout that holds a main-checkout target, so a journal append into the main scratch dir is granted rather than blocked, and git’s stderr no longer leaks into a block message. Every non-block rule (the grants and rewrites of rules 4, 7, 16, 17, 18 and 19, and rule 21’s advise) now declines a call rule 27 refuses. Nothing to do outside a linked worktree.

  • delegation-kit/templates/agent-execution.md — isolation now charges five costs, as its list always held. An isolated sweep over an untracked or gitignored surface is delegable: the dispatcher names the surface absolute into the main checkout, and the child reads it with the read tool and searches it with a read-only pipeline. An oracle-running sweep runs its gates inside the worktree on the stamp-matched main binary. Every agent whose run an interruption could cost, a read-only sweep included, is granted a journal, which an isolated agent appends by shell. Re-read your consumer agent definitions’ copies of the cost (3) child clauses and the journal clause.

  • guard-kit rule 2 — gains arm (d): the bash guard now blocks an export NAME=value, a leading NAME=value or an env NAME=value prefix that sets a scalar kit knob to the value it already resolves to, or to another spelling of the same file, and names the command without the prefix. Drop the prefix; a differing value is an override and still falls through.

  • guard-kit rule 27 — new, and inert outside a linked worktree: from a linked worktree, the bash guard blocks a command naming a main-checkout path outside the main checkout’s scratch dir unless it is a read-only pipeline of the read-only roster, the refusal naming the roster and the lawful routes. An isolated session reads a named main-checkout file with the harness’s read tool and appends its journal by shell under the main scratch dir; set GUARD_KIT_WORKTREE_READS = off to refuse shell reads of the main checkout too.

  • delegation-kit agent-dispatch-guard D4 and D5 — new rules, each inert until configured. D4 blocks an unisolated dispatch of any type outside a non-empty DELEGATION_KIT_MUTATING_TYPES. D5 is armed by DELEGATION_KIT_REQUIRE_TIER = on and blocks a dispatch naming no model whose type has no definition under DELEGATION_KIT_AGENT_DIR stating model:. To adopt them, bind the types that commit to your shared tree and dispatch every other type with isolation: worktree. With D5 on, name a model at each dispatch of a type that has no definition.

  • subagent-stop-liveness — the task-view refusal now counts only the emitting session’s own shell tasks: a running shell element of background_tasks refuses when its id occurs as a whole token in the transcript the payload’s agent_transcript_path names, so a dispatched child is no longer refused on a shell task its dispatcher holds (a dispatcher’s recorded producer still refuses it through the record set). An absent or unreadable transcript keeps the unscoped refusal. The log grammar, the refusal text and the exit contract are unchanged; nothing to configure.

  • guard-kit rules 14, 15 and 19 (B) — from a linked worktree, a relative GUARD_KIT_SCRATCH_DIRS member now resolves against the main checkout, the liveness record’s home: rule 14 holds a git write on a live record under the main checkout’s scratch dir, rule 15 counts a record written there and names that home in its corrective, and rule 19’s arm (B) grants the canonical recorded launch carrying it. A record written into the worktree’s own scratch dir is now refused. An isolated session records its producer under the main checkout’s scratch dir; nothing to configure. Outside a linked worktree the only change is that a record path is compared with ./ and .. folded.

  • guard-kit knob load — a library whose knob-load list names a knob the gate binary does not declare (the library updated ahead of the binary) no longer blocks every command: the hook advises naming the undeclared knob and the rebuild command, runs no rule, and lets the call take the harness’s own permission path. Rebuild or re-install the binary to restore steering. A malformed or missing knob file still blocks with the refusal’s text.

  • guard-kit rules 18 and 20, and templates/settings-allow.json — an allow entry whose only * is a closing space-led one (Bash(git show *)) now lends its head as a bare lead, which the harness already grants bare. So rule 20 blocks a decorated no-argument call such as git show > out.txt or cargo test; make with its run-it-bare steer, and rule 18 grants such a head piped into a read-only tail. A redirected GUARD_KIT_APPEND_BINS emitter taken this way is left to rule 25. The template drops its three no-argument twins: the binary, the front end and ls. Drop the no-argument twin beside each space-star entry in your own allowlist; the guard and the harness read the space-star entry alone the same way.

  • subagent-stop-liveness — a harness helper’s firing now allows on every arm. A helper is the progress-summary firing the harness makes while a dispatched session runs, and the hook classes a firing as one only when all three marks hold: agent_type empty, agent_transcript_path naming a path that does not exist, and agent_id naming no subagent element of background_tasks. The line keeps the reader’s verdict and records decision=allow, so red or corrupt beside allow marks a helper, and a task-held refusal in the log is now always a dispatched session’s own. Every firing of a dispatched session decides as before. The log grammar, the refusal text and the exit contract are unchanged; nothing to configure.

  • guard-kit’s PreToolUse(Bash) hook (lib/guard.sh, templates/bash-guard.sh) — both deleted; the hook is now --hook shell-guard, a compiled member of the gate binary carrying every generic rule with its verdicts unchanged. Rewire your .claude/settings.json’s PreToolUse(Bash) command from bash <script> to bash gate-sdk/bin/run-gates.sh --hook shell-guard, the wiring templates/settings-hooks.json now ships. Move any project rule out of your copied bash-guard.sh into a command named by the new GUARD_KIT_CONSUMER_RULES_CMD knob — a plain PreToolUse hook command of your own, speaking the harness’s own protocol on stdin/stdout/exit code, with --guard-json view <view> handing it the same quote- and heredoc-inert match surface a copied rule read off the shell primitives — then delete the copied bash-guard.sh. Honest limit: a consumer that upgrades without rewiring keeps a copied guard whose first line tests for the now-deleted lib/guard.sh and exits 0; its guard fails open silently until you rewire, and this entry is the only notice.

  • --emit enforcement-map — the Guards table now rows the command hooks of every event other than SessionStart (a SubagentStop or Stop registration is no longer missing), gains an event column, and heads a --hook <member> row with the member’s name, credited to the member’s owning kit rather than to gate-sdk’s front-end path. Regenerate your committed page with the command check-enforcement-fresh prints.

  • --hook declines — a hook member that cannot resolve its own knob now tells the session model on PreToolUse (an additionalContext advise envelope) and the operator on every other event (a systemMessage warning), and a tree with no runnable gate binary now has run-gates.sh/run-gates.ps1 --hook write a systemMessage telling the operator its hook guards are off. Before, both wrote only to stderr, which the harness keeps in its debug log. Nothing to do; a hook command of your own that wraps --hook and reads its stdout now meets that envelope on the decline paths.

  • run-gates.sh --enter-stage --dispatch <stage> --waive <reason…> — new form: an operator-ruled audit waiver is declared with it by the dispatcher (a lead, or the session the operator instructs) and written by the entering stage’s own entry, as a <iter> <waiver-token> <id> <date> <head> line immediately before its stamp; the entry report prints a body: line carrying the reason, for the stamp commit’s body. A dispatch-marker line may now carry fields after its stage. check-stage-entry’s assertion-C recovery names this form, and every state-file line it prints names your configured LIFECYCLE_KIT_STATE_FILE rather than the scratch candidate --enter-stage hands it. If you copied the align or lead template out, re-take its waiver sentence; a hand-appended waiver line still satisfies assertion C.

  • --hook workflow-state-guard (stamp-before-write) — with the new LIFECYCLE_KIT_STAGE_SESSION_TYPES knob set (default empty, so nothing changes until you set it), the workflow-state guard also refuses a Write/Edit from a dispatched stage session — a caller whose payload agent_type is on that roster — until a data line of LIFECYCLE_KIT_STATE_FILE carries that session’s id (its agent_id, normalized as --emit session-id normalizes it). A top-level session, a lead and a stage run by hand carry no agent_type and are untouched. To adopt it, set the knob to the agent type your lead dispatches stage sessions as; no settings edit is needed, the guard’s existing registration carries it.

  • --hook workflow-state-guard (state-file rule) and --rewrite — the direct-edit rule and --rewrite’s state-file refusal now resolve the state file through LIFECYCLE_KIT_STATE_FILE, as the stamp-before-write rule and every stamp writer already did, in place of GATE_SDK_WORKFLOW_DIR/WORKFLOW-STATE.txt. Nothing changes at the default, which derives from GATE_SDK_WORKFLOW_DIR. If you set LIFECYCLE_KIT_STATE_FILE to another path, the guard now protects that path rather than the one it left.

  • --hook shell-guard — now grants a recorded launch whose target, and steers a knob echo whose value, spells SQ or DQ, where it declined; and it blocks a command carrying a raw control byte other than tab, line feed or carriage return, naming the escape that spells it (printf '\x<hh>'). Act only if a command of yours carries such a byte raw: write it as that escape. --guard-json view prints nothing for such a command and prints every placeholder as its two letters, as before.

  • guard-kit/templates/settings-hooks.json — the shell guard’s matcher is Bash|PowerShell. Re-merge it, and a call through the harness’s PowerShell tool then meets these generic rules (cd_compound, scratch_redirect, git_grep, git_mutation_under_producer, git_rewrite, rm_tracked, worktree_confinement) read through a PowerShell reader, takes no guard grant, and is logged when nothing decides it.

  • GUARD_KIT_CONSUMER_RULES_CMD — under the widened matcher your consumer command also receives PowerShell payloads, and --guard-json view reads them through the PowerShell reader. Read tool_name first in any rule written for bash grammar.

  • --emit scan-prompts — PowerShell fall-throughs, logged as PowerShell, a tab, then the encoded command, print in their own advisory section keyed by command word, off the headline and --count; every Bash line and the KPI series read as before.

  • --emit queue-migrate — no longer cuts a wrapped sentence at a line that begins with bold text: a continuation line opening with a bold span starts a paragraph only as a lead-in, its own span ending in . or : or the paragraph before it ending a sentence, and otherwise joins. A queue you already converted is returned as it stands, so any split the earlier arm left there is yours to join by hand.

  • supported-platform roster — aarch64-unknown-linux-gnu joined: init on an arm64 Linux host installs a published native gate binary where it refused the host before.

  • supported-platform roster — aarch64-pc-windows-msvc joined: init on an ARM64 Windows host under Git for Windows’ bash installs a published native gate binary where it refused the host before. The host is resolved from the OS’s architecture, since Git’s shell runs emulated there and reports x86_64.

  • guard-kit rule background_no_record — now applies to the PowerShell tool: a PowerShell call backgrounded by the harness, or a detached Start-Process without -Wait, is blocked unless it writes the LF-terminated record the corrective spells, a Set-Content -NoNewline to <scratch-dir>/<key>.run whose value ends in the `n escape, the command wrapped in a try/finally that removes it; a plain Set-Content or a > record reads as corrupt. An inline while/do wait loop owes no record. On a native Windows build the liveness predicate now answers a Windows process id first, so a PowerShell-launched producer’s record reads held while it runs.

  • lifecycle-kit/templates/stages/ and lifecycle-kit/templates/lead.md — scope’s unit-set escalation states its push need against the close binding’s budget, the ruling lands a push line on each push-needing entry, build pushes under it, and close’s push needs no ask of its own. A copied template is re-taken.

  • check-commit-msg refusal help / gate-sdk/templates/msg-patterns.list — the refusal help text no longer argues against banning the Co-Authored-By trailer (it names the pattern set only now), and the template’s promo-URL comment states leaving the trailer unbanned as the shipped default rather than a rule the kit takes a side on. Banning the trailer is a consumer-selectable class (§check-commit-msg, Policy-as-choice): add your own pattern to your copy of the template for the ban, and note it also scans the tree through check-tree-terms, since both gates share one pattern source. If you copied the template or quote the old help text, re-take the comment.

  • --scratch-run — executes a snapshot of the body it echoed rather than re-reading the target, so a rewrite landing after the echo no longer runs unseen. The snapshot is a dot-led file beside the target, created per run and removed when the child exits, and the second header now reads executing <target> as <snapshot>. $0, BASH_SOURCE and $PSCommandPath name the snapshot: a script of yours that locates itself through them still finds its siblings, but not its own original name.

  • GUARD_KIT_SCRATCH_POWERSHELL — new guard-kit knob, default empty (off). Set it to pwsh or powershell, bare or as a path, and --scratch-run runs a .ps1 body under that host with -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -File, echoing it first; left empty, a .ps1 is refused. A .sh whose shebang names a PowerShell host is now refused with the corrective to name it .ps1. Setting the knob widens what your front-end grant runs, so set it in your own knob file only if you want that.

  • guard-kit rule script_interpreter — now applies to the PowerShell tool and to a PowerShell host under Bash: & .tmp\x.ps1, .\.tmp\x.ps1, a dot-sourced or command-position scratch path, pwsh -File .tmp/x.ps1 and a scratch path piped into iex are blocked, as are bash and the interpreter roster on a scratch body through the PowerShell tool. The steer names the runner’s PowerShell path when GUARD_KIT_SCRATCH_POWERSHELL is set and a bash body under the runner when it is empty. Run a scratch PowerShell body through the runner, or set the knob.

  • context-kit/templates/session-context.sh — step 8, the craft-rule block, now prints one warning line naming CONTEXT_KIT_STAGE_RULES and the exit status when that command exits non-zero, where it used to drop the block silently; the hook still exits zero. Your copy of the hook keeps the old silent form until you re-copy step 8 from the template.

  • capture writes from a linked worktree — every capture writer the gate binary carries (the guard’s fall-through log, the wakeup log, the turn-end liveness log, the wait-primitive evidence, the lesson sink’s fallback harvest file, the usage history, the overhead and stage-economics logs, the knowledge-friction log and the install-observation record) now resolves a relative path against the main checkout when it runs in a linked worktree, so an isolated agent’s capture reaches your close triage instead of dying with the worktree. An absolute knob value is used as written, and reads are not redirected. A capture writer of your own outside the binary meets the same rule with git rev-parse --git-dir --git-common-dir.

  • --emit capture-drain and the close triage templates — a new arm, --emit capture-drain [--done] [--] <log>, renames a capture log aside to <log>.drain before a close reads it and leaves the live log present and empty, so a line a concurrent session appends mid-triage is no longer erased. guard-kit/templates/close-triage.md and drift-kit/templates/close-knowledge.md now drain the friction, wakeup and knowledge-friction logs through it and remove the drain with --done, the shipped close-surface: rows name it as their reclaim, and --emit scan-prompts closes its report with the drain command rather than a : > truncate. Gitignore .workflow/*.drain and .workflow/*.drain.part (the close-surface derivation folds them into their log’s row), re-take the two templates if you copied them out, and drop any exact-string : > grant for those logs from your settings.

  • delegation-kit/templates/agent-execution.md — the in-turn liveness wait is now spelled while kill -0 <pid> 2>/dev/null; do sleep N; done, <pid> the literal number read out of the .run record, where it spelled "$pid", an expansion guard-kit refuses. If you copied the template out or restate its wait in an agent definition, respell the PID as a literal.

  • --emit <name> — every --emit member now refuses an argument outside its grammar at exit 2, where a surplus or misspelt one was dropped at exit 0: a member taking no argument refuses any, and one reading only flags refuses any other token. A member reading free-text operands — md-index and pub-index paths, the stage-rules stage, ruling-staleness names, the close-surfaces scan root — refuses a dash-led one, which -- admits, and close-surfaces refuses a second operand. Every --emit refusal of an argument’s shape now prints the member’s usage block on stderr, --help included; a refusal on anything else, such as a missing file, names its cause alone. Act only if a script or knob value of yours passes an --emit member an argument it never read — drop the argument.

  • --run-gate-tests — a named checks dir that does not exist is now exit 2 naming it, where it was dropped and every gate then reported resolves in none of:; a dash-led argument, --help included, is a refusal printing the usage at exit 2, and -- admits a path beginning with -. Act only if a fixture-suite line of yours names a checks dir your tree does not have — drop that argument.

  • --run-validate — any argument, --help included, is now refused at exit 2 before the lock is claimed, where it was dropped and a full validate run started. Act only if a script of yours passes it an argument — drop the argument.

  • --upgrade-smoke — runs from inside a linked git worktree, where it refused with not a git repository; GATE_SDK_UPGRADE_REPO must still name a work tree’s toplevel, so a subdirectory is refused. Nothing to reconcile.

  • --emit docs-mirror, --emit roadmap, --emit queue-index, --emit entry-history and --emit port-blockers — -h/--help is now refused at exit 2 like any other unrecognized argument, its refusal printing the member’s usage on stderr, where it printed the usage on stdout at exit 0; for port-blockers that holds in every position, where --help first won over whatever followed it. Every --emit member now answers --help the same way. Act only if a script of yours reads one of these members’ usage off --help at exit 0 — read it off stderr from the refusal.

  • --usage-poll, --statusline — --usage-poll now refuses any argument, --help included, at exit 2 before it polls, where the argument was dropped and a cycle ran. --statusline still ignores an argument and renders as before, and now names it on stderr. Act only if a timer, a DELEGATION_KIT_REFRESH_CMD value or a statusLine command of yours passes either arm an argument: drop the argument.

  • --install-hooks — a positional argument, bare or after --, is now refused at exit 2 before any git config is written, where it was dropped and the full opt-in ran at its normal status; a dash-led one was already refused. Act only if a script of yours passes it an argument — drop the argument.

  • lifecycle-kit/templates/stages/build.md and close.md — each now points at doctrine-kit’s Enforcement-first rule body at its discovery step: build before a mid-build defect or redundancy fix lands, close at step 1’s gap generalization. A copied template is re-taken.

  • --enter-stage pre-flight refusal help — a refused entry’s help: line no longer invites a hand-written stamp: it names resolving the finding at its source or escalating it, and states that a hand stamp is the operator’s override, which the workflow-state guard blocks from a session. Act only if a script of yours matches the old help text.

  • check-stage-entry refusal help and lifecycle-kit/templates/stages/spec.md — assertion C’s help now says that respelling an amendment body token so it stops resolving is not a remedy, and assertion D’s help says that a claim no delta rests on keeps its honest limit in prose when its marker goes. The spec template’s causal-completeness step carries the respelling line too. The verdicts are unchanged. A copied spec template is re-taken.

  • doctrine-kit/DOCTRINE.md Absence statements — gains methodology rule 14, Absence statements: state an absence only where a reader must know it was considered, and then as a token; omit it elsewhere, so an empty section is its heading alone. The engineering-craft rules renumber from 14–27 to 15–28. check-doctrine-registration reds your agent file until it carries the new digest bullet: re-run --install-doctrine after re-vendoring.

  • --emit roadmap empty horizon — an empty horizon is now its heading alone, with no placeholder line under it. check-roadmap-fresh reds a page whose committed block still carries the old line: regenerate it with --emit roadmap --write.

  • --install-doctrine link paragraph — the digest block’s intro paragraph is now written as one line where it was hard-wrapped over three, so a tree registering check-md-unwrapped stays green after an install. Nothing to do; a block you already rejoined is left as it is.

  • --with-foreign-shells — new gate-sdk contributor arm, with two new knobs, GATE_SDK_FOREIGN_PWSH_IMAGE and GATE_SDK_FOREIGN_DASH_IMAGE, each defaulting to a digest-pinned image reference: on a Linux host with Docker it copies pwsh and dash out of those images into your scratch directory and runs the command you hand it with pwsh, dash and sh (as dash) first on PATH, passing its exit status through; without Docker, or off Linux, it skips at exit 0. Nothing runs it unless you do, so nothing to do; to test a PowerShell or POSIX sh change before a push, wrap the suite that needs the shell in it.

  • check-evidence-manifest assertion C remedy — the finding for a validate stamp with no evidence line now tells you to run --run-validate on the binary GATE_SDK_NATIVE_BIN names, instead of naming this repository’s gate-sdk/bin/run-gates.sh, which an installed tree does not carry. The verdict is unchanged; nothing to do.

  • init — seeds .github/workflows/gates.yml where it is absent, a workflow running the battery through this project’s CI action at the release your checkwright.lock names and uploading each red as SARIF. update seeds it into an existing install too. If you already run the battery from a workflow at another path, delete one of the two.

  • GATE_SDK_SARIF_FILE — a new environment-only gate-sdk knob: set, the battery also writes its verdict as a SARIF 2.1.0 log to that path. Unset, nothing changes.

Upgrading

Replace the vendored kit directories wholesale at v0.26.0. Then retire each shell config the new kits replaced with a knob file: rewrite what you set in <gates-dir>/<stem>-config.sh, and in any <stem>-config.local.sh, into its .knobs file and delete it. A copy init seeded that you never edited is simply deleted. Regenerate your generated artifacts; each one’s freshness gate names its own regen command when it reds. Then run the full battery.

The allowed reds. Every gate under Tightened gates above may red, and its bullet names what clears it. They fall into two groups:

  • A gate refusing at exit 2 on a left-behind <gates-dir>/<stem>-config.sh is the knob-file migration. The phase-A step above clears it.
  • Every other red is a finding the gate could not previously see, or a new gate you chose to register. Reconcile it as its bullet states. A new opt-in gate stays silent until you register or configure 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.