Skip to content

chore: pin the toolchain and declare an MSRV that is true - #149

Merged
ApiliumDevTeam merged 1 commit into
refactor/exportformat-implements-fromstrfrom
chore/pin-toolchain-and-unify-msrv
Jul 30, 2026
Merged

chore: pin the toolchain and declare an MSRV that is true#149
ApiliumDevTeam merged 1 commit into
refactor/exportformat-implements-fromstrfrom
chore/pin-toolchain-and-unify-msrv

Conversation

@ApiliumDevTeam

Copy link
Copy Markdown
Contributor

Fifth of the stack. Base is #148. Both blockers for tagging a release.

1 · Pin the toolchain

The workflows installed dtolnay/rust-toolchain@stable. On 2026-07-29 the runner moved to clippy 1.97, question_mark arrived, and CI went red with no commit behind it — a compiler release is not a change to this repository and must not be able to fail its checks.

Sixteen installations across four workflows now take RUST_VERSION, declared once.

Not a rust-toolchain.toml alone, and the reason matters: that action exports RUSTUP_TOOLCHAIN, which takes precedence over the file — the pin would be ignored in exactly the place it is needed. The file is here as well, for local parity, with a note that the two move together.

2 · An MSRV that is true

where said
twelve crates 1.83
aingle_minimal 1.85
CI environment 1.85
CLAUDE.md 1.83
reality 1.91

Bisected: 1.85 fails (cranelift needs 1.90) · 1.90 fails (wasmer needs 1.91) · 1.91 builds.

Nothing had ever noticed, because MSRV Check ran cargo check -p aingle_minimal — the one member with no wasmer in its dependency tree. Same disease as the lint job: scoped so narrowly it validated almost nothing while reporting green.

It now checks --workspace --all-targets, and the figure lives once in [workspace.package] with thirteen members inheriting it. Uniform rather than per-crate on purpose: a lower number on a crate never built at that version is the same unverified promise this replaces.

Knock-on, and a good one

Clippy honours rust-version. Raising it let clippy suggest APIs that were previously too new, surfacing eight manual_is_multiple_of sites. Applied.

Verification, under both toolchains that now matter

  • pinned 1.97.0clippy --workspace --all-targets -- -D warnings exit 0 · fmt --all --check exit 0 · 2248 tests green
  • declared 1.91.0cargo check --workspace --all-targets exit 0

crates/adk and crates/adk_derive are excluded from the workspace and were deliberately left alone: an excluded crate cannot inherit [workspace.package].

Two blockers for tagging a release, both of the same shape as the lint
gate: a number nothing verified.

PIN. The workflows installed `dtolnay/rust-toolchain@stable`, so on
2026-07-29 the runner moved to clippy 1.97, `question_mark` arrived, and
CI went red with no commit behind it. Sixteen installations across four
workflows now take `RUST_VERSION`, declared once. Not a
`rust-toolchain.toml` alone: that action exports RUSTUP_TOOLCHAIN, which
takes precedence over the file, so the pin would be ignored exactly where
it is needed. The file is here too, for local parity, and the note says
they must move together.

MSRV. Twelve crates said 1.83, the CI environment said 1.85, CLAUDE.md
said 1.83, and the truth was none of them. Bisected: 1.85 fails
(`cranelift` needs 1.90), 1.90 fails (`wasmer` needs 1.91), 1.91 builds.
Nothing had ever noticed because the MSRV job ran `cargo check -p
aingle_minimal` — the single member with no wasmer in its tree. It now
checks the whole workspace, and the figure lives once in
`[workspace.package]` with thirteen members inheriting it.

Uniform rather than per-crate deliberately: a lower number on a crate
never built at that version is the same unverified promise being removed
here.

Raising the declared MSRV also lets clippy suggest APIs that were too new
before, which surfaced eight `manual_is_multiple_of` sites. Applied.

Verified under both toolchains that now matter: clippy and rustfmt clean
and 2248 tests green on the pinned 1.97.0, and `cargo check --workspace
--all-targets` clean on the declared 1.91.0.
@ApiliumDevTeam
ApiliumDevTeam merged commit c3f9665 into refactor/exportformat-implements-fromstr Jul 30, 2026
12 checks passed
ApiliumDevTeam added a commit that referenced this pull request Jul 30, 2026
* test: assert something in the tests that asserted nothing

Nine assertions compared an unsigned counter against zero — `peer_count
>= 0`, `uptime_secs >= 0`, `verification_time_ms >= 0`. Every one of them
held for every possible value, including the failure each test exists to
catch: `test_add_peer` passed whether or not the peer was added.

