Skip to content

fix(ci): close the two release-guard gaps v0.0.11 shipped through - #127

Merged
Nexlab-One merged 1 commit into
mainfrom
fix/release-guard-gaps-0011
Aug 13, 2026
Merged

fix(ci): close the two release-guard gaps v0.0.11 shipped through#127
Nexlab-One merged 1 commit into
mainfrom
fix/release-guard-gaps-0011

Conversation

@Nexlab-One

Copy link
Copy Markdown
Contributor

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 runs

It builds each crate with bare default features, on the host. CI builds --all-features,
std,all-algorithms, and --no-default-features cross-compiled to thumbv7em-none-eabi. None of
those is that combination, so a crate can be green everywhere and still fail to package:

error: unwinding panics are not supported without std
error: could not compile `lib-q-core` (lib)

lib-q-kem is crate-type = ["cdylib", "rlib"] — a native cdylib needs a panic runtime — while its
own default was [] and it pinned lib-q-core = { default-features = false }. --all-features
structurally cannot see this (it turns std on); the thumbv7em build is a different target with
no cdylib to link.

New scripts/ci-guard-cdylib-default-link.sh, two modes:

  • static shape check on every PR (ci.yml / core-validation)
  • real cargo build -p <crate> --lib on the release path (cd.yml / pre-release-validation)

The static shape is necessary but not sufficientlib-q-blind-pcs, lib-q-hpke and
lib-q-stark have it and link fine, so they sit in a KNOWN_LINKABLE allowlist that --build
re-proves every release rather than exempting.

Proven red before trusting it green: reverting lib-q-kem to default = [] (the tree that shipped
broken) gives guard exit 1 and cargo build -p lib-q-kem --lib exit 101; restored, both exit 0.
9/9 self-test cases pass.

2. ci-guard-yanked-deps.sh --published was vacuous since it was written

It 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 84
    manifests at 0.0.11; --npm also fixes npm/**/package.json, which drifts invisibly because CD
    overwrites it at publish time (npm/lib-q-types still said 0.0.2). --report-docs lists and
    never rewrites: at 0.0.11 lib-q-hqc/SECURITY.md and lib-q-types/src/hqc.rs were correct to
    keep 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.sh and ci-guard-new-crates-and-npm.sh are not vacuous — both go red
under 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-hpke and lib-q-stark, unverified at release time, both dry-run clean now that 0.0.11 is
live (their dry-runs previously died earlier, on unpublished dependencies).

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.
@github-actions

Copy link
Copy Markdown

🔒 Security Validation Report

Generated: Thu Aug 13 06:16:32 UTC 2026

📊 Summary

  • NIST compliance: success
  • Cryptographic validation: success
  • Constant-time operations: success
  • Memory safety: success
  • Dependency security: success
  • WASM security: success

✅ Overall Security Status: PASSED

All critical security validations passed successfully.

🔍 Details

This report covers:

  • NIST post-quantum algorithm compliance
  • Constant-time operation verification
  • Memory safety and zeroization checks
  • Dependency vulnerability scanning
  • WASM build artifact validation

📋 Next Steps

✅ Security validation passed. Code is ready for deployment.


Security validation passed! This code meets all security requirements.

@github-actions

Copy link
Copy Markdown

🔍 Pull Request Summary

Generated: Thu Aug 13 06:17:59 UTC 2026

📋 Validation Results

  • Core Validation: success
  • Security Validation: success
  • Test Coverage: success
  • WASM Compatibility: success
  • Documentation: success

✅ Overall Status: PASSED

🔒 Security Checklist

  • No classical cryptographic algorithms
  • Only SHA-3 family hash functions
  • Constant-time operations
  • Proper memory zeroization
  • Input validation
  • Error handling

📝 Review Notes

Please review the security implications of this change carefully.
All cryptographic changes require security team review.


Automated validation passed! This PR is ready for review.

@Nexlab-One
Nexlab-One merged commit 0e9473a into main Aug 13, 2026
148 checks passed
@Nexlab-One
Nexlab-One deleted the fix/release-guard-gaps-0011 branch August 13, 2026 06:58
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.

1 participant