gate-sdk

A self-testing lint framework for the surfaces conventional linters ignore: markdown specs, glossaries, task queues, config projections, diagrams — any text whose drift is mechanically decidable. Built for repos where coding agents do the writing: a stateless agent session cannot be trusted to remember conventions, so the conventions are enforced by machine-run gates that block the commit instead.

A gate is a small program checking one invariant across one or more governed surfaces. The kit ships the machinery that keeps a gate family honest:

  • lib/gate.sh — the one sourced helper: the fail_closed wrapper (a crashed parser must never read as “clean”), the fixture-tree prune adapters, and the registry/resolution helpers.
  • bin/run-gates.sh — the aggregate battery: every gate in your gates.list, one shot, per-gate timings. Two selectors narrow it without losing the config bridge or the output contract: --only <name>... runs the gates you name (in registry order; an unregistered name is a refusal), and --for <path>... runs the gates coupling to the paths you name, exactly as the generated hook would.
  • bin/run-gate-tests.sh — the golden-fixture runner: every gate proves it accepts a good/ case and rejects a bad/ case with the right error text.
  • bin/run-consumer-smoke.sh — the end-to-end check no fixture makes: builds a fresh scratch consumer, runs each vendored kit’s smoke/ installer, and asserts the battery is green under zero config (then red on each kit’s crafted violation). Each kit ships a smoke/ directory to join the party.
  • bin/gen-pre-commit.sh + bin/install-hooks.sh — the pre-commit and commit-msg hooks generated from per-gate # graph: coupling manifests (tier=precommit vs tier=commit-msg); adding a gate to a hook is manifest-only, so hook membership cannot drift.
  • bin/build-native.sh — the one spelling of the crate build for the binary substrate: resolves the crate from GATE_SDK_NATIVE_CRATE, passes trailing arguments to cargo (so a per-target build reuses it), and returns cargo’s own exit code. Every reader of that command cites this script rather than copying it.
  • bin/port-blockers.sh — the port report, in three exclusive arms over two corpora. The default arm gives, per registered gate, the external programs its rule requires beyond GATE_SDK_PROGRAM_FLOOR — tokenized out of a shell rule, or read off the binary’s --needs for a member that has ported; --group partitions the still-shell members by derived corpus derivation, largest group first, with the mechanically derivable port criteria beside each member. Those two walk the gate registry and so answer for the battery alone, and each counts what it could not decide. --tree walks the tracked shell tree instead and gives each non-test script its port disposition — owed, no-port or port-until:<slug> — with a trailer whose owed count is the project’s completion predicate; it has no undecidable class, an undeclared file being owed. All three derive from the tree rather than from a list anywhere. Advisory — never a gates.list member; nothing parses any arm’s rows, and the one machine-read line is --tree’s trailer, which a consumer’s measured-claim emitter may read.
  • checks/ — the meta-gates that hold the family to its own standard: ShellCheck self-lint, the output contract, the fail-closed contract, fixture coverage, SPEC↔code assertion coupling, exemption-list hygiene, and manifest / hook / graph-artifact freshness (check-graph).
  • templates/check-skeleton.sh — the copy-paste skeleton a new gate starts from; templates/gates-workflow.yml — the CI workflow a consumer copies to .github/workflows/gates.yml.

Enforcement runs in three concentric tiers, each an outer backstop for the one inside it: the generated pre-commit hook, the local and bypassable inner tier; run-gates.sh’s pre-push full battery, whole-tree before the work leaves the machine; and the CI workflow, the server-side backstop that catches a --no-verify or a clone that never opted in. Only CI is a guarantee — see SPEC.md §Enforcement tiers for the full definition, including the deferred hosted-attestation rung beyond these three (so CI cannot be edited away in the same change) that is out of scope here.

The design contracts, the manifest grammar, and each component’s full contract live in SPEC.md.

Quick start

Vendor the kit into your repo at gate-sdk/, then:

mkdir -p scripts                     # your gates dir (GATE_SDK_GATES_DIR to relocate)
cat > scripts/gates.list <<'EOF'
# kit meta-gates (resolve from gate-sdk/checks/)
check-shellcheck
check-gate-output
check-gate-fail-closed
check-gate-fixture-coverage
check-gate-exemption-tasks
check-gate-assertions
check-graph
EOF

mkdir -p .workflow docs
bash gate-sdk/bin/gen-pre-commit.sh --write                       # generate the hook
bash gate-sdk/bin/run-gates.sh --emit graph > scripts/CHECK-GRAPH.html   # the coupling graph
bash gate-sdk/bin/run-gates.sh --emit enforcement-map > docs/enforcement.md # the enforcement map (regenerate on any class-registry change)
bash gate-sdk/bin/install-hooks.sh                                # opt in this clone

bash gate-sdk/bin/run-gates.sh                                    # the full battery
bash gate-sdk/bin/run-gates.sh --only check-graph                 # one gate's verdict
bash gate-sdk/bin/run-gates.sh --for scripts/gates.list           # the gates coupling to a path
bash gate-sdk/bin/run-gate-tests.sh gate-sdk/gate-tests gate-sdk/checks  # the kit's own tests

Write your first gate by copying gate-sdk/templates/check-skeleton.sh to scripts/check-<area>.sh, editing it, adding check-<area> to scripts/gates.list, and shipping a scripts/gate-tests/check-<area>/{good,bad}/ fixture pair. The meta-gates will hold you to the rest.

Gates

The Quick start registers the subset a new consumer begins with; gate-sdk ships the full meta-gate roster below, each contract in SPEC.md:

check-shellcheck
check-gate-output
check-gate-fail-closed
check-gate-fixture-coverage
check-gate-assertions
check-gate-substrate-parity
check-gate-binary-fresh
check-crate-arms
check-install-disposition
check-test-hermetic
check-assertion-strength
check-gate-exemption-tasks
check-graph
check-reads-couples
check-enforcement-fresh
check-kit-enum
check-kit-registration
check-readme-roster
check-smoke-entry-guard
check-core-files
check-identity
check-hook-exec-bit
check-exec-bit
check-root-tiering
check-workflow-tiering
check-action-pinning
check-action-run-shell
check-action-gh-repo
check-action-permissions
check-commit-msg
check-commit-subject
check-tree-terms
check-template-copy-parity
check-template-registry-parity

Requirements

The toolchain contract lives in docs/install.md §Requirements: the roster, each pinned floor, and the construct that forces it. check-install-toolchain holds that page to context-kit/lib/toolfloor.sh, the roster’s owner, so the requirement has one statement and no copy.

License

Apache-2.0 — see the repository root.