Skip to content

CI guards

A family of small scripts, each watching one rule, that run on every pull request. If any of them fails, the change cannot merge.

The gate and Landlock stop a bad action while it happens. The CI guards stop it one step later: when the change tries to enter the main branch. Each guard is a separate script in scripts/, written to check exactly one thing, and each is wired into .github/workflows/operator-authority-checks.yml as a required check. GitHub’s branch protection (see CODEOWNERS) refuses the merge until every one of them passes.

Job What it stops Escape hatch
policy-consistency The machine rulebook (AGENT_POLICY.yaml) and the human one (PROTECTED_PATHS.md) drifting apart. It loads the YAML and extracts every backticked path from the Markdown, then compares. None — fix the drift.
changelog-updated Merging a change without a CHANGELOG.md entry (HARD RULE 8). None.
sidebar-no-removal The dashboard sidebar quietly losing links, or new links being hidden behind adminOnly / feature flags (HARD RULE 7-bis). It counts the hrefs — the count may never go down. Write HR7-bis: confirmed in the PR description.
operator-authority-tests Breaking the enforcement code itself. The full test suite for the gate, Landlock, manifests, the sweep, the shims and the check scripts must pass. None.
moth-no-write The worker-moth agent gaining any write capability (ADR 0115). A scanner looks for write operations in its code paths. None.
invariant-fitness-tests Breaking a numbered architecture invariant. Each invariant has a matching automated test (ADR 0118). None.
canonical-containers A new container appearing in a compose file without anyone deciding it should exist. New services must be on the allowlist in the policy. Commit message token [allow-new-service].
no-agent-workflow-edits An agent editing the CI configuration that judges its own work. PRs authored by the bot accounts (cnysoho, cnry-morpheus) may not touch .github/workflows/. Commit message token [allow-workflow-edits].
pr-size-cap Pull requests too large to review honestly. The cap is 1,000 changed lines. Commit message token [allow-large-pr].
existing-invariants Three older standing checks in one job: no OpenAI imports outside the adapter, no secret materialization patterns, and no legacy secret loaders in startup scripts. None.

A few scripts in the same family run on the host or on demand rather than on pull requests:

  • check-process-secret-exposure.py — a runtime audit. It walks the environment and command line of live processes (and the env of every container) looking for secret-shaped values. It reports where a secret leaks, never the value itself, and keeps a small allowlist for harmless matches like broker URLs.
  • check-release-versions.sh — keeps the version number in CHANGELOG.md, VERSION, pyproject.toml, __version__ and package.json in lockstep, so a release can’t ship half-bumped.
  • run-fast-path-regression-gate.sh — a performance tripwire: if message-ingestion latency regresses past a threshold, the run fails and the regression is on record.

Every guard here repeats a rule that already exists somewhere else — in CLAUDE.md, in the invariants, in an ADR. That repetition is the point. The write-time layers can be bypassed by an agent running somewhere unexpected; a person can make the same mistake an agent would. The merge is the one chokepoint everything passes through, so the rules are checked once more there, by code that doesn’t get tired or distracted.