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

Development

The toolchain version is pinned in rust-toolchain.toml, today at 1.97.0, unsafe is forbidden in every crate, and the lint table denies panics, lossy casts, wildcard matches, and undocumented errors. The version in that sentence is this book’s first live value claim: the definition below pins the pinning line itself, so a toolchain bump that forgets this page fails the repository’s own gate with the corrected expectation in the finding.

Gate-tool versions live in one place, the workspace.metadata.tools tables of the root manifest. Runtime consumers use Cargo’s projection directly: the CI tools composite and ratchet hooks query cargo metadata with jq. The pinned tool names and versions key a shared cache warmed by main; jobs install only their requested tools on a miss, and pull requests never save partial benches. The agent lanes use the same composite and gh-aw’s native Copilot installer without private CLI caches. A documentation-contract test parses the manifest independently and refuses any workflow spelling a declared tool at another version. Bumping a tool is one edit, except cargo-about: a release restores no cache, so the release job and the self-scan that mirrors it spell its version in the workflow, and that test holds both to the manifest.

Hooks run through prek: formatting and the cheap checks on commit, then Clippy with warnings denied, the full test suite, cargo deny, cargo shear, and a similarity-rs twin-function gate on push. The tool compares functions within one file, so a file that did not change keeps its edges, and the gate scans only the Rust files that differ from the base commit, in both trees. It also concatenates the deliberately parallel provider transports, lane-test harnesses, service runtimes, and verification files, and compares those when one of them changed, mapping the generated lines back to their source paths. Pair identities are stable and follow renames. Every candidate edge must already exist in the base set, so removals pass while new relationships, including an equal-count remove-and-replace, fail. Nothing is cached, and no baseline or allowlist lives in the tree. A last push-stage hook runs cargo-sweep over target/, dropping artifacts and incremental sessions older than two days; cargo never collects superseded builds, and this repository mints a fresh copy of every test binary on each lockfile or version change. Five days held 86 GB and the sweep reclaimed nothing from it, because every generation was inside the window. The hook is a no-op where cargo-sweep is not installed. Code CI runs the same two hook stages. What CI adds on top is the work that does not belong on a developer’s machine: the fuzz packages’ release runs, whose release builds cost minutes, and mutation, which costs ten of them for a code change. A push should not buy what a pull request already measures.

CI also runs the module-cycles lint through Dylint, pinned by commit under workspace.metadata.dylint. It reports sibling modules that depend on each other in a cycle, where a module counts together with everything nested in it and a parent and its own child are never compared. The workspace has none since #1133 was worked through, so the job denies the lint and a new cycle fails it.

The shared change detector compares Git revisions, including renames and deletions. Documentation-only changes retain the cheap hooks, documentation contracts, book build, and self-scan, but skip unrelated platform, coverage, fuzz, packaging, and mutation builds. Reviewer-only changes retain the cheap hooks and Actions analysis. Rust, fixtures, schemas, build configuration, and unknown paths take the full code lane. The required workflow always starts, and gates fails if classification fails; there are no workflow-level path filters. Weekly and manual CI runs force full code validation. The platform and coverage suites include the JSON, generated-report, and documentation contracts. The separate contract job runs documentation contracts on documentation-only changes.

The commit hooks also reject handwritten Serde implementations, callbacks, trait-bound overrides, and custom JSON representation guards. Models use derives and library adapters; domain checks remain separate. See Architecture for the standard Serde input behavior and the canonical representation required by authenticated boundaries.

Tests use the production wire models too. Load published examples into those types, mutate typed fields for domain cases, and call the production validators. Raw JSON mutations and independent byte fixtures cover representation and malformed-input cases; they do not need parallel wire structs or test-only encode/decode helpers.

Coverage executes the whole workspace with instrumentation and keeps the 85% line floor, but excludes the three fixture-only packages from the report. It does not replace uninstrumented platform tests or the release-mode eligibility checks. Rust caches remain separate across platforms and build settings; disposable CI runners remove their unused floating stable toolchain before restoring them so runner image updates cannot invalidate the pinned-toolchain key.

The test profile strips debug information from executable fixtures, which are repeatedly copied and verified. Symbols and assertions remain; source-line backtraces can be restored locally with CARGO_PROFILE_TEST_STRIP=none. Every dependency is optimized in development and test builds, since dependencies are cached and most tests spend their time in them. The workspace crates are not: a package override reaches the package’s test binaries too, and those are what a build waits on. Debug assertions and overflow checks stay on, and release settings are unchanged.

Compiling the tests costs far more than running them, so platform CI builds the binaries in one step and runs them in another: a slow run says which half it was. The build is throughput bound on a four-core runner, so each platform splits in two. The libraries half builds the engine’s library crates; the programs half builds the CLI, the bootstrap wrapper, and every package under controller/ and api/, whose tests start a process per case. A new crate lands in the libraries half unless it lives under those two directories. Each half keeps its own dependency cache, since Cargo resolves features for the packages it builds and the halves resolve some shared dependencies differently. A restored dependency cache still leaves every workspace crate to compile. To see where that time goes, add --timings to cargo nextest run --workspace --locked locally and read Cargo’s report next to nextest’s summary.

Windows links the test binaries with rust-lld and runs eight test threads, since starting a test process there costs more than running most tests.

Platform CI also builds without debug information. The test profile strips it at link time, so generating it only hands the linker bytes it discards. Local builds keep their line tables, since the setting is an environment variable on the CI job rather than a profile change. CARGO_PROFILE_TEST_DEBUG and CARGO_PROFILE_DEV_DEBUG turn it back on anywhere.

The wire integration tests share one executable, so the Serde code they instantiate compiles once instead of four times. That crate sets autotests = false and names its own target, so a new file under crates/amiss-wire/tests runs nothing until a module declares it; add cases to the modules that are already there. Targets with no unit tests set test = false, which skips an empty executable per target; turn it back on when you add one, since integration tests reach those libraries and binaries either way. Two targets stay on their own. The fatal-envelope test counts allocations process wide, and the two release eligibility checks live in eligibility so the extra release build stays small. They run in their own job, on Linux for a pull request and on every release platform for main and the weekly run.

Two similarly named files point in opposite directions. .pre-commit-config.yaml is the hook table this repository runs on itself through prek. .pre-commit-hooks.yaml is the hook this repository publishes: a consumer’s own pre-commit configuration names this repository and reads that manifest to discover the amiss staged-index check shown in Running it in CI.

cargo nextest run --workspace --locked
cargo clippy --workspace --all-targets --locked -- -D warnings

cargo test -p amiss-fuzz -p amiss-controller-fuzz --locked --release

The first pair checks every crate, engine and provider alike, from one lockfile. The third replays the committed fuzz seeds in release; the coverage-guided runs need nightly and cargo-fuzz on top, from the same lockfile. The trust boundary is a dependency boundary rather than a workspace boundary: the compiler-output specialist under api/ and the HTTP, provider API, Git acquisition, credential, storage, and service-runtime crates under controller/ are unpublished. deny-engine.toml drops them from the graph and then bans the network and async stack, so what an amiss user downloads cannot acquire it. The prek hooks run the first pair and Linux CI runs both. The macOS and Windows jobs also run the controller tests, including the cross-process file stores, provider authentication, worker, and supervised-process cases. The supported service deployments are documented in Provider-verified controls.

Tests answer to a house rule called the teeth check: important tests are exercised against deliberately broken behavior before they are trusted. The mutation lanes publish a non-gating measurement of that property, in three sizes: per pull request, per push to main, and on demand, each on its own trigger.

Every pull request measures only the mutants the change itself reaches, over shards counted from the mutants the diff actually reaches rather than from a guess, because listing them needs no build. Zero mutants skip the worker entirely; nonempty diffs use ten mutants per shard, capped at eight shards. That lane asks the whole workspace whether each mutant lives, at about twenty seconds per mutant. The shards used to pay a worse floor, a cold workspace build plus a baseline test pass repeated in every shard; now each shard restores the build cache that the baseline job saves on code pushes to main, and skips its own baseline because required CI proves the same commit, so what remains is the build delta against the last merge and the mutants themselves. A release pull request measures its version and generated-file diff like every other pull request. The code it packages was already measured before each change reached main; measuring the accumulated release again would duplicate that work and eventually exceed the bounded PR lane.