Replace them with the claim the test is named for where there is one
(peer counts), an invariant that can actually break where there is not
(time in a power profile cannot exceed total uptime; energy is never
negative), and nothing at all where the author's own comment says the
value is legitimately zero — with a line explaining why no bound is
asserted, so it does not get "fixed" back.

Also: `3.14` as an arbitrary test float in eight places, which clippy
reads as an approximation of PI. These tests are about storing a
decimal, not about geometry, so use a number that does not claim to be a
constant.

And `aingle_zk`'s memory benchmark had not compiled since
`prove_knowledge` settled on `(secret, public_point, message)` — it was
still passing the last two swapped. Invisible to `cargo test`, which
does not build benches.

* style: apply cargo clippy --fix across the workspace

Machine-applicable suggestions only, applied by the tool and left
otherwise untouched: needless borrows, redundant closures, `is_empty`
over length comparisons, `io::Error::other`, derivable impls, unused
imports.

Mechanical and behaviour-preserving by construction, but it is 40 files
of churn in the engine, so it is a separate commit: drop it without
losing the fixes in the commit before, which are not mechanical.

Twenty-four lints remain that clippy declines to auto-fix because they
need judgement — clamp (NaN handling differs), `sort_by_key` where the
key borrows, `Default::default()` field reassignment, and a public
`from_str` that shadows the trait method and cannot be renamed without
breaking callers. Left for a later pass rather than rushed.

* style: clear the clippy lints that do not change behaviour

Twenty-one of the twenty-four remaining lints, split by the kind of
decision each one represents rather than by the lint that reports it.

Notation (15). Ten `Config::default()` values built by assigning fields
afterwards become struct literals with `..Default::default()`. One loop
that existed only to index an array becomes an iterator. One `unwrap`
guarded by a separate `is_some` becomes `if let`. One doc list item that
rustdoc read as a continuation gets its blank line. And one more
assertion that asserted nothing: `!contexts.is_empty() || matches.len()
>= 0` held for every usize, so the test passed even when the query
returned nothing — the failure it exists to catch.

Descending sorts (6). `sort_by(|a, b| b.k.cmp(&a.k))` becomes
`sort_by_key(|x| Reverse(x.k))`, which is stable in the same way and
removes the class of bug where `a` and `b` are quietly transposed. All
six keys turned out to be `Copy`, so none needed the borrow-shaped
exception this pass was ready to grant.

Formatting is applied to the files this branch touches and to no others,
so the diff stays readable.

Three lints are deliberately NOT here, because they are decisions and not
cleanup, and burying them in a commit called "style" is how a semantic
change ships unreviewed. Both `clamp` sites change NaN behaviour, and the
`from_str` one shapes a public API. Each has its own issue.

* ci: enforce the lint bar the project already declares

CLAUDE.md defines `make lint` as `cargo clippy --workspace --all-targets
-- -D warnings`. CI ran `cargo clippy -p aingle_minimal --features rest
-- -W clippy::all`: one member of seventeen, the library target only, and
warnings that failed nothing. It reported green throughout.

Behind it: a benchmark that had not compiled in months, assertions that
compared unsigned counters against zero and so passed no matter what the
code did, and a `flatten()` over `io::Lines` that spins forever on a
repeated read error. `--all-targets` is what covers benches and
integration tests, and is the only reason the dead benchmark surfaced.

`Format Check` was separately red on main, so the aggregate `CI Success`
gate has been failing for some time; `cargo fmt --all` here makes it pass
and the widened clippy keeps it honest.

Note the lints were an onion: with `-D warnings` a failing crate stops
everything downstream from being linted at all, so the count grew from 24
to 54 as each layer was cleared. It took four passes to reach a clean
run — worth knowing before trusting a single measurement.

The three lints that are decisions rather than cleanup carry
`#[allow(...)]` with the full reasoning at the site: two `clamp`
substitutions that change NaN behaviour, and a public `from_str` that
shadows the trait method. Removing each attribute is the definition of
done for that decision.

* ci: run the checks on every pull request, not just those onto main and dev

`pull_request: branches: [main, dev]` meant a PR onto any other branch ran
no checks at all. A stack of dependent PRs therefore had no CI until each
was retargeted — which is after the review, not before it.

Found by building such a stack: three of its five PRs showed no checks
whatsoever, and nothing about the interface said why.

* fix(ingest): rewrite a fallthrough with `?`, as clippy 1.97 asks

