Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Workflow gates registry

gates.toml in this directory is the machine-readable workflow-gates registry: the single authority for each capOS workflow gate’s name, command, what it proves, applicability (slice class / hazards / path patterns), and enforcement point.

It exists to end gate-description drift. Gate prose used to be restated across ../../CLAUDE.md, ../../AGENTS.md, ../../REVIEW.md, ../../REVIEW_CYCLE.md, ../../tools/check-workflow-hygiene.sh, per-task loopyard validation fields, and ../../tools/slice-review/slices.toml; every process change meant editing all of them. Now those places reference gates by name and read this registry.

Authority model

  • This registry is authoritative for each named gate.
  • The Makefile stays authoritative for the existence and recipe of every make target. [families.qemu-smoke] captures legacy run-* and migrated test-* focused proofs as one family rather than enumerating hundreds of concrete targets. “Gate C” below is the run-*-to-test-* test split retiring those legacy names.
  • .cargo/config.toml [alias] is authoritative for cargo host-test aliases.

Consumers

All consumers read the registry through ../../tools/workflow_gates.py:

  • ../../tools/check-workflow-hygiene.sh (make workflow-check) validates that every gate-shaped token in a commit’s Evidence: trailer resolves to a registered gate or a real Makefile target / cargo alias. The ordinary workflow base is the newest comparable merge-base from local main and origin/main. Ref refresh is limited to a detached, standalone clone whose top-level and private common Git directory match the runtime’s documented vibe-loop/main-verification/<repository-id>/repo cache shape. A linked worktree, ordinary standalone clone, release-tag checkout, or explicitly pinned WORKFLOW_BASE never mutates refs. In a recognized reusable checkout, the checker force-fetches the source repository’s main into origin/main, requires it to equal the checked-out verification HEAD, and updates the checkout’s private local main to the same commit. A missing remote, failed fetch or ref update, or mismatched tip fails the check rather than using stale containment evidence. Runtime main integration locks serialize use of the reusable checkout, so successive verifications can move these private refs to different tips without sharing a stale boundary.

    Evidence validation in that post-integration checkout discovers the candidate’s Plan-Item from the first non-empty first-parent trailer, bounds the candidate segment at the first different first-parent predecessor, and validates matching commits from the bounded ancestry. This includes merged topic commits owned by the candidate while excluding commits from other board items, including mainline work that landed between verifications. A fresh checkout therefore validates the candidate even when main and origin/main already equal HEAD, and commit acceptance does not depend on an optional Run-Id trailer. If no first-parent commit identifies the candidate, the check fails while naming HEAD and the missing Plan-Item trailer. Other checkouts validate HEAD --not main origin/main, excluding history contained by either mainline ref even when local and remote main have diverged; when neither ref exists, the ordinary base fallback is HEAD. An explicit WORKFLOW_BASE instead validates <WORKFLOW_BASE>..HEAD without refreshing refs.

  • ../../tools/slice-review/slices.toml names each per-slice check by gate = "..."; review_slice.py resolves the command from this registry.

  • tools/workflow_gates.py for-paths resolves the named gates made mandatory by paths globs for changed repository files. Its output is a lower bound; task acceptance, slice class, hazards, and focused behavior proofs can add gates that path matching alone cannot infer.

  • tools/workflow_gates.py check-registry fails when a named gate command no longer resolves to its authoritative Makefile target, Cargo alias, or repository script. make workflow-check invokes this subcommand explicitly through workflow-gates-test. The complete named-registry scan includes every path-mandatory gate.

  • make workflow-check rejects a branch that adds a new literal run-* rule target to the root Makefile. run and run-display are the only operator entrypoints; focused proofs use test-*. The check compares target sets parsed by the workflow-gates resolver with the merge base, so prerequisite edits, target-specific variable assignments, block moves, and run-* to test-* renames do not mistake existing Gate C debt for a new target. Multi-target and variable-headed rules use the resolver’s normal semantics; pattern rules do not represent concrete targets.

  • make workflow-check runs ../../tools/run_target_inventory.py as a failing Gate C debt ratchet. It derives every current literal run-* target from the root Makefile, including literal targets that share a rule head with variable-expanded targets, and treats every name except the run-display operator entrypoint as migration debt. Rules whose recipes only invoke QEMU remain debt. The inventory groups debt by the first component after run- and labels additional recipe or prerequisite evidence such as harnesses, assertions, and timeout wrappers. run-target-budget.toml records a grandfathered name set and count. A normal refresh writes the exact current set and matching count; accepting a lower count is defense-in-depth against a hand-edited allowlist expansion that does not also raise the recorded ceiling. The check fails when a non-compliant name is absent from the set or the total exceeds the ceiling, while removals pass against a stale baseline. make gate-c-target-inventory-refresh records only removals and refuses a larger set or count. A Makefile or baseline parse failure exits 2: an absent or unreadable inventory must not look like a zero count.

  • make workflow-check runs ../../tools/run_target_artifact_logs.py as a debt ceiling over stale target/run-*.log artifact paths still written by migrated test-* recipes. The tool reuses the Gate C Makefile parser and records the sorted unique path set and count in run-target-artifact-log-budget.toml. New paths or a count above the baseline fail; removals pass without changing the baseline. The check also compares against the workflow merge base, or the budget-introduction commit when the merge base predates the budget, so editing the path list and count cannot admit new debt. Run python3 tools/run_target_artifact_logs.py --update-baseline to record only reductions. workflow-gates-test, a prerequisite of make workflow-check, exercises the documented refresh command and rejects documentation drift. This ratchet contains deliberately deferred naming debt; it does not perform or authorize the coordinated artifact rename.

  • make workflow-check audits command-form make <target> citations in every Git-tracked or non-ignored untracked Markdown and Typst file, plus direct .yml and .yaml files under .github/workflows/. It also audits comments and string literals in tools/**/*.sh, tools/**/*.py, tools/**/*.js, tools/**/*.rs, and capos-js/**/*.mjs, without treating executable Make invocations or ordinary uses of the verb “make” as citations. Git-ignored generated aggregates and nested workflow files are excluded. Inline-code and fenced-code commands may use leading Make options and variable assignments; prose beginning with “make” is not a command citation. Workflow shell lines also recognize wrapped Make invocations and command substitutions. Path exemptions record the Python release workflow because its Make invocations build an external Cap’n Proto source tree, and the checker’s regression suite because it embeds deliberately invalid citations as fixture data. The checker imports the Makefile parser from ../../tools/run_target_inventory.py, fails every reference to a missing target, and also fails verification instructions that resolve only to a retirement diagnostic followed by exit 2. References to live noncompliant run-* targets remain Gate C migration debt and emit warnings grouped by target. Their non-exempt per-target counts are pinned in run-target-citation-budget.toml; an increased count or the first citation of an unlisted debt target fails, while removals pass against a stale baseline. The workflow check also compares the working budget with the Git baseline so a hand-edited increase cannot admit new debt. Run python3 tools/check_doc_make_targets.py --repo . --update-baseline to record only reductions. New current instructions link to the existing command-owning page instead of repeating a debt-target citation. If a new location must carry the runnable command, migrate the target to its compliant test-* name through the Gate C work first, then cite that name; do not raise the budget or create a historical exception for a current instruction. doc-make-target-exceptions.toml is the only exemption authority: it exempts docs/changelog.md as historical record and records other historical-evidence or reconcile-owned exceptions as exact path:target entries with a task owner. An entry that no longer matches any citation fails as unused, so exceptions are removed with their owning reconciliation rather than becoming permanent filename allowlists.

  • tools/workflow_gates.py check-vocab <entries...> validates the gate-shaped tokens in a loopyard task validation field against the registry (free-form ad-hoc validation commands pass) for a loopyard-side adapter or hook.

