Skip to content

release: aingle 0.9.0 → main (PRs #144–#149) - #150

Merged
ApiliumDevTeam merged 10 commits into
mainfrom
release/aingle-0.9.0
Jul 30, 2026
Merged

ApiliumDevTeam merged 10 commits into
mainfrom
release/aingle-0.9.0

Conversation

@ApiliumDevTeam

Copy link
Copy Markdown
Contributor

Release: aingle 0.9.0 → main

Consolida en main el conjunto de cambios de la versión 0.9.0. Esta rama reúne los seis PRs que conforman el release (#144–#149), incluyendo el bump de versión del workspace.

Nota de flujo: los PR #144 y #145 se fusionaron a dev. El resto del stack (#146–#149) tenía como base ramas intermedias y no llegó a dev, por lo que este release es la primera integración conjunta de los seis hacia main.


PRs incluidos

#144 — fix(ingest): a cloud-evicted file is not a deletion

Los clientes de nube (OneDrive, Google Drive, iCloud Drive) reclaman disco de forma distinta. iCloud reemplaza Notes.md por .Notes.md.icloud, con lo que is_file() devuelve false y ingest_paths lo leía como un borrado: retraía todos los triples que la nota había escrito, tumbaba sus nodos de tarea y tarjeta, y olvidaba sus chunks.

  • El daño real: nada se pierde de forma permanente — la nota vuelve al hidratarse macOS — pero la DAG registraba una retracción que nunca ocurrió seguida de la reinserción. Historia falsa en la estructura cuyo valor es que su historia es cierta; y la recuperación fundamentada respondía como si la nota no existiera.
  • La decisión de diseño: cuando el stub .icloud está presente se omite la retracción, prefiriendo dejar una entrada obsoleta antes de escribir una falsa. Reversible.
  • Nuevo test que reproduce una evacuación exacta y verifica que triples, tareas, registro de fuentes y recuperación fundamentada sobreviven. El test hermano ingest_paths_retracts_a_deleted_file sigue en verde: un borrado real sigue retrayendo.

#145 — Clear the workspace lint debt (A+B)

Tres commits deliberadamente separados:

  1. test: — Nueve aserciones comparaban un contador unsigned contra cero (peer_count >= 0, uptime_secs >= 0, verification_time_ms >= 0): se cumplían para cualquier valor, incluido el fallo que el test debía cazar. Pasaban a afirmar lo que el nombre dice, o un invariante rompible, o nada (con una línea explicando por qué es legítimamente cero). Aquí también: el benchmark de memoria de aingle_zk no compilaba desde que prove_knowledge cambió su firma — cargo test no compila los benches, así que nadie lo notó.
  2. style: — cargo clippy --fix, solo sugerencias aplicables por la herramienta, 40 archivos.
  3. style: — 21 lints que requerían criterio: diez Config::default() construidos por asignación posterior, un bucle que solo indexaba, un unwrap custodiado por un is_some independiente, seis ordenes descendentes a sort_by_key(Reverse(..)).

Hallazgo sobre la medición: el conteo no era 24. Con -D warnings, una crate en rojo oculta todo lo que depende de ella, así que hubo que hacer cuatro pasadas para llegar a un verde limpio — incluyendo un flatten() sobre io::Lines en el log de auditoría que buclea infinito ante un error de lectura repetido.

#146 — ci: enforce the lint bar the project already declares

La brecha entre lo declarado y lo ejecutado:

CLAUDE.md declara CI ejecutaba
alcance --workspace -p aingle_minimal
targets --all-targets solo librería
severidad -D warnings -W clippy::all

Un miembro de diecisiete, solo advertencias. Reportó verde mientras se acumulaban 54 lints. El --all-targets es estructural, no un detalle: es lo que cubre benches y tests de integración.

#147 — fix(ai): stop a single NaN from silently disabling two subsystems

Cambio de comportamiento, no limpieza (por eso no está en #145). Los sitios usaban x.min(hi).max(lo), que clippy sugería como x.clamp(lo, hi): no son equivalentes — min/max descartan NaN, clamp lo propaga.

  • SurpriseGate: seguir el lint habría convertido NaN en un umbral NaN, y como toda comparación contra NaN es falsa, should_update dejaría de disparar y la memoria dejaría de aprender sin avisar. Pero la entrada real es aguas arriba: record_surprise alimenta el algoritmo de Welford y una muestra no finera envenena la media móvil para siempre. Solución: rechazar muestras no fineras en la frontera y, si llegan estadísticas inutilizables por otra vía (estado deserializado), caer al umbral configurado, no a una constante inventada.
  • TransactionClassifier::confidence: partial_cmp().unwrap() pánicaba con una distancia NaN (que l2_distance produce ante un centroide corrupto). total_cmp ordena NaN al final, así que un centroide corrupto ya no puede hacerse pasar por el más cercano. Y sin distancias usables, el código viejo devolvía 1.0 — confianza total en el momento exacto en que no hay información; ahora devuelve 0.5, el mismo valor neutral que la función ya usaba sin centroides.
  • Cinco tests, cada uno fijando un fallo que era silencioso.

#148 — refactor(dag): ExportFormat implements FromStr instead of shadowing it

Cambio de firma pública, por eso va aparte.

  • ExportFormat::from_str era un método inherente que devolvía Option y sombreado a std::str::FromStr::from_str con otra forma: un llamador no podía saber cuál invocaba. Ahora es un FromStr de verdad, con un error que lleva el input ofensivo y conoce los spellings aceptados.
  • Esto arregló una inconsistencia viva: la lista de formatos válidos vivía en el handler REST, donde podía desincronizarse del parser sin que nada lo cazara. Ya lo estaba — el handler anunciaba dot, mermaid, json mientras el parser también aceptaba graphviz y md. El tipo es ahora dueño de la lista.
  • Eliminado el último #[allow] de esta serie.

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

Los dos bloqueantes para tagear el release.

  1. Pin del toolchain: los workflows instalaban dtolnay/rust-toolchain@stable. El 2026-07-29 el runner subió a clippy 1.97, llegó question_mark y CI se puso en rojo sin ningún commit detrás. Dieciséis instalaciones a lo largo de cuatro workflows ahora toman RUST_VERSION declarado una sola vez. No es solo rust-toolchain.toml: esa action exporta RUSTUP_TOOLCHAIN, que tiene prioridad sobre el archivo, así que el pin se ignoraría justo donde hace falta. El archivo queda también, para paridad local.
  2. MSRV verdadero: las fuentes discrepaban — doce crates decían 1.83, aingle_minimal 1.85, el entorno de CI 1.85, CLAUDE.md 1.83, y la realidad 1.91 (bisectado: 1.85 y 1.90 fallan, 1.91 compila). Nunca se notó porque MSRV Check corría cargo check -p aingle_minimal, el único miembro sin wasmer en su árbol. Ahora verifica --workspace --all-targets y la cifra vive una sola vez en [workspace.package] con trece miembros heredándola. Efecto colateral bueno: al subir la versión, clippy sugirió ocho sitios manual_is_multiple_of, aplicados.

Release notes — aingle 0.9.0

Correcciones

  • Ingesta: los archivos desalojados por clientes de nube (p. ej. el stub .Notes.md.iccloud de iCloud Drive) ya no se tratan como borrados. La DAG deja de registrar retracciones falsas y la recuperación fundamentada sigue encontrando la nota mientras el archivo siga presente como stub.
  • Subsistemas de inferencia: un único valor NaN en la entrada ya no envenena la media móvil de SurpriseGate ni lo convierte en un umbral inválido, y TransactionClassifier::confidence ya no pánicaba ni devolvía confianza total cuando no había información usable. Ambos fallos eran silenciosos.

Mejoras internas / calidad

  • Se saldó la deuda de lint del workspace: 2248 tests en verde, cargo clippy --workspace --all-targets -- -D warnings y cargo fmt --all --check en cero.
  • ExportFormat ahora implementa FromStr (en lugar de sombrearlo), con errores informativos y una única fuente de verdad para los formatos aceptados.
  • Diez tests que no afirmaban nada ahora afirman lo que su nombre dice; un benchmark de aingle_zk que llevaba meses sin compilar vuelve a construirse.
  • Se cerró un bucle infinito en el log de auditoría ante errores de lectura repetidos.

Toolchain / CI

  • Toolchain fijado a 1.97.0 y MSRV declarado de forma veraz y uniforme en 1.91 (workspace + --all-targets). La compuerta de lint ahora aplica el nivel que el proyecto declaraba: --workspace --all-targets -D warnings. Format Check vuelve a verde, y el agregado CI Success con él.

Versión

  • Bump del workspace: aingle_graph, aingle_logic, aingle_zk, aingle_ingest, aingle_cortex a 0.9.0; aingle_minimal (y dependientes) a 0.7.2; kaneru a 0.7.6.

Verificación

  • cargo clippy --workspace --all-targets -- -D warnings → exit 0
  • cargo fmt --all --check → exit 0
  • 2248 tests en verde
  • cargo check --workspace --all-targets con la versión MSRV declarada (1.91) → exit 0

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.
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.
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.
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.
…d 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.
`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.
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.
`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.
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 11eafd9 into main Jul 30, 2026
21 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
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