`clippy::question_mark` is new in 1.97. Nobody touched this code; the
toolchain moved under it.

Which is the whole argument for pinning: this repository installs
`dtolnay/rust-toolchain@stable`, so the gate added in this PR can go red
from a compiler release with no commit behind it. Verified by installing
1.97.0 locally — clippy, rustfmt and the tests are all clean under the
exact toolchain the runner used, which 1.96 could not have told me.

* fix(ai): stop a single NaN from silently disabling two subsystems

Both sites carried a clamp-shaped expression that clippy wanted rewritten.
Following that advice mechanically would have introduced a bug in one and
papered over a worse one in the other, so each is settled here instead.

SurpriseGate. `min`/`max` discard NaN and return the other operand;
`clamp` propagates it. But the real entry point is upstream:
`record_surprise` feeds Welford's algorithm, and one non-finite sample
makes the running mean NaN permanently — every later update keeps it, the
adaptive threshold inherits it, and because every comparison against NaN
is false the gate stops firing with nothing to show for it. Reject
non-finite samples at the boundary, and fall back to the CONFIGURED
threshold — the non-adaptive baseline this type already has — rather than
to an invented constant, if unusable statistics arrive some other way
(deserialised state is not covered by the entry guard).

TransactionClassifier::confidence. `partial_cmp().unwrap()` panics on a
NaN distance, which `l2_distance` produces from a corrupt centroid, so
the process died inside a confidence calculation. `total_cmp` is total
and also sorts NaN last, so a corrupt centroid can no longer pass itself
off as the nearest one. And with every distance unusable the old code
fell past the `second_dist > 0.0` test to return 1.0 — FULL confidence at
the exact moment there is no information. It now answers 0.5, the same
neutral value it already returns when there are no centroids at all.

Five tests, each pinning a failure that was silent: a poisoned mean, a
NaN threshold, the panic, and full confidence from no information.

* refactor(dag): ExportFormat implements FromStr instead of shadowing it

`ExportFormat::from_str` was an inherent method returning `Option`, which
shadows `std::str::FromStr::from_str` and returns a different shape from
it. A caller reading `ExportFormat::from_str(..)` could not tell which one
they were getting, and got no reason for the failure either way.

Implement the trait with an error that carries the offending input and
knows the accepted spellings. The list of valid formats lived in the REST
handler, where it could drift away from the parser with nothing to catch
it — it already advertised "dot, mermaid, json" while the parser also
accepted "graphviz" and "md". Now the type owns it and a test asserts the
message names every spelling the parser takes.

One caller migrated. `cargo clippy --workspace --all-targets -- -D
warnings` and `cargo fmt --all --check` are both clean with no `#[allow]`
left anywhere from this series.

* chore: pin the toolchain and declare an MSRV that is true

Two blockers for tagging a release, both of the same shape as the lint
gate: a number nothing verified.

PIN. The workflows installed `dtolnay/rust-toolchain@stable`, so on
2026-07-29 the runner moved to clippy 1.97, `question_mark` arrived, and
CI went red with no commit behind it. Sixteen installations across four
workflows now take `RUST_VERSION`, declared once. Not a
`rust-toolchain.toml` alone: that action exports RUSTUP_TOOLCHAIN, which
takes precedence over the file, so the pin would be ignored exactly where
it is needed. The file is here too, for local parity, and the note says
they must move together.

MSRV. Twelve crates said 1.83, the CI environment said 1.85, CLAUDE.md
said 1.83, and the truth was none of them. Bisected: 1.85 fails
(`cranelift` needs 1.90), 1.90 fails (`wasmer` needs 1.91), 1.91 builds.
Nothing had ever noticed because the MSRV job ran `cargo check -p
aingle_minimal` — the single member with no wasmer in its tree. It now
checks the whole workspace, and the figure lives once in
`[workspace.package]` with thirteen members inheriting it.

Uniform rather than per-crate deliberately: a lower number on a crate
never built at that version is the same unverified promise being removed
here.

Raising the declared MSRV also lets clippy suggest APIs that were too new
before, which surfaced eight `manual_is_multiple_of` sites. Applied.

Verified under both toolchains that now matter: clippy and rustfmt clean
and 2248 tests green on the pinned 1.97.0, and `cargo check --workspace
--all-targets` clean on the declared 1.91.0.

* chore(release): bump workspace versions for aingle 0.9.0
ApiliumDevTeam added a commit that referenced this pull request Jul 30, 2026
* test: assert something in the tests that asserted nothing