Runtime enforcement

The capOS host configuration defines a static universal floor equal to the prerequisites of make check. Each prerequisite is a separate completion command so a newly resolved runtime contract can record one gate_result per check. The ordered [runtime].universal_gates list in gates.toml is the tracked authority that maps each opaque completion.commands[N] key to a named gate and its command. tools/workflow_gates.py runtime-list renders that mapping; prose does not restate it.

tools/workflow_gates.py check-runtime-config checks that the operational configuration’s completion commands resolve exactly from that tracked list, that pre-review orchestration.gates and post-integration orchestration.verify_on_main contain the same derived keys, and that the named gates equal the make check prerequisite set. make workflow-check runs this comparison when an operational config is present. A warm reference run of the aggregate took 326.47 seconds and 1,619,720 KiB peak RSS; the configuration accepts that universal cost twice.

Runtime contracts are immutable per run. A configuration change applies only after the runtime reloads it and resolves a new contract; it does not add gates to an already recorded contract. To credit the universal floor, compare the contract source config with the tracked registry using check-runtime-config --expected-digest <contract-source-digest>, require the derived keys in both gates and integration.verify_on_main, and require a passing result for every pre-review key. A narrower or digest-mismatched contract remains narrower.

Loopyard task validation values are advisory requirements for the worker and reviewer, not executable runtime input. They are descriptive task data and may contain free-form commands, while runtime gates must be static references to allowlisted completion commands. Workers resolve registered names through this registry, add task-, hazard-, and behavior-specific gates, and supply their evidence for review. Reviewers must verify that evidence; even a complete universal gate set does not imply that the task-specific list ran.

