Skip to content

chore: correct the SPDX licence, state the resolver, and pin the MSRV - #273

Merged
mfw78 merged 1 commit into
mainfrom
chore/spdx-resolver-msrv
Aug 17, 2026
Merged

chore: correct the SPDX licence, state the resolver, and pin the MSRV#273
mfw78 merged 1 commit into
mainfrom
chore/spdx-resolver-msrv

Conversation

@mfw78

@mfw78 mfw78 commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

What this unblocks

The workspace declared license = "AGPL-3.0", a bare GPL-family identifier that the SPDX list deprecates, so crates.io rejects the manifest of every member at upload time.
No crate in the workspace could be published, so no v1 tag could exist, and nothing downstream could pin to one.
Phase 0 of #258 clears that, and adds the drift guard that keeps it cleared.

Be precise about what the issue's done-when actually proves, because I tested it and it proves nothing about the licence.
cargo publish --dry-run -p nexum-world was run with three values of license, each with --allow-dirty and after rm -rf target/package so neither the dirty-tree guard nor a stale artifact could confound the result.

Before, with the deprecated AGPL-3.0:

   Packaging nexum-world v0.1.0 (crates/nexum-world)
    Packaged 6 files, 102.2KiB (26.6KiB compressed)
   Verifying nexum-world v0.1.0 (crates/nexum-world)
   Compiling nexum-world v0.1.0 (target/package/nexum-world-0.1.0)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.28s
   Uploading nexum-world v0.1.0 (crates/nexum-world)
warning: aborting upload due to dry run

After, with AGPL-3.0-or-later:

   Packaging nexum-world v0.1.0 (crates/nexum-world)
    Packaged 6 files, 102.2KiB (26.6KiB compressed)
   Verifying nexum-world v0.1.0 (crates/nexum-world)
   Compiling nexum-world v0.1.0 (target/package/nexum-world-0.1.0)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.49s
   Uploading nexum-world v0.1.0 (crates/nexum-world)
warning: aborting upload due to dry run

Both exit 0, and so does a run with the outright invalid NOT-A-REAL-LICENCE.
Grepping the full output of all three for licen returns nothing.
Cargo 1.94.0 performs no local SPDX validation at all, so the dry run is not the gate on the licence and cannot distinguish the fix from the bug it replaced.
The justification for the licence change is the SPDX list; the guard against a regression is scripts/msrv-lint.sh, added here.
What the dry run does prove is that nothing else stops nexum-world from packaging: it packages, verifies and compiles the packaged crate, and stops only because it is a dry run.

Resolver: the issue text was wrong

Issue #258 said that edition 2024 implies resolver = "3" and the resolver key can therefore be removed.
That is wrong for this workspace, and removing the key would have been a silent behavioural change rather than a cleanup.
The root manifest is a virtual manifest, so it has no edition of its own, and the implication runs off the manifest's own edition, not off its members'.
With the key removed, cargo prints:

warning: virtual workspace defaulting to `resolver = "1"` despite one or more
workspace members being on edition 2024 which implies `resolver = "3"`
  |
  = note: to keep the current resolver, specify `workspace.resolver = "1"` in the workspace root's manifest
  = note: to use the edition 2024 resolver, specify `workspace.resolver = "3"` in the workspace root's manifest

Removing it would have demoted the workspace from resolver 2 to resolver 1.
That is not cosmetic: cargo tree -e features --workspace --all-features prints a materially different graph with the key absent, several hundred lines shorter, with features genuinely unified differently, for example hashbrown losing its foldhash edge and serde feature rows moving.

The feature graph did not change between resolver 2 and resolver 3.
The same cargo tree -e features --workspace --all-features run under resolver = "2" and under resolver = "3" is byte-identical, diff exits 0 with zero differing lines.
So stating 3 moves nothing that ships, and it puts the resolver 1 fallback permanently out of reach.
The key is now stated explicitly with a comment recording why it cannot be inferred.

Licence

license = "AGPL-3.0-or-later" on [workspace.package], inherited by all 20 members.
The -or-later half is the maintainer's explicit choice, not a default: bare AGPL-3.0 is deprecated precisely because it states no position on later versions of the licence, and this workspace takes the permissive position.
README.md now agrees.
The only remaining AGPL string outside the manifest is the prose reference inside LICENSE itself.

Panic profile

panic = "abort" on [profile.dev] and [profile.release] is pre-existing on main.
The decision here was to keep it rather than to add it, so this change adds only the comment explaining why, and carries zero behavioural change.
Both contested claims about it were verified.
The workspace has exactly eight #[should_panic] tests, and cargo forces unwind on the test and bench profiles regardless of the setting, so they are unaffected: run by name under the committed profiles, 8 tests run: 8 passed, and the full suite is 795 tests run: 795 passed, 0 skipped.
No cargo diagnostic about the panic setting appears anywhere in the full just ci log or the just build log.
The real cost is a second compile of the dependency graph for the test units, which the comment now states.

MSRV and the drift check

rust-version = "1.94.0" on [workspace.package], with rust-version.workspace = true on every member.
Three files state the toolchain version independently and none of them fails to compile when one drifts: rust-version in the root manifest is a promise to a consumer, the flake pin is what a developer actually runs, and the .github/actions/rust-setup/action.yml input is what a pull request is judged by.
scripts/msrv-lint.sh holds all three on one version.
The manifest and the flake pin must be byte-identical; the action names a two-component toolchain that rustup resolves to the newest patch, so it is compared on major.minor.
The script reads text and compiles nothing, so it runs in the toolchain-free content job and in just ci, and is available as just msrv.

Every failure mode was verified by mutating one file at a time and restoring, and the clean tree exits 0 in each case:

Mutation Result
Cargo.toml rust-version to 1.93.0 exit 1, manifest does not match the flake pin
flake.nix pin to 1.95.0 exit 1, two reasons: manifest mismatch and action mismatch
action toolchain to 1.92 exit 1, proves the major.minor comparison is live, not vacuous
a member dropping an inherit key exit 1, names the member and the key
action toolchain to bare stable exit 1, states the version is unreadable rather than dying under set -e
root licence to bare AGPL-3.0 exit 1, points at -only or -or-later

The check deliberately does not police the prose "Rust 1.94" strings in README.md and AGENTS.md, only the three machine-readable sources.

Review

The review raised three findings.
All three were confirmed empirically and fixed; none was rejected.

  1. scripts/msrv-lint.sh guarded rust-version inheritance per member but said nothing about license, so the identifier this change just corrected could silently return through a single member restating it. Reproduced: with license = "AGPL-3.0" on crates/nexum-sdk/Cargo.toml, the check printed msrv-lint: ok while cargo metadata resolved that one member to the deprecated value and the other nineteen to AGPL-3.0-or-later. Fixed by extending the existing member loop to for key in rust-version license, and by closing the root-manifest half of the same hole with a rule that rejects a bare GPL-family identifier by shape, ^(AGPL|LGPL|GPL)-[0-9]+\.[0-9]+$, rather than against this workspace's literal choice, so a future licence change needs no edit to the script.
  2. The issue's done-when is vacuous, because cargo performs no licence validation. Confirmed, with the three-value experiment quoted at the top of this description. The code remedy is the guard in finding 1; the documentation remedy is that both the commit message and this description say plainly that the dry run is not evidence for the licence change.
  3. flake.nix pointed a maintainer at .github/workflows/ci.yml for the CI toolchain version, which that file does not state; the value lives in .github/actions/rust-setup/action.yml. Pre-existing on main, but this change makes exactly that pairing load-bearing, so a maintainer bumping the MSRV would have been actively misdirected. The comment now names the right file and the script that holds the pair together.

One suggestion was declined, and it is worth naming.
scripts/msrv-lint.sh now covers the licence as well as the MSRV, so its name is no longer complete.
A rename would churn the CI workflow beyond the single step this change is allowed to touch, plus the justfile recipe, AGENTS.md and the commit body, for no behavioural gain, so it is left for a later change if wanted.

Deliberately absent, belongs to #145

No crate metadata beyond what already existed, no publish = false on the fixtures and examples, no CHANGELOG.md, and no release workflow.
.github/workflows/ci.yml receives the MSRV step and nothing else.

Verification

nix develop --command just ci exits 0.
It was run again after the commit so that content-lint.sh main..HEAD inspected a non-empty range and actually judged the new commit message.
Suite: 795 tests run, 795 passed, 0 skipped, doctests ok.
origin/main at fb20a73 is an ancestor of HEAD, nothing was rebased or rewritten, and the working tree is clean.

Closes #258

AI Assistance: Claude Opus 5 used for the implementation, review, and this description.

Set `license = "AGPL-3.0-or-later"` on `[workspace.package]`.
Bare `AGPL-3.0` is deprecated in the SPDX list.
Every member inherits the value, and README.md now agrees with it.

State `resolver = "3"` explicitly rather than remove the key.
Issue #258 said edition 2024 implies resolver 3 and the key is redundant.
That is wrong for this workspace: a virtual manifest has no edition of its
own, so an absent key falls back to resolver 1 no matter what the members
declare. With the key removed, cargo reports:

  warning: virtual workspace defaulting to `resolver = "1"` despite one or
  more workspace members being on edition 2024 which implies `resolver = "3"`

and `cargo tree -e features --workspace --all-features` then prints a
different feature graph, 4054 lines against 4247. The same command under
`resolver = "2"` and under `resolver = "3"` is byte-identical, so stating 3
changes nothing that ships and puts the resolver 1 fallback out of reach.

Keep `panic = "abort"` on both `[profile.dev]` and `[profile.release]` so a
debug run dies the way a release run dies. Cargo forces unwind on the test
and bench profiles, so the eight `#[should_panic]` tests are unaffected; all
eight pass and no cargo diagnostic about the setting appears. The cost is a
second compile of the dependency graph for the test units.

Add `rust-version = "1.94.0"` to `[workspace.package]` and the inherit key to
every member manifest. Three files state the toolchain version independently
and none of them fails to compile when one drifts, so `scripts/msrv-lint.sh`
compares the manifest against the flake pin and the rust-setup action input,
and fails when a member omits the inherit key. It reads text and compiles
nothing, so it runs in the toolchain-free content job and in `just ci`.

Hold the licence in the same check, because nothing else holds it. Cargo
performs no local SPDX validation: `cargo publish --dry-run -p nexum-world`
packages, verifies, compiles and reaches `warning: aborting upload due to dry
run` with exit 0 under `AGPL-3.0-or-later`, under the deprecated `AGPL-3.0`
and under `NOT-A-REAL-LICENCE` alike, and prints no licence diagnostic in any
of the three. The dry run is therefore not evidence for this change; the SPDX
list is. The check instead rejects a bare GPL-family identifier in the root
manifest and requires `license.workspace = true` on every member, so a licence
typo fails a pull request rather than an upload.

Closes #258

AI Assistance: Claude Opus 5 used for the implementation.
@mfw78
mfw78 force-pushed the chore/spdx-resolver-msrv branch from 8e315c0 to 84ad7c5 Compare August 17, 2026 04:44
@mfw78
mfw78 merged commit f3e2a1d into main Aug 17, 2026
5 checks passed
@mfw78
mfw78 deleted the chore/spdx-resolver-msrv branch August 17, 2026 04:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

chore: fix the SPDX identifier, the resolver, the panic profile and the MSRV

1 participant