Defect
edtf publishes edtf-postgres promising rust-version = "1.82", inherited
from [workspace.package], and that promise is false against edtf's own
lockfile. Measured 2026-08-24 on edtf@2c2a1ed with the belt from #813's
branch, at the pinned 1.82.0 toolchain:
$ RUSTUP_TOOLCHAIN=1.82.0 cargo check -p edtf-postgres --locked \
--no-default-features --features pg18
error: failed to parse manifest at `.../serde_spanned-1.1.1/Cargo.toml`
feature `edition2024` is required
... not stabilized in this version of Cargo (1.82.0)
Walking the toolchain up finds two more walls and then the real one.
1.85.0 hits icu_collections/icu_normalizer/icu_properties@2.2.0 requires rustc 1.86, reached through the same build-script closure. And
1.86.0 hits the one that actually decides it:
error: rustc 1.86.0 is not supported by the following packages:
pgrx@0.19.2 requires rustc 1.96
pgrx-bindgen@0.19.2 requires rustc 1.96
pgrx-macros@0.19.2 requires rustc 1.96
pgrx-pg-config@0.19.2 requires rustc 1.96
pgrx-pg-sys@0.19.2 requires rustc 1.96
pgrx-sql-entity-graph@0.19.2 requires rustc 1.96
So the floor is not an obscure transitive detail at all: pgrx, the
crate's own DIRECT dependency, declares rust-version = "1.96".
edtf-postgres promises 1.82 while the library it is built on promises
1.96 — a fourteen-minor gap between a published promise and the thing it
depends on, sitting in plain sight in a manifest nobody was comparing.
The other five members compile clean at 1.82.0; only the extension is
affected.
Why nothing caught it until now. lint:msrv (canon #820) verifies
every crate the gate compiles, and edtf-postgres was not one: it sat in
CLIPPY_EXCLUDE, outside the gate's compile surface, which is the whole
defect canon #813 exists to end. The moment #813's branch widens the
surface to the publish path's, the MSRV check reaches the crate and the
promise fails on its first run. That is the two issues working exactly as
intended, and it is the reason this is filed rather than quietly patched.
Scope of the claim, stated precisely. This is --locked: it says
edtf's own lockfile cannot build the extension at 1.82. A downstream
consumer resolving fresh on 1.82 might land on older icu/toml versions
and succeed, because cargo's resolver is MSRV-aware. The org verifies the
locked form deliberately — it is the only deterministic one, and it is what
every other Rust check in the belt measures — so "our lockfile builds at
our declared minimum" is the promise being kept, and it is currently not.
Decided build
The declaration moves to the truth; the truth is not moved to the
declaration.
edtf-postgres declares its own rust-version rather than
inheriting the workspace's — the crate genuinely has a different
dependency closure from the five pure-Rust members, and cargo supports
a per-package value for exactly this.
- The value is measured, not guessed: the lowest toolchain at which
cargo check -p edtf-postgres --locked --no-default-features --features pgNN passes. That is 1.96, which is what pgrx itself declares —
confirmed by running: cargo check at the 1.96.0 pin finishes
clean (3m57s), and at 1.86.0 it does not.
- The repository pins that toolchain beside the 1.82.0 one, and canon
lint:msrv then verifies both minimums, each at its own pin — the
multi-MSRV path that check already supports and that no repository had
exercised until now. Patch-exact, so a declared 1.96 wants a 1.96.0 pin.
- A standing consequence worth writing down: this crate's minimum is
pgrx's minimum, so it moves whenever the pgrx pin moves. The
agreement check turns that from a silent drift into a red Renovate PR
with an obvious one-line remedy, which is the right place for it.
[workspace.package] rust-version stays 1.82 for the five members that
genuinely meet it. Raising the whole workspace to 1.86 to make one crate
honest would be a false promise in the other direction, tightening a
minimum five published crates actually keep.
Rejected, recorded: pinning older icu/serde_spanned in Cargo.lock to
hold the 1.82 line — it is dependency surgery on a transitive closure the
repository does not own, to keep a number nobody chose deliberately;
edtf-postgres inherited 1.82 because inheritance was the default, not
because 1.82 was ever measured for it. Also rejected: excluding the
extension from lint:msrv, which is the exclusion that hid this for forty
releases wearing a different hat.
Canon consequence
None to the belt: lint:msrv and gate-surface.py already do the right
thing, and finding this is them doing it. This is edtf's repo-side change
and rides its canon pin bump for #813. release-lab is unaffected — its
declared minimum is its build toolchain, so its lab-pg meets it by
construction.
One thing the canon should note either way: this is the first repository
in the org to need two MSRV toolchains, so it is the first real
exercise of msrv-plan.py's per-declaration grouping, which until now was
covered only by table tests.
Done when
the floor is measured by running — done: 1.96.0 compiles the crate
clean, 1.86.0 does not, and pgrx@0.19.2 declares 1.96 itself
edtf-postgres declares that floor and the repository pins it
mise run lint:msrv on edtf is green and its output names both
minimums, each compiled at its own pinned toolchain
- the five other members still declare and still meet 1.82 — planted by
re-running after the change, not assumed
- the crates.io page for the next
edtf-postgres release shows the new
minimum, since it is a public promise that changed
Sequencing
After canon #813 (which found it) and its edtf pin bump; the change lands
in the same repo-side commit if that is still open, otherwise its own.
Refs .github#813, .github#820, edtf#172.
Defect
edtf publishes
edtf-postgrespromisingrust-version = "1.82", inheritedfrom
[workspace.package], and that promise is false against edtf's ownlockfile. Measured 2026-08-24 on
edtf@2c2a1edwith the belt from #813'sbranch, at the pinned 1.82.0 toolchain:
Walking the toolchain up finds two more walls and then the real one.
1.85.0 hits
icu_collections/icu_normalizer/icu_properties@2.2.0 requires rustc 1.86, reached through the same build-script closure. And1.86.0 hits the one that actually decides it:
So the floor is not an obscure transitive detail at all:
pgrx, thecrate's own DIRECT dependency, declares
rust-version = "1.96".edtf-postgrespromises 1.82 while the library it is built on promises1.96 — a fourteen-minor gap between a published promise and the thing it
depends on, sitting in plain sight in a manifest nobody was comparing.
The other five members compile clean at 1.82.0; only the extension is
affected.
Why nothing caught it until now.
lint:msrv(canon #820) verifiesevery crate the gate compiles, and
edtf-postgreswas not one: it sat inCLIPPY_EXCLUDE, outside the gate's compile surface, which is the wholedefect canon #813 exists to end. The moment #813's branch widens the
surface to the publish path's, the MSRV check reaches the crate and the
promise fails on its first run. That is the two issues working exactly as
intended, and it is the reason this is filed rather than quietly patched.
Scope of the claim, stated precisely. This is
--locked: it saysedtf's own lockfile cannot build the extension at 1.82. A downstream
consumer resolving fresh on 1.82 might land on older
icu/tomlversionsand succeed, because cargo's resolver is MSRV-aware. The org verifies the
locked form deliberately — it is the only deterministic one, and it is what
every other Rust check in the belt measures — so "our lockfile builds at
our declared minimum" is the promise being kept, and it is currently not.
Decided build
The declaration moves to the truth; the truth is not moved to the
declaration.
edtf-postgresdeclares its ownrust-versionrather thaninheriting the workspace's — the crate genuinely has a different
dependency closure from the five pure-Rust members, and cargo supports
a per-package value for exactly this.
cargo check -p edtf-postgres --locked --no-default-features --features pgNNpasses. That is 1.96, which is whatpgrxitself declares —confirmed by running:
cargo checkat the 1.96.0 pin finishesclean (3m57s), and at 1.86.0 it does not.
lint:msrvthen verifies both minimums, each at its own pin — themulti-MSRV path that check already supports and that no repository had
exercised until now. Patch-exact, so a declared 1.96 wants a 1.96.0 pin.
pgrx's minimum, so it moves whenever the pgrx pin moves. Theagreement check turns that from a silent drift into a red Renovate PR
with an obvious one-line remedy, which is the right place for it.
[workspace.package] rust-versionstays 1.82 for the five members thatgenuinely meet it. Raising the whole workspace to 1.86 to make one crate
honest would be a false promise in the other direction, tightening a
minimum five published crates actually keep.
Rejected, recorded: pinning older
icu/serde_spannedinCargo.locktohold the 1.82 line — it is dependency surgery on a transitive closure the
repository does not own, to keep a number nobody chose deliberately;
edtf-postgresinherited 1.82 because inheritance was the default, notbecause 1.82 was ever measured for it. Also rejected: excluding the
extension from
lint:msrv, which is the exclusion that hid this for fortyreleases wearing a different hat.
Canon consequence
None to the belt:
lint:msrvandgate-surface.pyalready do the rightthing, and finding this is them doing it. This is edtf's repo-side change
and rides its canon pin bump for #813. release-lab is unaffected — its
declared minimum is its build toolchain, so its
lab-pgmeets it byconstruction.
One thing the canon should note either way: this is the first repository
in the org to need two MSRV toolchains, so it is the first real
exercise of
msrv-plan.py's per-declaration grouping, which until now wascovered only by table tests.
Done when
the floor is measured by running— done: 1.96.0 compiles the crateclean, 1.86.0 does not, and
pgrx@0.19.2declares 1.96 itselfedtf-postgresdeclares that floor and the repository pins itmise run lint:msrvon edtf is green and its output names bothminimums, each compiled at its own pinned toolchain
re-running after the change, not assumed
edtf-postgresrelease shows the newminimum, since it is a public promise that changed
Sequencing
After canon #813 (which found it) and its edtf pin bump; the change lands
in the same repo-side commit if that is still open, otherwise its own.
Refs .github#813, .github#820, edtf#172.