The sweep over every mutant in both workspaces runs only when someone asks for it, through workflow_dispatch. It is split across shards sized from the mutant count rather than a fixed number, it takes tens of minutes, and it exists to find gaps in code that no longer changes, which is not something a release should pay for. Fixture crates are excluded, because code that exists to be exercised by its callers says nothing about the tests. Unlike the smaller lanes it runs each mutant against its own package’s tests, which is what makes it affordable and also means a mutant that a sibling package covers is reported as surviving. Its output is a list to verify, not a verdict.

The sweep of 2026-07-28 is the reading to compare against: 6,523 mutants, 4,103 caught, 1,335 missed, 1,085 unviable, no timeouts. Roughly half of those missed are the scoping artifact above, measured at 43% on one file and 62% on another, so the number of real gaps is nearer seven hundred. A later sweep that misses far more has either lost tests or gained untested code, and the point of writing the figure down is to be able to tell which.

None of the three gates a merge and none certifies a global mutation threshold: a surviving mutant is a place where a lie would go unnoticed, to be judged against whether the perturbed value is observable through real behavior, not a score to raise.

Three agent lanes sit beside the gates, none of them gating, written as gh-aw workflows whose agents hold no write token at all: they run read-only on DeepSeek inside the repository’s runners, and a separate harness job posts what their structured outputs request, nothing else. A new issue gets its premise checked against the tree before a maintainer reads it, whoever opened it. Every push to a same-repository, non-draft pull request gets one consolidated review, summary and line-anchored comments in a single card, and a review can be dispatched by hand for any PR number. And /oc in a comment summons the agent, which can answer, fix, and propose a pull request through the same structured channel. What the lanes read is a claim under test, never instructions; only the mention lane takes tasks, and only from a collaborator’s comment. Each comment carries its run link, and the full agent log survives as a run artifact. The parsers sit under a vendored test corpus, pinned by digest, whose manifest records node counts, extraction results, and byte positions for every case from the upstream CommonMark, GFM, and MDX suites; the corpus notes document every known difference. Scanner parsers that take untrusted bytes have targets under fuzz/. The controller fuzz package signs generated provider requests before varying their facts, so its account-free targets reach the provider identity and binding checks. Both suites carry committed seeds and a nightly coverage-guided run.

The scanner runs on its own repository under --profile enforce in CI. This documentation passes through that same gate: every relative link in this book resolves in the tree, or the pull request that broke it fails.

Every pull request also packages what a release would upload. cargo package over the publishable members resolves their siblings through a temporary local registry and builds every tarball, so a file dropped from a package, a path dependency missing a version, or a new crate its dependants cannot see fails on the pull request instead of halfway through an upload that cannot be taken back. It is cargo package rather than cargo publish --dry-run because the dry run prefers a version already on crates.io over the tree, which makes it blind to exactly the crate a change is adding.

Releases are automated. A bot keeps a release pull request current with the version bump, changelog, and exact Action-dispatch ref. Merging it publishes the crates and source tag while the GitHub release remains a draft. The release workflow then assembles the immutable action/vX.Y.Z tree and exercises both that exact tree and the source-tag dispatcher on Linux, both macOS architectures, and Windows. Only a green smoke matrix advances the stable major ref without rewriting history and makes the release public; prereleases never advance the major ref. The same gate governs the release assets: the per-platform engine and prober binaries, their THIRD_PARTY_LICENSES.txt, their SHA256SUMS, and the sigstore bundle attesting that file attach to the draft, so a release that fails the matrix never publishes a binary. If a forge outage leaves that pull request stale, manually dispatching the release automation on main refreshes its metadata without running the publishing job; crate publication remains restricted to pushes on main. Security checks layer in CI as well. Dependency update PRs arrive with a cooldown, a weekly advisory re-check runs against a fresh database, and CodeQL covers both the Rust and the workflows. The Rust analysis takes about ten minutes, so it runs on main and weekly, and a pull request gets the workflow analysis. Scorecard, secret scanning with push protection, and build provenance attestations on release binaries round it out.

Last change: , commit: e840e6b9