Loopyard vocabulary contract

Loopyard reads this same file – there is no second registry – but it accepts a narrower vocabulary than tools/workflow_gates.py does. _load_validation_gate_registry reads only the [gates] and [aliases] tables; it does not expand [families.qemu-smoke] and does not consult the Makefile. So a bare run-* / test-* token is a valid Evidence: trailer while being an unregistered validation value, and a proof a task record needs to cite has to be a named gate here.

Loopyard keeps a synchronized snapshot of that vocabulary – the name list plus a digest over it – in its database, and refuses to execute task gates while the snapshot and this file disagree. The validation field is a control field bound to that snapshot, so a task write naming an unregistered value is rejected outright. Two consequences for anyone editing [gates] or [aliases]:

loopyard project control-value audit-validation-gates -p capos <repo>   # classify, no writes
loopyard project control-value sync-validation-gates  -p capos <repo>   # file -> database
  1. Renaming or removing a gate is not complete until the snapshot is synchronized; until then audit-validation-gates reports registry_current=false.
  2. Synchronize after re-pointing the task records that name a value being retired, never before. The sync replaces the whole name list atomically, and every open record still holding a retired value then fails its next control write – including the ready transition that would dispatch it.

The new-run-target and Markdown-target checks are high-confidence hard failures, not reviewer heuristics. Existing unresolved future-design citations are not converted into a hidden allowlist: the Markdown check compares the repository-wide cited-target set with the merge base and prevents the unresolved set from growing.

QEMU proof outcome contract

../../tools/qemu-proof-outcome.sh is the shared classifier for completed QEMU proofs whose only passing isa-debug-exit status is 1. Harnesses that adopt it invoke it before their own assertions. It delegates the exact startup-stall signature check to ../../tools/qemu-startup-stall-guard.sh, which is also the companion guard for harnesses that intentionally accept timeout status 124 after reaching their success marker.

qemu-proof-outcome-adoption.toml is the machine-readable authority for the adoption state of every QEMU harness reached from a test-* target. It records status-1-only, accepts-124, and exit-0 classifications. The unclassified list is grandfathered migration debt. make workflow-check follows literal prerequisites and recursive $(MAKE) calls, rejects debt growth relative to the workflow base (or the registry-introduction commit when the base predates the registry), and make qemu-proof-outcome-adoption-refresh records only removals. The checker derives each classified harness’s contract from helper invocation and executable QEMU-status handling, then requires the recorded class to match. A derived status-1-only harness must invoke qemu-proof-outcome.sh. An accepts-124 harness must invoke qemu-startup-stall-guard.sh before its status and transcript assertions for every status-bearing QEMU boot whose accepted outcomes include 124. The checker also applies that accepts-124 guard rule to legacy tools/qemu-*.sh harnesses outside test-* reachability, without adding those harnesses to the adoption registry.

