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

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-sbf toolchain 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.md to avoid colliding with the concurrent docs lanes. It can be linked later under Appendix: Reference (alongside appendix/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

DependencyManifestResolvedLatest (index)VerdictBlast radius
opentelemetry0.320.27.10.33.0assess-latermail_observe only (1 file)
opentelemetry-otlp0.320.27.00.32.0assess-latermail_observe only
opentelemetry_sdk0.320.27.1~0.33 (train)assess-latermail_observe only
opentelemetry-semantic-conventions0.320.27.00.32.1assess-latermail_observe only
tracing-opentelemetry0.330.28.00.33.0assess-latermail_observe only
libp2p0.560.56.00.56.0holdipfs_swarm (already latest)
libp2p-stream0.4.0-alpha0.4.0-alpha0.4.0-alphaholdipfs_swarm (latest alpha, pinned by design)
azure_core_legacy (azure_core 0.21)0.210.21.01.1.0 (GA line)blockedmail_store azure tables backend
azure_data_tables0.210.21.00.21.0blockedmail_store azure tables backend
azure_storage0.210.21.00.21.0blockedmail_store azure tables backend
azure_core (GA)1.11.0.01.1.0holdGA blob/queue/kv stack (minor only)
azure_identity1.01.0.01.0.0holdGA stack (already latest)
azure_storage_blob / _queue1.01.0.01.0.0holdalready latest GA
azure_security_keyvault_secrets1.01.0.01.0.0holdkey_source (already latest GA)
mail-auth0.110.11.00.11.1assess-later / pin nowsuperseded 2026-08-04: adapted, caret kept; see the per-dependency notesmtp_server, mail_spooler, mail_submit, mail_client
reqwest0.130.13.40.13.4bump-now (pin floor)wide (host crates)
thiserror22.0.182.0.18bump-now (pin floor)wide
serde11.0.2281.0.xbump-now (pin floor)wide
anyhow11.0.1031.0.xbump-now (pin floor)wide
aes-gcm0.110.11.00.11.0bump-now (pin floor)mail_crypto
hex0.40.4.30.4.3bump-now (pin floor)wide
blake311.8.51.xbump-now (pin floor)program_common/solana_common
chrono0.40.4.450.4.xbump-now (pin floor)wide
base580.20.2.00.2.0bump-now (pin floor)mail_client
num-traits0.20.2.190.2.19bump-now (pin floor)model/programs
num-derive0.50.4.20.4.2bump-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, reqwest through num-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 (renamed SdkTracerProvider), Resource::new(vec![KeyValue…]) (replaced by Resource::builder()), opentelemetry_sdk::runtime::Tokio + the rt-tokio runtime feature (the runtime seam was reworked / removed), PeriodicReader:: builder(exporter, Tokio), and the TraceError/MetricError types (#[from] sources in ObserveError) were restructured.
  • Coupled entry outside this manifest: mail_observe/Cargo.toml also pins opentelemetry-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.lock also carries opentelemetry 0.17.0 + tracing-opentelemetry 0.17.4, pulled by tarpc 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.0 has a minor 1.1.0 available (not a major — hold/optional); azure_identity, azure_storage_blob, azure_storage_queue, azure_security_keyvault_secrets are 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 pulls reqwest 0.12.28. The * jumped a 0.x-major on its own. A future serde 2.0 or thiserror 3.0 would be adopted the same way and could break the build for the next contributor who runs cargo update, with no manifest signal that it was ever intended.
  • The duplicate aes-gcm 0.10.3 in the lock is transitive (via snow 0.9.6, the Noise handshake under libp2p); our direct * is at 0.11.0. Not under our control.
  • thiserror 1.0.69 also 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_fn from dmarc/verify.rs — the organizational-domain folding hook the workspace relies on. It is called at three sites in smtp_server/src/policy.rs (lines ~393, ~547, ~1102), each .with_domain_suffix_fn(organizational_domain) for DMARC relaxed alignment (the psl public-suffix path). This is a hard compile break.
  • reworks the report::Report / report/dmarc surface (new Discovery enum; new with_discovery_method / with_generator / with_np builders; np field). mail_spooler/src/pipeline/dmarc_report.rs builds Report via the fluent with_* 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.rs and 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_fn has no successor: 9989’s DNS tree walk does the organizational fold inside verify_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_server and mail_spooler only.
  • Pinning =0.11.0 would 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 committed Cargo.lock already 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

  1. Pin mail-auth = "=0.11.0" (highest urgency — a stray cargo update breaks the build via the removed with_domain_suffix_fn). Cheap, defensive.
  2. Convert every * entry to a caret major floor (see the block above). No behavior change; closes the silent-major hazard that already bit reqwest. Cheap, mechanical.
  3. OpenTelemetry coordinated bump wave (0.27/0.28 → 0.33 train, incl. mail_observe’s direct opentelemetry-proto). Real value, single-file rewrite of telemetry.rs, but needs its own scoped wave. Medium effort.
  4. Azure GA azure_core 1.0 → 1.1 minor — optional, low value; hold unless a fix is needed.
  5. Azure legacy tables stack — no action; blocked on the Cosmos/Tables GA crate (~late 2026). Track upstream.
  6. 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-derive is on 0.5, not the 0.4 the block suggests.
  • Item 3 — done. The OpenTelemetry stack is on the 0.32 line with tracing-opentelemetry 0.33 (its minor runs one ahead of core).
  • Item 4 — done. azure_core (GA) is at 1.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.