fix(ci): close the two release-guard gaps v0.0.11 shipped through - #127
Merged
Conversation
Card t_103554a6 left two verification gaps open and t_07aea26c recorded a
guard that could only ever pass. Both are closed here, each proved red before
being trusted green.
1. cdylib/no_std publish break — NEW GUARD
`cargo publish --verify` builds with BARE DEFAULT FEATURES ON THE HOST. No CI
job builds that combination: CI builds --all-features (which structurally
cannot see this — it turns std on), std,all-algorithms, and
--no-default-features cross-compiled to thumbv7em-none-eabi (a different
target, no cdylib to link). lib-q-kem reached a tag green and killed the
release at tier 3, 35 immutable crates in.
scripts/ci-guard-cdylib-default-link.sh, two modes:
* static (ci.yml, every PR) — flags publishable cdylib crates whose default
features do not reach their own `std`. One cargo metadata call, no build.
* --build (cd.yml pre-release-validation) — really runs
`cargo build -p <crate> --lib` on every flagged-shape crate so the
KNOWN_LINKABLE allowlist cannot rot into a stale claim.
The static shape is necessary but NOT sufficient — lib-q-blind-pcs has it and
links fine — so allowlisted crates are re-proved empirically each release
rather than exempted.
OBSERVED, both directions on the real tree:
reverting lib-q-kem to `default = []` (the tree that shipped broken)
static guard -> exit 1, naming lib-q-kem
cargo build -p lib-q-kem --lib -> exit 101, "unwinding panics are not
supported without std"
restored -> guard exit 0, build exit 0
--build -> lib-q-blind-pcs / lib-q-hpke / lib-q-stark all exit 0
self-test -> 9 mutation cases, incl. that a dependency's `dep/std` must NOT
count as ours (the exact confusion behind the incident)
2. lib-q-hpke and lib-q-stark — the UNVERIFIED crates in t_103554a6
Both share the kem's static shape and could not be cleared at release time
(their dry-runs died earlier on deps not yet published at 0.0.11). Now that
0.0.11 is live, both cleared:
cargo publish -p lib-q-hpke --dry-run -> exit 0
cargo publish -p lib-q-stark --dry-run -> exit 0
Recorded in KNOWN_LINKABLE with dates, and re-proved by --build every release.
3. ci-guard-yanked-deps --published was vacuous (t_07aea26c)
It audited crates at [workspace.package] version. cd.yml runs it on a tag and
fails the release unless tag == workspace version, so the audited version was
by construction the one NOT yet on crates.io: every crate reported `missing`,
every crate hit the `!= "false" && continue` skip, and the guard passed
without examining a single dependency — every release, since it was written.
Now resolves each crate's latest LIVE version from the registry, which is the
set that can actually suffer post-publication yank drift, and adds a tripwire
that FAILS when a run audits zero crates (the shape the bug had, previously
indistinguishable from a clean registry). Success now reports the count.
The dead YANK_GUARD_WS_VERSION knob and get_workspace_version() are removed
rather than left as a lie in the header.
OBSERVED: against the real registry, exit 0, "audited 76 live crate(s)" — was
structurally 0 before. Self-test grew case L (the release-path shape: with the
workspace at an unpublished version the guard must still audit the live one)
and case M (auditing nothing must FAIL). Reinstating the old skip turns the
self-test red: "MUTATION-CHECK FAIL (L) ... AUDITED=0".
4. Audited the two remaining release-path guards — both are FINE
ci-guard-publish-order.sh and ci-guard-new-crates-and-npm.sh had been run but
never shown to fail. Mutation-tested both:
dropping lib-q-mayo from publish-crates-io-ordered.ps1 -> exit 1,
"would silently skip 1 crate(s) that cd.yml publishes: lib-q-mayo"
dropping its cd.yml matrix entry -> exit 1,
"workspace crates missing from cd.yml publish-rust jobs: lib-q-mayo"
both restored -> exit 0. Neither is vacuous.
5. npm/wasm ordering gap — does NOT exist (checked, not assumed)
No npm package declares a dependency on another (@lib-q/types appears only in
a README link and an error string), and publish-wasm-packages /
publish-npm-types are each transitively ordered after all 43 rust publish jobs.
Deliberately NO guard added: asserting a constraint that does not exist is how
the guards above became vacuous in the first place.
6. scripts/bump-workspace-version.py — promoted out of scratchpad
Generalised from the throwaway 0.0.11 bumper. Rewrites Cargo.toml pins (434
across 84 manifests) and, with --npm, npm/**/package.json — which drift
invisibly because CD overwrites the version at publish time (lib-q-types sat
at 0.0.2). --report-docs LISTS and never rewrites: at 0.0.11 two doc pins were
correct to leave at 0.0.10 (`lib-q-types <= 0.0.10` describes a break shipping
IN 0.0.11), and a blanket replace falsifies them.
scratchpad/cdylib-scan.py is NOT promoted — superseded by the guard's
classifier; keeping both would duplicate the logic.
Release runbook (docs/crates-io-publish.md) documents the failure class, both
guard modes, and the allowlist discipline.
🔒 Security Validation ReportGenerated: Thu Aug 13 06:16:32 UTC 2026 📊 Summary
✅ Overall Security Status: PASSEDAll critical security validations passed successfully. 🔍 DetailsThis report covers:
📋 Next Steps✅ Security validation passed. Code is ready for deployment. ✅ Security validation passed! This code meets all security requirements. |
🔍 Pull Request SummaryGenerated: Thu Aug 13 06:17:59 UTC 2026 📋 Validation Results
✅ Overall Status: PASSED🔒 Security Checklist
📝 Review NotesPlease review the security implications of this change carefully. ✅ Automated validation passed! This PR is ready for review. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
v0.0.11 published 35 crates and then died at tier 3. Two guards should have caught it; neither could.
1.
cargo publish --verify's build is a combination no CI job runsIt builds each crate with bare default features, on the host. CI builds
--all-features,std,all-algorithms, and--no-default-featurescross-compiled tothumbv7em-none-eabi. None ofthose is that combination, so a crate can be green everywhere and still fail to package:
lib-q-kemiscrate-type = ["cdylib", "rlib"]— a native cdylib needs a panic runtime — while itsown
defaultwas[]and it pinnedlib-q-core = { default-features = false }.--all-featuresstructurally cannot see this (it turns
stdon); thethumbv7embuild is a different target withno cdylib to link.
New
scripts/ci-guard-cdylib-default-link.sh, two modes:ci.yml/core-validation)cargo build -p <crate> --libon the release path (cd.yml/pre-release-validation)The static shape is necessary but not sufficient —
lib-q-blind-pcs,lib-q-hpkeandlib-q-starkhave it and link fine, so they sit in aKNOWN_LINKABLEallowlist that--buildre-proves every release rather than exempting.
Proven red before trusting it green: reverting
lib-q-kemtodefault = [](the tree that shippedbroken) gives guard exit 1 and
cargo build -p lib-q-kem --libexit 101; restored, both exit 0.9/9 self-test cases pass.
2.
ci-guard-yanked-deps.sh --publishedwas vacuous since it was writtenIt audited the workspace version, and CD runs it on a tag where that version is by construction
not yet published — so every crate hit the "no such version" skip and zero were ever audited.
It now resolves each crate's latest live version, and a zero-crate audit FAILS rather than passing
silently. Against the live registry: 76 crates audited, previously structurally 0.
Also
scripts/bump-workspace-version.py— promoted from a scratch one-off. 434 pins across 84manifests at 0.0.11;
--npmalso fixesnpm/**/package.json, which drifts invisibly because CDoverwrites it at publish time (
npm/lib-q-typesstill said0.0.2).--report-docslists andnever rewrites: at 0.0.11
lib-q-hqc/SECURITY.mdandlib-q-types/src/hqc.rswere correct tokeep saying
lib-q-types <= 0.0.10.docs/crates-io-publish.md— the failure class written up.Checked and deliberately left alone
ci-guard-publish-order.shandci-guard-new-crates-and-npm.share not vacuous — both go redunder mutation. The npm/wasm ordering gap does not exist: no npm package depends on another, and
both npm jobs sit transitively after all 43 Rust publish jobs. No guard added there — asserting a
constraint that does not exist is how the vacuous guard arose in the first place.
lib-q-hpkeandlib-q-stark, unverified at release time, both dry-run clean now that 0.0.11 islive (their dry-runs previously died earlier, on unpublished dependencies).