Nine assertions compared an unsigned counter against zero — `peer_count
>= 0`, `uptime_secs >= 0`, `verification_time_ms >= 0`. Every one of them
held for every possible value, including the failure each test exists to
catch: `test_add_peer` passed whether or not the peer was added.

Replace them with the claim the test is named for where there is one
(peer counts), an invariant that can actually break where there is not
(time in a power profile cannot exceed total uptime; energy is never
negative), and nothing at all where the author's own comment says the
value is legitimately zero — with a line explaining why no bound is
asserted, so it does not get "fixed" back.

Also: `3.14` as an arbitrary test float in eight places, which clippy
reads as an approximation of PI. These tests are about storing a
decimal, not about geometry, so use a number that does not claim to be a
constant.

And `aingle_zk`'s memory benchmark had not compiled since
`prove_knowledge` settled on `(secret, public_point, message)` — it was
still passing the last two swapped. Invisible to `cargo test`, which
does not build benches.

* style: apply cargo clippy --fix across the workspace

Machine-applicable suggestions only, applied by the tool and left
otherwise untouched: needless borrows, redundant closures, `is_empty`
over length comparisons, `io::Error::other`, derivable impls, unused
imports.

Mechanical and behaviour-preserving by construction, but it is 40 files
of churn in the engine, so it is a separate commit: drop it without
losing the fixes in the commit before, which are not mechanical.

Twenty-four lints remain that clippy declines to auto-fix because they
need judgement — clamp (NaN handling differs), `sort_by_key` where the
key borrows, `Default::default()` field reassignment, and a public
`from_str` that shadows the trait method and cannot be renamed without
breaking callers. Left for a later pass rather than rushed.

* style: clear the clippy lints that do not change behaviour

Twenty-one of the twenty-four remaining lints, split by the kind of
decision each one represents rather than by the lint that reports it.

Notation (15). Ten `Config::default()` values built by assigning fields
afterwards become struct literals with `..Default::default()`. One loop
that existed only to index an array becomes an iterator. One `unwrap`
guarded by a separate `is_some` becomes `if let`. One doc list item that
rustdoc read as a continuation gets its blank line. And one more
assertion that asserted nothing: `!contexts.is_empty() || matches.len()
>= 0` held for every usize, so the test passed even when the query
returned nothing — the failure it exists to catch.

Descending sorts (6). `sort_by(|a, b| b.k.cmp(&a.k))` becomes
`sort_by_key(|x| Reverse(x.k))`, which is stable in the same way and
removes the class of bug where `a` and `b` are quietly transposed. All
six keys turned out to be `Copy`, so none needed the borrow-shaped
exception this pass was ready to grant.

Formatting is applied to the files this branch touches and to no others,
so the diff stays readable.

Three lints are deliberately NOT here, because they are decisions and not
cleanup, and burying them in a commit called "style" is how a semantic
change ships unreviewed. Both `clamp` sites change NaN behaviour, and the
`from_str` one shapes a public API. Each has its own issue.

* ci: enforce the lint bar the project already declares

CLAUDE.md defines `make lint` as `cargo clippy --workspace --all-targets
-- -D warnings`. CI ran `cargo clippy -p aingle_minimal --features rest
-- -W clippy::all`: one member of seventeen, the library target only, and
warnings that failed nothing. It reported green throughout.

Behind it: a benchmark that had not compiled in months, assertions that
compared unsigned counters against zero and so passed no matter what the
code did, and a `flatten()` over `io::Lines` that spins forever on a
repeated read error. `--all-targets` is what covers benches and
integration tests, and is the only reason the dead benchmark surfaced.

`Format Check` was separately red on main, so the aggregate `CI Success`
gate has been failing for some time; `cargo fmt --all` here makes it pass
and the widened clippy keeps it honest.

Note the lints were an onion: with `-D warnings` a failing crate stops
everything downstream from being linted at all, so the count grew from 24
to 54 as each layer was cleared. It took four passes to reach a clean
run — worth knowing before trusting a single measurement.

The three lints that are decisions rather than cleanup carry
`#[allow(...)]` with the full reasoning at the site: two `clamp`
substitutions that change NaN behaviour, and a public `from_str` that
shadows the trait method. Removing each attribute is the definition of
done for that decision.

* ci: run the checks on every pull request, not just those onto main and dev

`pull_request: branches: [main, dev]` meant a PR onto any other branch ran
no checks at all. A stack of dependent PRs therefore had no CI until each
was retargeted — which is after the review, not before it.

Found by building such a stack: three of its five PRs showed no checks
whatsoever, and nothing about the interface said why.

* fix(ingest): rewrite a fallthrough with `?`, as clippy 1.97 asks

`clippy::question_mark` is new in 1.97. Nobody touched this code; the
toolchain moved under it.

Which is the whole argument for pinning: this repository installs
`dtolnay/rust-toolchain@stable`, so the gate added in this PR can go red
from a compiler release with no commit behind it. Verified by installing
1.97.0 locally — clippy, rustfmt and the tests are all clean under the
exact toolchain the runner used, which 1.96 could not have told me.

* fix(ai): stop a single NaN from silently disabling two subsystems

Both sites carried a clamp-shaped expression that clippy wanted rewritten.
Following that advice mechanically would have introduced a bug in one and
papered over a worse one in the other, so each is settled here instead.

SurpriseGate. `min`/`max` discard NaN and return the other operand;
`clamp` propagates it. But the real entry point is upstream:
`record_surprise` feeds Welford's algorithm, and one non-finite sample
makes the running mean NaN permanently — every later update keeps it, the
adaptive threshold inherits it, and because every comparison against NaN
is false the gate stops firing with nothing to show for it. Reject
non-finite samples at the boundary, and fall back to the CONFIGURED
threshold — the non-adaptive baseline this type already has — rather than
to an invented constant, if unusable statistics arrive some other way
(deserialised state is not covered by the entry guard).

TransactionClassifier::confidence. `partial_cmp().unwrap()` panics on a
NaN distance, which `l2_distance` produces from a corrupt centroid, so
the process died inside a confidence calculation. `total_cmp` is total
and also sorts NaN last, so a corrupt centroid can no longer pass itself
off as the nearest one. And with every distance unusable the old code
fell past the `second_dist > 0.0` test to return 1.0 — FULL confidence at
the exact moment there is no information. It now answers 0.5, the same
neutral value it already returns when there are no centroids at all.

Five tests, each pinning a failure that was silent: a poisoned mean, a
NaN threshold, the panic, and full confidence from no information.

* refactor(dag): ExportFormat implements FromStr instead of shadowing it

`ExportFormat::from_str` was an inherent method returning `Option`, which
shadows `std::str::FromStr::from_str` and returns a different shape from
it. A caller reading `ExportFormat::from_str(..)` could not tell which one
they were getting, and got no reason for the failure either way.

Implement the trait with an error that carries the offending input and
knows the accepted spellings. The list of valid formats lived in the REST
handler, where it could drift away from the parser with nothing to catch
it — it already advertised "dot, mermaid, json" while the parser also
accepted "graphviz" and "md". Now the type owns it and a test asserts the
message names every spelling the parser takes.

One caller migrated. `cargo clippy --workspace --all-targets -- -D
warnings` and `cargo fmt --all --check` are both clean with no `#[allow]`
left anywhere from this series.

* chore: pin the toolchain and declare an MSRV that is true

Two blockers for tagging a release, both of the same shape as the lint
gate: a number nothing verified.

PIN. The workflows installed `dtolnay/rust-toolchain@stable`, so on
2026-07-29 the runner moved to clippy 1.97, `question_mark` arrived, and
CI went red with no commit behind it. Sixteen installations across four
workflows now take `RUST_VERSION`, declared once. Not a
`rust-toolchain.toml` alone: that action exports RUSTUP_TOOLCHAIN, which
takes precedence over the file, so the pin would be ignored exactly where
it is needed. The file is here too, for local parity, and the note says
they must move together.

MSRV. Twelve crates said 1.83, the CI environment said 1.85, CLAUDE.md
said 1.83, and the truth was none of them. Bisected: 1.85 fails
(`cranelift` needs 1.90), 1.90 fails (`wasmer` needs 1.91), 1.91 builds.
Nothing had ever noticed because the MSRV job ran `cargo check -p
aingle_minimal` — the single member with no wasmer in its tree. It now
checks the whole workspace, and the figure lives once in
`[workspace.package]` with thirteen members inheriting it.

Uniform rather than per-crate deliberately: a lower number on a crate
never built at that version is the same unverified promise being removed
here.

Raising the declared MSRV also lets clippy suggest APIs that were too new
before, which surfaced eight `manual_is_multiple_of` sites. Applied.

Verified under both toolchains that now matter: clippy and rustfmt clean
and 2248 tests green on the pinned 1.97.0, and `cargo check --workspace
--all-targets` clean on the declared 1.91.0.

* chore(release): bump workspace versions for aingle 0.9.0
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