chore(deps): move reqwest to 0.13 and adopt the fleet TLS-provider convention - #731
Conversation
…nvention
harmonia ran reqwest 0.12 with the `rustls-tls` feature while aletheia and
theatron ran 0.13 with `rustls-no-provider`. The version gap mattered less than
the feature: aletheia's manifest documents a fleet convention that the ring
crypto provider is installed once, explicitly, process-wide, and that no library
is ever allowed to link one implicitly. `rustls-tls` does exactly what that
forbids.
So this is two changes, and the second is the one that can hurt. Building with
`rustls-no-provider` means any process performing TLS must install a provider
itself, and failing to is a RUNTIME PANIC on first use rather than a compile
error -- it would pass CI and fail in the operator's hands.
Both binary targets are covered at their real entry points:
crates/archon/src/main.rs the `harmonia` binary, install added directly
crates/theatron/desktop/src/main.rs calls periskopio::run(), whose first
statement is the install
The eight library crates that depend on reqwest are deliberately untouched:
installing a provider from a library is the implicit linking the convention
exists to prevent.
`form` and `query` join the feature list because 0.13 moved them behind
features. periskopio now inherits reqwest from the workspace rather than
carrying its own pin, so the two cannot drift again.
Authored by an agent whose result was lost when this box thermally shut down
mid-run; the tree survived and every claim above was re-derived from it rather
than taken on trust. Not compiler-verified here -- harmonia is public, its
runner minutes are free, and this laptop shuts down under a workspace check.
…cannot inherit
This branch had periskopio reading `reqwest.workspace = true` and
`rustls.workspace = true`. crates/theatron/desktop is in the ROOT manifest's
`exclude` list, not `members`, so it has no workspace to inherit from and cargo
refuses the manifest outright:
error inheriting `reqwest` from workspace root manifest's
`workspace.dependencies.reqwest`
failed to find a workspace root
Declared explicitly instead, with a comment saying why, because this is the
second time today this exact exclusion has produced this exact error -- the
licence fix in #725 hit it first. A reader who does not know the crate is
excluded will reach for inheritance again.
Features match what periskopio declared before this branch -- json, plus
rustls-no-provider where it read rustls-tls. Not the root's fuller set: it never
used `form`, `query` or `stream`, and inheriting features a crate does not
exercise is how a dependency graph grows without anyone deciding to.
Verified the manifest now parses standalone via its own --manifest-path, which
is how CI checks it.
Second blocker found: this is entangled with librqbitThe manifest-inheritance failure is fixed (an excluded crate cannot inherit — see the follow-up commit). The remaining The lockfile carries reqwest 0.12.28 and 0.13.4, and
The entanglement runs both ways#724 proposes librqbit 8.1.1 → 9.0.1, and a prior read of its CI found that bump also reports duplicate-version violations, naming the same crates: I have not established which reqwest DispositionLeft open. The TLS-provider alignment in this PR is correct and worth keeping — it is the fleet convention, both binaries install the ring provider at their real entry points, and that part is independent of the version question. What it cannot do is land while librqbit holds reqwest 0.12. These two PRs need sequencing, and #724 is the one carrying the actual work. |
…three dead advisories librqbit 9 widens the graph and cargo-deny's multiple-versions rule refuses it. Three duplicates, and only one is genuinely ours to exempt. atoi is upstream-blocked in both directions: sqlx-sqlite's newest release still pins 2.0 and librqbit 9 pins 3. cargo update cannot close a gap where both sides are already newest and disagree on a major, so it is skipped with the specific crates and versions named. reqwest is deliberately NOT skipped. Our own manifest caps us below 0.13, and lifting that cap is #731's core change -- so this unifies by sequencing rather than by exemption. wasm-streams is purely downstream of reqwest and needs no action of its own. Three advisory exceptions are dead and removed. bincode is entirely absent from this branch's lock where main carried two versions, so RUSTSEC-2025-0141 and its two skip entries go. Checking that turned up the same shape in quick-xml: main had two versions, this branch has one because librqbit-upnp no longer pins the old range, so RUSTSEC-2026-0194 and -0195 go with it. A comment on an unrelated syn entry that referred back to bincode is corrected rather than left dangling. The derived ignore files are regenerated through the tool their own header names as the source of truth, not hand-edited, and check clean against deny.toml.
reqwest 0.13 requires a process-level crypto provider before a Client can be built, and this branch installs one at each real binary entry point. Test binaries never call main(), so nextest ran every one of them without it and the first client build panicked: No rustls crypto provider is configured... you must install a crypto provider before building a Client nextest then cancelled with three tests still running, so the reported blast radius was the count before the abort rather than the set. Swept the workspace for Client::new, Client::builder and reqwest::get -- 88 call sites across 29 files -- and covered all of them. Where a crate already funnels its tests through a shared spawn helper the install goes there, one call covering roughly seventy sites; the rest are fixed at the site because they build a client without the fixture. The pattern follows aletheia's existing organon::testing::install_crypto_provider rather than inventing a new one, and install_default is the only lever: reqwest 0.13.4 has no per-call override. Worth knowing for review: `.unwrap_or_default()` does NOT catch this. It is a real panic, not a Result::Err, so nine production sites carry a comment saying the build "fails only with invalid TLS config" that is now wrong. Harmless today because main() installs first; left alone rather than widening this change. The three ambiguous rustls builder sites are fixed here too. They do not panic on this branch -- aws-lc-rs is absent from its lock -- but librqbit 9 reintroduces the ambiguity through reqwest's redefined default-tls, so fixing them now means they are already right when that rebases, and avoids a conflict. The reqwest and wasm-streams duplicates are skipped TEMPORARILY, labelled as such and naming the PR that resolves them: our own manifest caps reqwest below 0.13, so only this change can lift it, and the versions converge once librqbit 9 rebases on top. The skip should be deleted then rather than left standing.
…tests The first sweep found every direct client construction and then misjudged some of them. A site was marked "production, safe -- main() installs the provider" after checking whether its OWN crate's tests reached it through a test seam. That check is per-crate, and the reach that matters is not. archon's supervisor test constructs epignosis's real ProviderBackedResolver by name -- not a mock, not a seam -- so epignosis's production reqwest builder ran inside a test binary that never called main(). A per-crate audit cannot see that; the question has to be "does any test ANYWHERE in the workspace call this constructor", asked across the whole tree. Re-asked that way, seven more crates had reachable sites. Fixed at the choke points where one exists -- epignosis's shared resolver fixture covers sixteen sites, eksetasis's service builder covers twenty-three, archon's feed scheduler covers four -- and per-site where no fixture funnels them. Sites confirmed already covered are listed in the PR rather than touched: every PlexClient and ByparrProxy test is preceded by a spawn helper that installs, and ergasia reaches librqbit 8's separate reqwest instance, which this branch's feature does not affect. The nine wrong `.unwrap_or_default()` comments are corrected. They claimed the build "fails only with invalid TLS config"; it panics, and unwrap_or_default cannot intercept a panic. Each now says so and names the install that makes its call site safe.
…ve client Completes the sweep by enumeration rather than by waiting for CI to name the next one. serve.rs has five test modules, not one; all 46 tests are now accounted for individually -- constructor called, whether it can reach a client build, and why not where it cannot. Ten reach a client and were already covered. The eleventh, zetesis_supervisor_tick_refreshes_stale_caps_and_skips_fresh, constructs eksetasis::SearchIndexerService::new directly, at a second call site of a constructor already fixed at its first. That is the whole miss, and it is worth naming: the previous method -- grep each named constructor across the workspace -- was right and did surface this line. It was not followed to the end of its own results. Every constructor fixed in this branch has now had its full grep result count reconciled. Crates confirmed unable to reach a client are recorded rather than assumed: kritike, kathodos, paroche and syntaxis declare no reqwest dependency, and ergasia reaches librqbit 8's separate reqwest instance, which this branch's feature does not affect.
Note on why this took several rounds, and the mechanism that would end the classThree rounds of "CI names one test, fix it, CI names the next". Worth recording, because the cause is not what it looks like. nextest cancels on first failure, so every list CI produced was a sample, not a census. Treating each one as the full set guaranteed another round. The final pass replaced that with enumeration: all 46 tests across The last miss deserves naming honestly: the method — grep each named constructor across the whole workspace — was correct and did surface the failing line. It was not followed to the end of its own results; the first hit was fixed and a second, further down, was not checked. Every constructor touched in this branch has now had its full grep result count reconciled. The automatic mechanism, evaluated and deliberately not takenA A true per-binary init needs Where it would genuinely help is Not done here. It introduces a new workspace dependency with no prior use in this codebase, and it is the kind of call that deserves an explicit decision rather than riding in on a "fix the named test" pass. Recorded so it is available if this class recurs. |
…ared helper Enumerated every test in all eight crates that depend on reqwest directly -- the eighth, theatron/desktop, had never been checked and turns out to construct no client at all. The failure shape is not what the earlier rounds assumed. It is not that a test forgot to spawn a mock server. It is that a shared local helper funnels most tests through a safe path while a minority in the same file construct at the site, bypassing the precondition entirely. cardigann has 52 tests behind a three-tier wrapper and two that go straight to CardigannClient::new; the wrapper is fixed once and both direct sites individually. newznab and torznab each funnel every test through one client() helper, so those are one call each. ByparrProxy::new is hardened at the constructor as defence-in-depth even though its tests were already safe. Worth recording because it nearly repeated the miss: syndesmos's plex/collections and plex/stats contain ZERO literal "reqwest" and still construct a real PlexClient at eight sites. A keyword sweep calls those files safe. All eight are preceded by an installing spawn helper in the prior statement, so they need no change -- but only reading each body establishes that.
Five CI rounds established that per-site installation does not converge here. Each round the sweep was keyed on something -- a call pattern, a constructor name, a crate boundary -- and each round some path did not use that key. The last one reached a client through a registry entry point rather than CardigannClient::new, so a careful count of that constructor was correct and still missed it. A constructor that runs at binary load removes the precondition instead of satisfying it repeatedly. There is no longer a question of whether a given test's construction path was preceded by the right call. Every per-site install is deleted rather than left alongside it, including one in search/tests.rs from an earlier round. Keeping both would leave a reader unable to tell which is load-bearing, and the dead one rots. The attribute form is read from the crate's live documentation, not recalled: ctor 1.0 made `unsafe` mandatory inside the attribute, so bare `#[ctor]` is rejected. The function body stays safe; the unsafe is on the attribute. Scoped to this crate deliberately. `#[cfg(test)]` code is compiled per crate, so this protects eksetasis's own tests and nothing else -- a constructor here would never load into another crate's test binary. syndesmos, the only other crate that has produced this bypass in the wild, is tracked separately in #737.
…three dead advisories librqbit 9 widens the graph and cargo-deny's multiple-versions rule refuses it. Three duplicates, and only one is genuinely ours to exempt. atoi is upstream-blocked in both directions: sqlx-sqlite's newest release still pins 2.0 and librqbit 9 pins 3. cargo update cannot close a gap where both sides are already newest and disagree on a major, so it is skipped with the specific crates and versions named. reqwest is deliberately NOT skipped. Our own manifest caps us below 0.13, and lifting that cap is #731's core change -- so this unifies by sequencing rather than by exemption. wasm-streams is purely downstream of reqwest and needs no action of its own. Three advisory exceptions are dead and removed. bincode is entirely absent from this branch's lock where main carried two versions, so RUSTSEC-2025-0141 and its two skip entries go. Checking that turned up the same shape in quick-xml: main had two versions, this branch has one because librqbit-upnp no longer pins the old range, so RUSTSEC-2026-0194 and -0195 go with it. A comment on an unrelated syn entry that referred back to bincode is corrected rather than left dangling. The derived ignore files are regenerated through the tool their own header names as the source of truth, not hand-edited, and check clean against deny.toml.
Rebased onto #731. The prediction it carried holds: with archon on reqwest 0.13 and librqbit 9 resolving to 0.13.4, the two converge -- the lock now has ONE reqwest entry and wasm-streams 0.4.2 drops out entirely. So #731's temporary skips are removed rather than duplicated here, which is what its own comment asked for: one exemption, removed once, not two. bincode goes with them; it is absent from this lock, since librqbit 9 no longer pulls the 1.x line. atoi stays, and it is the only duplicate genuinely exempted: sqlx-sqlite pins 2.0 at its newest release and librqbit 9 pins 3, so no cargo update closes it. The rustls provider fix is not re-applied -- #731 landed the same change in archon and syndesis, and this branch takes main's version of both files. The lock is regenerated by cargo rather than hand-merged through the rebase conflict. Each side had resolved with the other's crate still present, so combining them by hand would have produced a file cargo would not.
harmonia ran reqwest 0.12 with the
rustls-tlsfeature while aletheia andtheatron ran 0.13 with
rustls-no-provider. The version gap mattered less thanthe feature: aletheia's manifest documents a fleet convention that the ring
crypto provider is installed once, explicitly, process-wide, and that no library
is ever allowed to link one implicitly.
rustls-tlsdoes exactly what thatforbids.
So this is two changes, and the second is the one that can hurt. Building with
rustls-no-providermeans any process performing TLS must install a provideritself, and failing to is a RUNTIME PANIC on first use rather than a compile
error -- it would pass CI and fail in the operator's hands.
Both binary targets are covered at their real entry points:
crates/archon/src/main.rs the
harmoniabinary, install added directlycrates/theatron/desktop/src/main.rs calls periskopio::run(), whose first
statement is the install
The eight library crates that depend on reqwest are deliberately untouched:
installing a provider from a library is the implicit linking the convention
exists to prevent.
formandqueryjoin the feature list because 0.13 moved them behindfeatures. periskopio now inherits reqwest from the workspace rather than
carrying its own pin, so the two cannot drift again.
Authored by an agent whose result was lost when this box thermally shut down
mid-run; the tree survived and every claim above was re-derived from it rather
than taken on trust. Not compiler-verified here -- harmonia is public, its
runner minutes are free, and this laptop shuts down under a workspace check.