The helper returns 2 for invalid invocation and 3 when status 124 accompanies an existing, empty kernel UART log; exit 3 means the proof did not start observably and must be rerun, not that its assertions failed. All other outcomes fall through to the harness’s status and transcript assertions. Proofs that intentionally accept status 124 use the companion guard rather than the status-1-only helper.

When that exact status-124/empty-UART signature fires, the guard appends one JSON Lines record to ../../target/qemu-startup-stall-diagnostics.jsonl before returning 3. The record has schema version 1 and contains the UTC timestamp; proof name derived from the kernel-log basename; kernel and terminal/QEMU log paths and byte sizes; stdout/stderr TTY booleans; the host’s three load-average values and online CPU count; /dev/kvm presence and read/write accessibility; and the first qemu-system-x86_64 --version line. Unavailable facts are JSON null. The retained tail is lock-protected and limited to 256 records and 256 KiB; diagnostic-capture failure does not change the established exit code or message. Non-stall outcomes do not create or modify the artifact.

Summarize the retained artifact without running a proof:

python3 tools/qemu_startup_stall_report.py
python3 tools/qemu_startup_stall_report.py --json
python3 tools/qemu_startup_stall_report.py path/to/diagnostics.jsonl

The default path is target/qemu-startup-stall-diagnostics.jsonl. A missing or empty artifact reports zero stalls. The report includes totals by proof, first/last timestamps, stdout and stderr TTY posture, KVM presence and accessibility, QEMU versions, and one-minute load average normalized by the recorded online CPU count. The fixed load buckets are less than 0.5, 0.5 to less than 1, 1 to less than 2, and at least 2 per CPU, plus unavailable. --json emits the same report with sorted object keys.

The reader accepts only the guard’s schema version 1 record shape and enforces the same 256-record and 256-KiB ceilings. It rejects malformed, structurally invalid, unsupported-schema, or changing input rather than producing a partial summary. It reads only the selected artifact and does not modify it. The report describes conditions recorded when stalls occurred; it does not establish that TTY posture, KVM access, host load, QEMU version, or any other recorded condition caused a stall.

The standing recovery instruction remains a serialized rerun without a PTY. The guard does not retry automatically: the artifact is diagnostic evidence for resolving the startup mechanism, not authority to alter a proof’s pass/fail contract.

Using the reader

python3 tools/workflow_gates.py list                       # registered gates + families
python3 tools/workflow_gates.py resolve run-smoke          # -> make run-smoke
python3 tools/workflow_gates.py for-paths docs/status.md    # path-mandatory gates
python3 tools/workflow_gates.py check-registry
{
  git diff --no-renames --name-only "$(git merge-base HEAD main)"
  git ls-files --others --exclude-standard
} | sort -u | python3 tools/workflow_gates.py for-paths --stdin
python3 tools/workflow_gates.py check-evidence "make run-net; git diff --check"
python3 tools/workflow_gates.py check-vocab "make workflow-check" run-smoke
python3 tools/workflow_gates.py runtime-list
python3 tools/workflow_gates.py check-runtime-config <contract-source-path> \
  --expected-digest sha256:<contract-source-digest>

Path globs use repository-relative POSIX paths. *, ?, and character classes match inside one path component; a complete ** component spans zero or more components. Output is sorted as <gate-name><TAB><command> so both agents and scripts can consume it without scraping prose.

