Skip to content

The whole red cargo column on main is one unsatisfiable pair: testcontainers ^0.28 vs testcontainers-modules ^0.15 #838

Closed

Description

@hyperpolymath

The finding

Every cargo-touching check on main is red for one reason: the integration
crate pins two crates that cannot co-exist.

From Build AsciiDoc / "Generate Rust API documentation", run 35763405727:

error: failed to select a version for `testcontainers`.
    ... required by package `testcontainers-modules v0.15.0`
    ... which satisfies dependency `testcontainers-modules = "^0.15"`
        of package `hypatia-integration-tests v0.1.0` (/integration)
versions that meet the requirements `^0.27.0` are: 0.27.3, 0.27.2, 0.27.1, 0.27.0

all possible versions conflict with previously selected packages

  previously selected package `bollard v0.21.0`
    ... which satisfies dependency `bollard = "^0.21.0"` of package `testcontainers v0.28.0`
    ... which satisfies dependency `testcontainers = "^0.28"` of package `hypatia-integration-tests`

failed to select a version for `testcontainers` which could resolve this conflict

integration/Cargo.toml asks for testcontainers = "^0.28" and
testcontainers-modules = "^0.15", but testcontainers-modules 0.15 requires
testcontainers ^0.27. The two requirements are unsatisfiable together, so
resolution fails before any compilation starts.

Blast radius

This is almost certainly the single cause behind the whole red cargo column on
main — Build AsciiDoc, Cargo check + clippy + fmt, Check, Clippy,
Test. Resolution failure happens before the step's actual subject, so each of
those checks reports a failure that has nothing to do with what it is named for.
Build AsciiDoc in particular is not an AsciiDoc failure: its AsciiDoc steps
pass and it dies later, in a cargo step.

Acceptance criteria

  1. cargo metadata --locked (or cargo check --workspace --locked) resolves
    with rc=0 — fix by moving to a compatible pair, e.g. testcontainers ^0.27
    with testcontainers-modules ^0.15, or testcontainers-modules ^0.16+ if it
    supports ^0.28. Check the actual published matrix; do not guess.
  2. Cargo check + clippy + fmt, Check, Clippy and Test are all green on
    main, evidenced from /commits/<sha>/check-runs.
  3. Build AsciiDoc green — and if it is still red afterwards, that residual is a
    different fault and gets its own issue rather than being folded in here.
  4. Cargo.lock is committed and CI builds use --locked, so a future
    incompatible bump fails at the lockfile rather than resolving differently in
    CI than locally.
  5. A mutant proves the guard: reintroduce the incompatible pair on a branch and
    confirm the check goes red at resolution.

Note on naming

Worth renaming Build AsciiDoc, or splitting its cargo-doc step out. A check
whose name describes one subject and whose failures come from another is how a
real fault goes unread for weeks.

Found while preparing #835. Related: #836, #837.

Activity

  1. hyperpolymath commented on Sep 22, 2026

    @hyperpolymath
    OwnerAuthor

    Fixed by #840, merged as 4fa2e393181d97c4a0679974399930fac64ff3f5 at
    2026-09-22T18:25:58Z.

    The hypothesis held, and the mechanism is now measured

    This issue predicted that one unsatisfiable pair was behind the whole red cargo
    column. It was. Side-by-side, from /commits/<sha>/check-runs:

    Check main @ c18608e (before) main @ 4fa2e39 (after)
    Build AsciiDoc failure success
    Cargo check + clippy + fmt failure success
    Check failure success
    Clippy failure success
    Test failure success
    Format success success

    Format is the diagnostic, and it was in the data all along. cargo fmt is
    the only cargo subcommand that does not resolve the dependency graph — so it was
    the only one that survived. Five red checks with five different subject names
    and one green outlier is the signature of a resolution failure, not of five
    faults. Partitioning the checks by "does this subcommand resolve?" localizes it
    in one step, without opening a single log.

    And as predicted, Build AsciiDoc was never an AsciiDoc failure: it is green
    now with no AsciiDoc change in the PR whatsoever.

    The fix was a removal, not a version bump

    There was no compatible pair to move to. As of 2026-09-22, crates.io shows
    testcontainers newest 0.28.0 and testcontainers-modules newest 0.15.0,
    which still requires testcontainers ^0.27. The modules crate lags.

    The decisive fact was that nothing imported it — git grep -rn 'testcontainers_modules' returns no hits. Dragonfly/Redis is provisioned by
    integration/compose.test.yaml and reached via DRAGONFLY_URL
    (integration/src/lib.rs:109-110). The crate that reddened the entire workspace
    contributed nothing, so it was deleted rather than negotiated down. That keeps
    the newest testcontainers and drops 8 packages (ppv-lite86, rand,
    rand_chacha, rand_core, testcontainers-modules, ureq, ureq-proto,
    utf8-zero) — including ureq, a second HTTP client alongside reqwest.
    Zero packages added. governance / Trusted-base reduction policy passes.

    The rand removals were duplicate-version removals, not a loss: rand v0.10.2
    remains on main, still required by bollard, ferroid and the integration
    crate directly.

    Acceptance criteria

    1. Met. cargo metadata --locked and cargo check --workspace --all-targets --locked both rc=0. Resolved against the actual published matrix, not a guess.
    2. Met. All four named checks green on main @ 4fa2e39, evidenced above
      from /commits/<sha>/check-runs.
    3. Met. Build AsciiDoc green on main, with no residual failure.
    4. Half met — split to CI never passes --locked, so the committed Cargo.lock constrains nothing #841. Cargo.lock is committed and updated. The other
      half is not done: no cargo invocation in any workflow passes --locked
      (measured: zero hits across .github/workflows/), so the committed lockfile
      currently constrains nothing in CI. That needs workflow edits, which require
      the workflow OAuth scope this session lacks. Filed as CI never passes --locked, so the committed Cargo.lock constrains nothing #841 rather than
      left silently ticked.
    5. Met by the pre-existing state. main was itself the mutant: it carried
      the incompatible pair, and cargo metadata there returned rc=101 with the
      resolution error quoted in this issue from run 35763405727. The failure was
      at resolution, as the criterion requires.

    Two things found alongside

    Closing. The naming concern raised in this issue — a check whose name describes
    one subject and whose failures come from another — stands on its own and is
    worth acting on separately.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions