Skip to content

Add rustfmt, clippy, type-check and test gates to CI - #60

Merged
adbarc92 merged 7 commits into
mainfrom
chore/ci-lint-gates
Aug 15, 2026
Merged

Add rustfmt, clippy, type-check and test gates to CI#60
adbarc92 merged 7 commits into
mainfrom
chore/ci-lint-gates

Conversation

@adbarc92

@adbarc92 adbarc92 commented Aug 14, 2026

Copy link
Copy Markdown
Owner

CI gated on three things: the embargo guard, cargo test --workspace, and the cross-platform tauri build matrix. No linting, no formatting check, no frontend gate of any kind — and, it turns out, no coverage of the cockpit crate's tests or the vitest suite. This adds five jobs and clears what they surfaced.

All 9 jobs green on this branch, including the untouched 3-OS build matrix.

The two-workspace problem

cockpit/ui/src-tauri/Cargo.toml opens with an empty [workspace] table, making it a standalone cargo workspace. The root manifest lists only crates/fleet-core and crates/fleetd as members. Nothing invoked from the repo root — not --workspace, not --all — reaches that crate.

The existing cargo test (workspace) job therefore ran 120 tests from crates/ and zero of the cockpit crate's 26. That suite covers the app-plugin state machine, manifest validation and discovery, and until now was verified only by hand. Every Rust gate below runs once per manifest for the same reason.

Gates added

Job Gate Root workspace cockpit/ui/src-tauri
fmt + clippy cargo fmt -- --check yes yes (via --manifest-path)
fmt + clippy cargo clippy --all-targets -- -D warnings yes yes (via working-directory)
svelte-check + tsc npm run check
vitest (cockpit/ui) npm test
cargo test (cockpit) cargo test (already gated) yes

npm run check resolves to svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json. There is no ESLint in this project, so there is no lint gate to add on the frontend side.

The vitest job closes a half-gated pair, not just a coverage gap: the regression pin for the plugin_launch main-thread freeze is split across a Rust test (now covered by cargo test (cockpit)) and cockpit/ui/src/App.appPlugin.test.ts (covered here). That file is not on main yet — it lands with the plugin-runtime work — so this job gates 91 tests today and picks up the JS half of the pin the moment it merges.

Compiling the tauri crate needs the WebKitGTK system deps the tauri build job already installs, plus the fleetd sidecar binary — tauri-build resolves the externalBin resource at compile time and hard-errors without it. Both the lint and cockpit-test jobs repeat that setup (apt deps, npm ci, npm run sidecar). rustfmt needs none of it, so both fmt checks run first and fail fast.

Existing jobs are untouched; build still gates on test only.

Debt cleared

style: apply cargo fmt — neither workspace had ever been run through rustfmt, so this is the entire backlog in one pass: 34 files, +2486/-642, all whitespace. There is no rustfmt.toml; these are stock defaults, mostly struct literals and multi-arg calls being split across lines. Kept in its own commit so it does not bury the real fixes.

fix(cockpit): clear clippy debt — the root workspace was already clean under -D warnings. The cockpit crate had twelve findings. Three were real:

  • nonminimal_bool in the discovery filter — the pruned test is now a named binding instead of a negated conjunction
  • two unnecessary_to_owned (&"audience".to_string()) in the check_crash tests

The other nine are dead_code, each carrying a narrowly scoped #[allow(dead_code)] with a comment. No blanket allows, nothing at module or crate level. Deleting the code would amputate work in flight on #49, so it stays — but the comments distinguish two very different situations, verified by grep across origin/main and origin/feat/plugin-runtime:

Genuinely awaiting a caller (verified to exist):

Site Caller
PluginManager::url_for embedding.rs:99 on feat/plugin-runtime

Unwired features with no caller on any branch — read only by test assertions, if at all. The comments say so plainly, so the allow cannot become the permanent resting place of unbuilt work:

Site Reality
state::check_crash only its own two unit tests; nothing destroys the webview of a plugin whose process dies
Spawner::has_exited reachable only via check_crash, so dead with it; the real impl has never run outside a test
Manifest::webview read only by test assertions
WebviewCfg (popups, external_links, title) read only by test assertions — a plugin author setting popups: block gets no blocking
Manifest::window_title one test assertion, no production caller

Not independently verified — comments left as originally written, and each already states plainly that nothing reads the item today: Running::child_id (written at launch, never read back), Lifecycle::managed (accepted, not acted on), Spawner::kill (documented force-kill fallback), state::STOPPED (rest state, never emitted).

test(cockpit): give each local_projects test its own temp dir — a pre-existing flake the new gate would have inherited. tmp() keyed its directory on the process id alone, so all four local_projects tests shared one path while running concurrently in the same binary; one test's remove_dir_all raced another's create_dir_all. Measured 5 failures in 20 runs on Windows before, 0 in 30 after. An atomic counter makes each directory unique. Test-only, no production code touched.

test(cockpit): make the absolute-cwd test pass off Windows — caught by the first CI run, which is the first time this suite has ever executed on Linux. keeps_absolute_cwd_as_is leaned on the AUDIENCE_JSON fixture's D:/… cwd, which Path::is_absolute only calls absolute on Windows; elsewhere resolved_cwd correctly joins it to the manifest dir and the assertion fails. Production code is right — only the test baked in a platform. It now picks an absolute path the host agrees with.

Verification

Green in CI on ubuntu, and run locally on Windows against the final tree:

cargo fmt --all -- --check (both) pass
cargo clippy --workspace --all-targets -- -D warnings pass
cargo clippy --all-targets -- -D warnings (cockpit) pass
npm run check 342 files, 0 errors, 0 warnings
cargo test --workspace 120 passed, 3 ignored (Docker ITs)
cargo test (cockpit) 26 passed
npm test 91 passed, 13 files

Behavior is unchanged: the clippy fixes are semantics-preserving rewrites, both test fixes touch only test code, and the comment corrections touch only comments. Test counts differ from the operator's hand-recorded figures (28 Rust / 135 JS) because those were taken on feat/plugin-runtime; this branch is cut from origin/main.

Note for the operator

No rustfmt.toml. Stock defaults are what the new gate enforces. use_small_heuristics = "Max" would have roughly halved the formatting diff (measured: +1185/-483 vs +2486/-642) by preserving the compact struct literals the code was written with — worth a look if that style was deliberate, but it is a config choice for the repo owner, not one to make inside a CI PR.

The 34-file formatting commit will conflict with feat/plugin-runtime. That is known and left for the operator to sequence; nothing on that branch was touched here.

Four gates the workflow was missing:

  - cargo fmt --all -- --check
  - cargo clippy --workspace --all-targets -- -D warnings
  - npm run check (svelte-check + tsc)
  - cargo test for cockpit/ui/src-tauri

The last one closes a real hole. cockpit/ui/src-tauri declares its own empty
[workspace] table, so it is a standalone workspace that the root manifest does
not list as a member — 'cargo test --workspace' run from the repo root never
reached its tests, and that suite (the app-plugin state machine, manifest
validation, discovery) had only ever been run by hand. The fmt and clippy gates
run once per manifest for the same reason.

Compiling the tauri crate needs the WebKitGTK system deps the build job already
installs, plus the fleetd sidecar binary, because tauri-build resolves the
externalBin resource at compile time and hard-errors without it. Both the lint
and cockpit-test jobs therefore repeat that setup.
Pure rustfmt output, no behavior change — the new 'cargo fmt --all -- --check'
gate's baseline. Neither workspace had ever been run through rustfmt, so this is
the whole backlog in one pass; there is no rustfmt.toml, so these are the stock
defaults. Mostly struct literals and multi-argument calls being split across
lines.
The root workspace was already clean under '-D warnings'; the cockpit crate had
never been linted and had twelve findings.

Three were real and are fixed: a nonminimal_bool in the discovery filter (the
pruned test is now a named binding rather than a negated conjunction) and two
unnecessary_to_owned in the check_crash tests.

The other nine are dead_code on plugin-runtime scaffolding that is deliberately
ahead of its caller — the Phase-6 embedding layer that will read the webview
manifest surface, the crash watcher that will poll Spawner::has_exited via
check_crash, and the STOPPED rest state. Deleting any of it would amputate an
in-flight feature, so each site gets a narrowly scoped #[allow(dead_code)] with
a comment naming the caller it is waiting on. No blanket allows, and nothing at
module or crate level.
The tmp() helper keyed its directory on the process id alone, so all four
local_projects tests shared one path while running concurrently in the same test
binary. One test's remove_dir_all raced another's create_dir_all and the run
failed with AlreadyExists or PermissionDenied — 5 times in 20 on Windows.

Pre-existing, and invisible until now because these tests were never run by CI.
The new 'cargo test (cockpit)' gate would have inherited it as a ~25% flake
rate, so: an atomic counter makes each directory unique. 0 failures in 30 runs
after. Test-only change, no production code touched.
keeps_absolute_cwd_as_is leaned on the AUDIENCE_JSON fixture's 'D:/…' cwd. That
is only an absolute path on Windows; everywhere else Path::is_absolute says no,
resolved_cwd joins it to the manifest dir, and the assertion fails.