Named gate commands use one of three auditable forms: make <target>, cargo <alias>, or a repository-relative script path, optionally preceded by python3, python, bash, or sh. Make and Cargo commands name exactly one target or alias; a Make target must have an explicit rule head because a stale .PHONY declaration alone exits successfully without running a recipe or prerequisite. Script commands may carry arguments after the path. A deliberately aspirational entry must carry a non-empty liveness_exemption reason in gates.toml. The exemption is not a compatibility alias and must not preserve a command after its implementation authority is renamed or removed.

../../markdown-links.toml declares the reader entrypoints and maximum link-hop bound used by ../../scripts/check-md-links.py; its optional exclude list names generated Markdown paths that are not source documents. The checker validates relative Markdown targets and requires every working-tree Markdown file under docs/ to be reachable from a configured root. Its staged mode builds the graph from the full working tree but limits broken-link and orphan reports to staged Markdown files, so an unchanged index can make a newly staged page reachable without making unrelated existing files part of the pre-commit result. make workflow-check separately runs the full-tree mode so deleting an index link cannot hide an unchanged subtree from pre-review enforcement.

Reachability follows Markdown links, not directory depth. An orphan diagnostic names the closest reachable document in the undirected link graph when one exists, providing a likely index to connect without treating reverse links as reader reachability.

Documentation budgets

doc-budgets.toml is the repository-specific authority for Markdown size ratchets and structural prose-run ceilings. make workflow-check reads it without modifying it. make doc-budget-refresh is the explicit write path: after documentation shrinks, it lowers size baselines toward their targets and grandfathered structural baselines to the measured values. A size baseline stops at its target, and refresh never raises either kind of baseline. A required increase is therefore a direct, reviewable edit to the TOML file.

Before each deadline, content between a target and its recorded baseline warns; growth above the baseline fails. After the deadline, the target is the hard limit. Structural checks warn above 4,000 bytes of unbroken prose and fail above 12,000 bytes unless the file has a recorded lower-only grandfather baseline. Fenced code, lists and their indented continuations, tables, and blockquotes do not contribute to that prose measurement.

The staged pre-commit check evaluates only size-budget groups intersecting the staged paths and structural limits for staged Markdown files. A budget past its deadline therefore blocks changes to its governed documents, not unrelated source-only commits. Staging doc-budgets.toml evaluates the complete configuration and corpus.

Documentation source line width

md-line-length.toml bounds how wide a source line under docs/ may be, so a documentation edit diffs as words rather than as a whole-line replacement. ../../tools/check_md_line_length.py runs from make workflow-check and from the staged pre-commit hook; make md-line-length-fix is the rewrap path and make md-line-length-refresh the baseline write path.

The line limit applies only where Markdown can express the break. The checker reflows a candidate paragraph itself and reports a line only when that reflow lowers the paragraph’s widest line, so a width forced by one unbreakable atom – a long URL, an inline code span, a whole inline link – is neither reported nor rewritten. Reflow refills at the paragraph’s own widest conforming line rather than at the ceiling, so a paragraph wrapped at 80 columns stays there. A continuation line never begins with an atom that would start a different block, so a rewrap cannot turn prose into a spurious bullet, heading, or quote.

Code is exempt outright, because its content is verbatim. That covers a fence at any indentation, including one nested inside a list item, and an indented code block four columns past the enclosing item’s content column. The checker tracks open list items for exactly that reason: without them a six-column list continuation and a six-column code block are indistinguishable.

Two line kinds cannot carry a source line break and are measured against their own ceilings with per-page baselines instead: pipe-table rows, which GFM gives no row continuation, and front-matter lines, which the parser in ../../tools/mdbook-doc-metadata/mdbook-doc-metadata.js reads one at a time so a folded scalar is truncated at the break. make md-line-length-refresh lowers a baseline to the measured width and seeds one for a page that has none; it never raises an existing entry, so widening an already-baselined table or front-matter value fails until it is restructured or shortened, or the TOML is edited directly for review. A baseline naming a file outside the configured scope fails, so an exemption cannot outlive the line it was granted for. docs/AGENTS.md states the authoring rule and the reasoning behind each exempt category.