Manifest-majors dependency audit — 2026-07
Superseded by the 2026-08 audit. That pass read “latest” from the live crates.io sparse index rather than the local index cache this one used, covers all 121 workspace entries rather than only the major-version-relevant ones, and adds the MSRV dimension this audit has no coverage of — the on-chain
build-sbftoolchain is nine Rust versions behind the host, so an MSRV bump can break the programs while the workspace gate stays green. The rows below are kept as-written: they are the evidence their verdicts were reached on, not a live status board.
Scope: an assessment (not a bump) of every major-version-relevant entry in the
root Cargo.toml [workspace.dependencies]. It answers, per dependency: is a
major bump available, and is it warranted right now? No manifest or lockfile
was changed by this audit — the actual bumps are a deferred downstream wave.
Method: resolved versions read from Cargo.lock; “latest published” read from
the local crates.io index cache (~/.cargo/registry/index) — so “latest” here
means latest the index has seen at last fetch; entries flagged (offline?)
could not be confirmed against a live registry. cargo-outdated is unusable in
this tree (the azure_core_legacy package alias plus the yanked core2 0.4.0
transitive via ipfs-cid/cid 0.10 in mail_client make it error out), so
every row below was derived manually.
Not wired into
SUMMARY.mdto avoid colliding with the concurrent docs lanes. It can be linked later under Appendix: Reference (alongsideappendix/program-reference.md) if a permanent home is wanted.
Verdict legend
- bump-now — safe, low blast radius, warranted this cycle.
- assess-later — a real major exists and is worth taking, but needs its own scoped adaptation wave (API churn), not a drive-by bump.
- blocked — cannot move until an upstream event (no action possible now).
- hold — already at latest, or intentionally pinned; no bump wanted.
Summary table
| Dependency | Manifest | Resolved | Latest (index) | Verdict | Blast radius |
|---|---|---|---|---|---|
| opentelemetry | 0.32 | 0.27.1 | 0.33.0 | assess-later | mail_observe only (1 file) |
| opentelemetry-otlp | 0.32 | 0.27.0 | 0.32.0 | assess-later | mail_observe only |
| opentelemetry_sdk | 0.32 | 0.27.1 | ~0.33 (train) | assess-later | mail_observe only |
| opentelemetry-semantic-conventions | 0.32 | 0.27.0 | 0.32.1 | assess-later | mail_observe only |
| tracing-opentelemetry | 0.33 | 0.28.0 | 0.33.0 | assess-later | mail_observe only |
| libp2p | 0.56 | 0.56.0 | 0.56.0 | hold | ipfs_swarm (already latest) |
| libp2p-stream | 0.4.0-alpha | 0.4.0-alpha | 0.4.0-alpha | hold | ipfs_swarm (latest alpha, pinned by design) |
azure_core_legacy (azure_core 0.21) | 0.21 | 0.21.0 | 1.1.0 (GA line) | blocked | mail_store azure tables backend |
| azure_data_tables | 0.21 | 0.21.0 | 0.21.0 | blocked | mail_store azure tables backend |
| azure_storage | 0.21 | 0.21.0 | 0.21.0 | blocked | mail_store azure tables backend |
| azure_core (GA) | 1.1 | 1.0.0 | 1.1.0 | hold | GA blob/queue/kv stack (minor only) |
| azure_identity | 1.0 | 1.0.0 | 1.0.0 | hold | GA stack (already latest) |
| azure_storage_blob / _queue | 1.0 | 1.0.0 | 1.0.0 | hold | already latest GA |
| azure_security_keyvault_secrets | 1.0 | 1.0.0 | 1.0.0 | hold | key_source (already latest GA) |
| mail-auth | 0.11 | 0.11.0 | 0.11.1 | assess-later / pin now — superseded 2026-08-04: adapted, caret kept; see the per-dependency note | smtp_server, mail_spooler, mail_submit, mail_client |
| reqwest | 0.13 | 0.13.4 | 0.13.4 | bump-now (pin floor) | wide (host crates) |
| thiserror | 2 | 2.0.18 | 2.0.18 | bump-now (pin floor) | wide |
| serde | 1 | 1.0.228 | 1.0.x | bump-now (pin floor) | wide |
| anyhow | 1 | 1.0.103 | 1.0.x | bump-now (pin floor) | wide |
| aes-gcm | 0.11 | 0.11.0 | 0.11.0 | bump-now (pin floor) | mail_crypto |
| hex | 0.4 | 0.4.3 | 0.4.3 | bump-now (pin floor) | wide |
| blake3 | 1 | 1.8.5 | 1.x | bump-now (pin floor) | program_common/solana_common |
| chrono | 0.4 | 0.4.45 | 0.4.x | bump-now (pin floor) | wide |
| base58 | 0.2 | 0.2.0 | 0.2.0 | bump-now (pin floor) | mail_client |
| num-traits | 0.2 | 0.2.19 | 0.2.19 | bump-now (pin floor) | model/programs |
| num-derive | 0.5 | 0.4.2 | 0.4.2 | bump-now (pin floor) | model/programs |
How to read the columns (their as-of dates differ). The Manifest column
is live: it tracks the root Cargo.toml [workspace.dependencies] as it stands
on 2026-08-04, after the bumps recorded in “Where this list stands” below. The
Resolved, Latest (index) and Verdict columns are the July 2026 audit
snapshot and are deliberately not refreshed — they are the evidence the
verdicts were reached on. Two consequences to expect while reading:
- Six rows — the five OpenTelemetry entries and GA
azure_core— now carry a Manifest requirement ahead of the Resolved version beside them, because those bumps have since been taken (audit items 3 and 4). - Eleven rows,
reqwestthroughnum-derive, carried a bare*when the audit was written; that is the hazard their “pin floor” verdict describes. The caret requirements shown are the fix, already landed (item 2), and for ten of the eleven the resolved version did not move — only the requirement’s notation did.
Per-dependency notes
OpenTelemetry stack (0.27 line + tracing-opentelemetry 0.28)
All five entries sit one coordinated release train behind by ~5–6 minor-major
steps: opentelemetry 0.27 → 0.33, -otlp 0.27 → 0.32, _sdk 0.27 → ~0.33,
-semantic-conventions 0.27 → 0.32.1, tracing-opentelemetry 0.28 → 0.33. In
OpenTelemetry’s 0.x SemVer each 0.x is a breaking major, and they only
inter-operate as a matched set (SDK, otlp, semantic-conventions, and the
tracing-opentelemetry bridge must all move together).
- Consuming surface is small but the API churn is not. The only workspace
consumer is
mail_observe/src/telemetry.rs(~150 lines, single file). But the 0.27→0.30+ API broke the exact symbols it uses:sdktrace::TracerProvider(renamedSdkTracerProvider),Resource::new(vec![KeyValue…])(replaced byResource::builder()),opentelemetry_sdk::runtime::Tokio+ thert-tokioruntime feature (the runtime seam was reworked / removed),PeriodicReader:: builder(exporter, Tokio), and theTraceError/MetricErrortypes (#[from]sources inObserveError) were restructured. - Coupled entry outside this manifest:
mail_observe/Cargo.tomlalso pinsopentelemetry-proto = "0.27"directly (not via workspace). It must move in lockstep with the workspace set. Flagging it here; the actual edit belongs to the bump wave, not this audit. - Duplicate old major is transitive, not ours.
Cargo.lockalso carriesopentelemetry 0.17.0+tracing-opentelemetry 0.17.4, pulled bytarpc 0.29.0(a transitive test/dev dep, reachable via the Solana program-test / surfpool stack). It is not manifest-addressable and is a harmless separate-major coexistence — the otel bump will not remove it.
Verdict: assess-later. Warranted (5+ majors behind, active telemetry crate)
but should be a dedicated small wave rewriting telemetry.rs against the target
train (pick the latest matched set: opentelemetry/_sdk/tracing-opentelemetry
0.33, -otlp 0.32, -semantic-conventions 0.32.1, -proto to match) with the
health/OTLP smoke path re-exercised.
libp2p 0.56 / libp2p-stream 0.4.0-alpha
The index cache tops out at libp2p 0.56.0 — i.e. the manifest is already on
the latest published release (offline? could not confirm a newer 0.57
against a live registry, but none is cached). libp2p-stream 0.4.0-alpha is
likewise the latest alpha, and is a recorded pinned-alpha adoption that rides
the exact libp2p-swarm/-core versions the lockfile holds. Verdict: hold.
No bump available/wanted; revisit only if a live check shows 0.57+.
Azure legacy 0.21 tables stack (blocked)
azure_data_tables 0.21 and azure_storage 0.21 are each the last published
release of the legacy pre-GA line — there is no newer major to take; the
successor is the not-yet-shipped Cosmos/Tables GA crate (planned ~late 2026).
azure_core_legacy is the intentional package = "azure_core", version = "0.21" alias these two drag in; the GA azure_core has since reached 1.1.0,
and the two majors coexist by design behind the mail_store trait seam.
Verdict: blocked for all three — no action until the GA tables crate ships;
re-open this row when it does.
- GA companions are effectively current:
azure_core 1.0has a minor 1.1.0 available (not a major — hold/optional);azure_identity,azure_storage_blob,azure_storage_queue,azure_security_keyvault_secretsare all at their latest 1.0.0 GA. Verdict: hold.
The *-versioned entries (aes-gcm, anyhow, hex, serde, thiserror, blake3, chrono, base58, num-traits, num-derive, reqwest)
Every * entry currently resolves to the latest published major already
(thiserror 2.x, serde 1.x, aes-gcm 0.11.0, reqwest 0.13.4, hex 0.4.3,
etc.), so there is nothing to bump up to. The finding here is the opposite: a
bare * grants Cargo license to adopt any future major on the next
cargo update, silently and non-reproducibly.
- This has already happened once in-tree:
reqwest = "*"resolved to the 0.13 major, while the rest of the graph (azure_core 0.21, solana-rpc-client 4.x, reqwest-middleware) still pullsreqwest 0.12.28. The*jumped a 0.x-major on its own. A futureserde 2.0orthiserror 3.0would be adopted the same way and could break the build for the next contributor who runscargo update, with no manifest signal that it was ever intended. - The duplicate
aes-gcm 0.10.3in the lock is transitive (viasnow 0.9.6, the Noise handshake under libp2p); our direct*is at 0.11.0. Not under our control. thiserror 1.0.69also coexists transitively; our direct*is 2.0.18.
Verdict: bump-now — but the “bump” is pinning a floor, not raising a version.
Replace each * with a caret floor at the current major so the lock stays
reproducible and surprise majors are opt-in:
aes-gcm = { version = "0.11", ... }
anyhow = "1"
hex = "0.4"
serde = { version = "1", features = ["derive"] }
thiserror = "2"
blake3 = "1"
chrono = "0.4"
base58 = "0.2"
num-traits = "0.2"
num-derive = "0.4"
reqwest = { version = "0.13", features = ["blocking"] }
No behavior change today (the lock already pins these exact versions); this only removes the silent-major hazard. Belongs to the downstream bump wave.
Outcome (2026-08-04): landed. All eleven entries carry a caret floor and the
Manifest column above reflects them. The one departure from the block: the
manifest took num-derive = "0.5" rather than the "0.4" proposed here, so
that row is a genuine major bump and not only a floor pin.
mail-auth — 0.11.0 vs 0.11.1 (the decision)
Manifest: mail-auth = { version = "0.11", default-features = false, features = ["ring", "report"] }. Lock: 0.11.0. 0.11.1 is published and
is an API-breaking patch (SemVer-illegal on a 0.11.x patch bump).
What actually broke in 0.11.1 (diffed from the two cached .crates): it lands
RFC 9989/9990/9991 DMARC support and, in doing so,
- removes
DmarcParameters::with_domain_suffix_fnfromdmarc/verify.rs— the organizational-domain folding hook the workspace relies on. It is called at three sites insmtp_server/src/policy.rs(lines ~393, ~547, ~1102), each.with_domain_suffix_fn(organizational_domain)for DMARC relaxed alignment (thepslpublic-suffix path). This is a hard compile break. - reworks the
report::Report/report/dmarcsurface (newDiscoveryenum; newwith_discovery_method/with_generator/with_npbuilders;npfield).mail_spooler/src/pipeline/dmarc_report.rsbuildsReportvia the fluentwith_*chain, so it must be re-validated against the new required/ optional fields even where it still compiles. - restructures
dmarc/mod.rs,dmarc/parse.rs,dmarc/verify.rsand the ARF parser — the verify semantics changed under RFC 9989.
Blast radius: mail-auth is a direct dep of four crates (smtp_server,
mail_spooler, mail_submit, mail_client); the DMARC verify + report surface
is exactly what 0.11.1 churned.
Recommendation (do this now, in the bump wave): pin mail-auth = "=0.11.0"
in the manifest (with the existing default-features = false, ["ring", "report"]). Rationale: the lock is already on 0.11.0 by luck of resolution;
making the pin explicit prevents a cargo update from silently pulling the
breaking 0.11.1 and snapping the three with_domain_suffix_fn call sites. The
0.11.1 adaptation (find the 0.11.1 organizational-domain replacement API, port
the three smtp_server sites, re-validate the mail_spooler DMARC report
builder against the RFC 9990/9991 fields, re-run the DMARC/SPF/DKIM policy
tests) is a real, DMARC-scoped refactor across two crates — schedule it as its
own item, not as part of a version bump. Only take 0.11.1 when the RFC
9989/9990/9991 features are actually wanted.
Outcome (2026-08-04): this recommendation was declined, deliberately.
The RFC 9989/9990/9991 features were wanted, so the adaptation was run as
its own wave and the manifest kept the caret requirement mail-auth = "0.11", which now resolves 0.11.1 — no exact pin. What that wave found, and
why the caret was the answer:
- The audit’s premise that a replacement folding hook exists was wrong.
with_domain_suffix_fnhas no successor: 9989’s DNS tree walk does the organizational fold insideverify_dmarc, and the port was a deletion of the three call sites, not a re-pointing. - Two of the four crates never touched the churned surface, so the “four
crates” blast radius overstated the work: the port landed in
smtp_serverandmail_spooleronly. - Pinning
=0.11.0would have frozen the workspace on the obsoleted RFC 7489 line — the opposite of where this protocol is going — for a hazard that a caret plus a committedCargo.lockalready contains.
The residual exposure is real and is accepted, not solved. A caret
requirement lets a future cargo update take another patch release from an
upstream that has already shipped one SemVer-illegal patch: 0.11.1 broke three
call sites while claiming to be a patch bump. The lockfile is what holds the
line between updates, so treat any cargo update touching mail-auth as a
change that needs the DMARC suites re-run, not as routine maintenance.
Prioritized recommendation list
- Pin
mail-auth = "=0.11.0"(highest urgency — a straycargo updatebreaks the build via the removedwith_domain_suffix_fn). Cheap, defensive. - Convert every
*entry to a caret major floor (see the block above). No behavior change; closes the silent-major hazard that already bitreqwest. Cheap, mechanical. - OpenTelemetry coordinated bump wave (0.27/0.28 → 0.33 train, incl.
mail_observe’s directopentelemetry-proto). Real value, single-file rewrite oftelemetry.rs, but needs its own scoped wave. Medium effort. - Azure GA
azure_core1.0 → 1.1 minor — optional, low value; hold unless a fix is needed. - Azure legacy tables stack — no action; blocked on the Cosmos/Tables GA crate (~late 2026). Track upstream.
- libp2p / libp2p-stream — hold; already at latest published/alpha.
Where this list stands (2026-08-04). Items 1–4 have all been answered, so read them as history rather than as work:
- Item 1 — declined. The 0.11.1 adaptation was taken instead and the
manifest carries the caret
0.11; see the outcome note above for the reasoning and the residual exposure. - Item 2 — done. No
*requirement remains in[workspace.dependencies]; all eleven entries carry a caret major floor, and the summary table’s Manifest column shows them. One went further than the proposal above:num-deriveis on0.5, not the0.4the block suggests. - Item 3 — done. The OpenTelemetry stack is on the 0.32 line with
tracing-opentelemetry0.33 (its minor runs one ahead of core). - Item 4 — done.
azure_core(GA) is at1.1; the legacy 0.21 tables stack is untouched and still blocked, as item 5 says.
Anything urgent
Nothing on this list is urgent any more (see the status block above). As the
audit was written, item 1 (mail-auth) was the only time-sensitive entry, and
it was a hazard from inaction — a future cargo update — rather than a live
breakage. Everything else was planned, deferrable maintenance.