Production code is fine — is_absolute is doing exactly its job. Only the test
baked in a platform. It now picks an absolute path the host agrees with.

Surfaced by the new 'cargo test (cockpit)' gate, which is the first time this
suite has ever run on Linux.
Fifth gate: 'npm test' in cockpit/ui, 91 tests across 13 files. Same shape as
the svelte-check job — node only, no Rust, no system deps.

Closes a half-gated pair rather than just a coverage gap. The regression pin for
the plugin_launch main-thread freeze is split across two suites: a Rust test,
now covered by 'cargo test (cockpit)', and cockpit/ui/src/App.appPlugin.test.ts,
covered here. That file is not on main yet — it lands with the plugin-runtime
work — so this job gates 91 tests today and picks up the JS half of the pin the
moment it merges. Wiring it now means the pin is never half-enforced.
Four of the allows I added claimed the code was waiting on the Phase-6 embedding
layer. Verified by grep across origin/main and origin/feat/plugin-runtime — that
is false for these:

  state::check_crash        only its own two unit tests call it
  Manifest::webview         read only by test assertions
  WebviewCfg (all fields)   read only by test assertions
  Manifest::window_title    one test assertion, no production caller
  Spawner::has_exited       reachable only via check_crash, so dead with it

These are unimplemented features with no caller anywhere, not code whose caller
is en route. A plugin author setting 'popups: block' today gets no blocking. The
allows stay — deleting the code would amputate work in flight on #49 — but the
comments now say plainly that nothing calls this on any branch, so the allow
cannot quietly become the permanent resting place of two unbuilt features.

PluginManager::url_for keeps its original comment: that one is genuinely waiting
on a caller, verified at embedding.rs:99 on feat/plugin-runtime. Comments-only
change, no code touched.
@adbarc92 adbarc92 changed the title Add rustfmt, clippy, frontend type-check and cockpit test gates to CI Add rustfmt, clippy, type-check and test gates to CI Aug 14, 2026
@adbarc92
adbarc92 marked this pull request as ready for review August 15, 2026 17:14
@adbarc92
adbarc92 merged commit 3cc1ae6 into main Aug 15, 2026
18 checks passed
adbarc92 added a commit that referenced this pull request Aug 15, 2026
Bootstrap run of the testing-plan skill against a3edc78. Scores 131 gaps on
likelihood x impact across seven test tiers, records a trust verdict per runner,
and ranks what to close next.

The load-bearing discovery is that five of the seven automated tiers did not run
in CI: 135 vitest tests, 28 Tauri-host Rust tests, 52 session-state tests and 53
pytest tests were advisory, not gating. `cargo test --workspace` never reached
the cockpit crate at all, because `cockpit/ui/src-tauri/Cargo.toml` opens with a
bare `[workspace]` and the root manifest lists only crates/fleet-core and
crates/fleetd. GAP-057, GAP-111 and GAP-113 are the entries for that; PR #60
closes all three, so those rows and the tier map's `in_ci` flags are already
stale and will re-derive on the next run.

The app-plugin runtime is a deliberate scope carve-out (section 2): targeted
tests for the plugin_launch freeze and the Gate-5 teardown lifecycle were being
written concurrently, so GAP-005/009/010 are recorded as context rather than as
gaps to act on. The preceding commit is that work; R5 releases them back into the
ranking.

Six items await human ratification (section 3), including the `spine_weight`
table -- which is the Impact axis of every score in the file and is currently a
first-run proposal, not a ratified input.
adbarc92 added a commit that referenced this pull request Aug 15, 2026
#60 added `cargo fmt -- --check` to CI and applied the formatter to both
workspaces on main. This branch predates that gate, so its own additions -- the
Gate-5 tests from 0d05f55, the audience-manifest assertions, and the LRU touch in
embedding.rs -- were never formatted and would fail the new check job.

Whitespace only, stock defaults, no rustfmt.toml. Verified after: fmt --check
clean on both manifests, clippy --all-targets -D warnings clean, cargo test 34+2
(cockpit) and 120 (workspace), npm run check 353 files 0/0, vitest 135 in 19
files.
adbarc92 added a commit that referenced this pull request Aug 15, 2026
…n pass

Rewrites the State summary in place and appends a session-log entry. The audit
that opened this session found STATUS.md two sessions behind: it was silent on
the 8/13 testing plan, the Gate-5 tests, and PR #60 entirely.

Also records two wrong predictions so they are not re-derived -- the #60/#49
merge-tree conflict forecast that did not materialise, and the `gh pr merge
--delete-branch` failure that was only a local-branch delete failing after a
successful merge.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant