Repository navigation
Fix crates.io publishing for the current workspace layout #125
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -119,10 +119,56 @@ jobs: | |||||||||||||||||||||||||||||||||||||||
| path: convex-excel-addin-v*.zip | ||||||||||||||||||||||||||||||||||||||||
| retention-days: 5 | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| publish-crates: | ||||||||||||||||||||||||||||||||||||||||
| name: Publish to crates.io | ||||||||||||||||||||||||||||||||||||||||
| runs-on: ubuntu-latest | ||||||||||||||||||||||||||||||||||||||||
| needs: validate | ||||||||||||||||||||||||||||||||||||||||
| steps: | ||||||||||||||||||||||||||||||||||||||||
| - uses: actions/checkout@v6 | ||||||||||||||||||||||||||||||||||||||||
| with: | ||||||||||||||||||||||||||||||||||||||||
| ref: ${{ github.event.inputs.tag || github.ref }} | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| - name: Install Rust toolchain | ||||||||||||||||||||||||||||||||||||||||
| uses: dtolnay/rust-toolchain@stable | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| - name: Cache cargo registry | ||||||||||||||||||||||||||||||||||||||||
| uses: Swatinem/rust-cache@v2 | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| # Publish the five library crates in dependency order. cargo publish | ||||||||||||||||||||||||||||||||||||||||
| # waits for each version to land in the index before returning, so the | ||||||||||||||||||||||||||||||||||||||||
| # next crate can resolve it. The remaining workspace crates are | ||||||||||||||||||||||||||||||||||||||||
| # publish = false and are skipped automatically. | ||||||||||||||||||||||||||||||||||||||||
| # | ||||||||||||||||||||||||||||||||||||||||
| # crates.io versions are immutable, so a crate already at this version is | ||||||||||||||||||||||||||||||||||||||||
| # skipped (HTTP 200 from the per-version API) rather than erroring. This | ||||||||||||||||||||||||||||||||||||||||
| # makes the job safe to re-run after a mid-list failure. | ||||||||||||||||||||||||||||||||||||||||
| - name: Publish library crates | ||||||||||||||||||||||||||||||||||||||||
| env: | ||||||||||||||||||||||||||||||||||||||||
| CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} | ||||||||||||||||||||||||||||||||||||||||
| VERSION: ${{ needs.validate.outputs.version }} | ||||||||||||||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||||||||||||||
| set -euo pipefail | ||||||||||||||||||||||||||||||||||||||||
| ua="convex-release (https://github.com/sujitn/convex)" | ||||||||||||||||||||||||||||||||||||||||
| for crate in convex-core convex-math convex-curves convex-bonds convex-analytics; do | ||||||||||||||||||||||||||||||||||||||||
| status=$(curl -s -o /dev/null -w '%{http_code}' \ | ||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Add retry and timeout options to this Prompt for AI agents
Suggested change
|
||||||||||||||||||||||||||||||||||||||||
| -H "User-Agent: $ua" \ | ||||||||||||||||||||||||||||||||||||||||
| "https://crates.io/api/v1/crates/$crate/$VERSION") | ||||||||||||||||||||||||||||||||||||||||
| if [ "$status" = "200" ]; then | ||||||||||||||||||||||||||||||||||||||||
| echo "✓ $crate@$VERSION already on crates.io — skipping" | ||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||
| elif [ "$status" != "404" ]; then | ||||||||||||||||||||||||||||||||||||||||
| echo "Unexpected HTTP $status checking $crate@$VERSION on crates.io" >&2 | ||||||||||||||||||||||||||||||||||||||||
| exit 1 | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+153
to
+161
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win Add retries and timeouts around the crates.io probe. A transient DNS/5xx/slow-response failure here aborts the whole release before publishing starts. Bound the call and give it a small retry budget. Suggested retry/timeout diff- status=$(curl -s -o /dev/null -w '%{http_code}' \
+ status=$(curl -s -o /dev/null -w '%{http_code}' \
+ --retry 5 --retry-all-errors --connect-timeout 10 --max-time 30 \
-H "User-Agent: $ua" \
"https://crates.io/api/v1/crates/$crate/$VERSION")📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||||||||||||||
| echo "::group::cargo publish -p $crate ($VERSION)" | ||||||||||||||||||||||||||||||||||||||||
| cargo publish -p "$crate" | ||||||||||||||||||||||||||||||||||||||||
| echo "::endgroup::" | ||||||||||||||||||||||||||||||||||||||||
| done | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| github-release: | ||||||||||||||||||||||||||||||||||||||||
| name: Create GitHub Release | ||||||||||||||||||||||||||||||||||||||||
| runs-on: ubuntu-latest | ||||||||||||||||||||||||||||||||||||||||
| needs: [validate, build-excel-addin] | ||||||||||||||||||||||||||||||||||||||||
| needs: [validate, build-excel-addin, publish-crates] | ||||||||||||||||||||||||||||||||||||||||
| permissions: | ||||||||||||||||||||||||||||||||||||||||
| contents: write | ||||||||||||||||||||||||||||||||||||||||
| steps: | ||||||||||||||||||||||||||||||||||||||||
|
|
@@ -145,8 +191,17 @@ jobs: | |||||||||||||||||||||||||||||||||||||||
| body: | | ||||||||||||||||||||||||||||||||||||||||
| ## Installation | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| Internal workspace — not published to crates.io. Clone the repo | ||||||||||||||||||||||||||||||||||||||||
| and depend on the crates via path. | ||||||||||||||||||||||||||||||||||||||||
| The core library crates are published to crates.io. Depend on the | ||||||||||||||||||||||||||||||||||||||||
| unified analytics crate (it pulls in core, math, curves and bonds): | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| ```toml | ||||||||||||||||||||||||||||||||||||||||
| convex-analytics = "${{ needs.validate.outputs.version }}" | ||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| Individually published crates: `convex-core`, `convex-math`, | ||||||||||||||||||||||||||||||||||||||||
| `convex-curves`, `convex-bonds`, `convex-analytics`. The `convex` | ||||||||||||||||||||||||||||||||||||||||
| umbrella plus the FFI / WASM / MCP / server crates are internal — | ||||||||||||||||||||||||||||||||||||||||
| consume them via a git or path dependency. | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| ### Excel Add-in | ||||||||||||||||||||||||||||||||||||||||
| Download the `convex-excel-addin` zip from assets below, extract, | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,69 +22,61 @@ Convex is a production-grade fixed income analytics library providing comprehens | |
| - **High Performance**: Microsecond-level pricing | ||
| - **Type Safety**: Leverage Rust's type system to prevent errors | ||
| - **WebAssembly**: Full browser support via wasm-pack ([Live Demo](https://convex-demo.pages.dev/) | [WASM Demo](https://sujitn.github.io/convex/)) | ||
| - **Language Bindings**: C FFI bindings available; Python, Java, C# (coming soon) | ||
| - **Language Bindings**: C FFI and Java (published to Maven Central) available; Python and C# planned | ||
|
|
||
| ## Quick Start | ||
|
|
||
| ### Installation | ||
|
|
||
| Convex is an internal workspace — it is not published to crates.io. Depend on the | ||
| crate(s) you need via a git dependency: | ||
| The core library crates are published to [crates.io](https://crates.io). Most | ||
| users only need `convex-analytics`, which re-exports the lower layers (core, | ||
| math, curves, bonds): | ||
|
|
||
| ```toml | ||
| [dependencies] | ||
| convex-bonds = { git = "https://github.com/sujitn/convex.git" } | ||
| convex-analytics = { git = "https://github.com/sujitn/convex.git" } | ||
| convex-curves = { git = "https://github.com/sujitn/convex.git" } | ||
| convex-analytics = "0.13" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: The dependency snippet tells users to add only Prompt for AI agents</file context> |
||
| ``` | ||
|
Comment on lines
+31
to
38
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win The dependency snippet does not match the imports in the examples.
Also applies to: 59-62, 88-92 🤖 Prompt for AI Agents |
||
|
|
||
| or clone the repo and use a path dependency. | ||
| The full set of published crates: `convex-core`, `convex-math`, | ||
| `convex-curves`, `convex-bonds`, `convex-analytics`. | ||
|
|
||
| ### Example: Pricing a US Treasury Bond | ||
| > **Note:** the `convex` umbrella crate is *not* on crates.io — that name | ||
| > belongs to an unrelated project ([convex.dev](https://convex.dev)). The | ||
| > umbrella facade, along with the FFI, WASM, MCP and pricing-server crates, | ||
| > ships in-repo only. Depend on them via git or a path dependency: | ||
| > | ||
| > ```toml | ||
| > convex = { git = "https://github.com/sujitn/convex.git" } | ||
| > ``` | ||
|
|
||
| > The two snippets below are mirrored by compile-checked examples — run them | ||
| > with `cargo run -p convex-analytics --example readme_quickstart` and | ||
| > `--example readme_zspread`. | ||
|
|
||
| ### Example: Build a Bond and Compute Yield | ||
|
|
||
| ```rust | ||
| use convex::prelude::*; | ||
| use convex_analytics::functions::yield_to_maturity; | ||
| use convex_bonds::instruments::FixedRateBond; | ||
| use convex_core::types::{Date, Frequency}; | ||
| use rust_decimal_macros::dec; | ||
|
|
||
| fn main() -> Result<(), Box<dyn std::error::Error>> { | ||
| // Create a US Treasury bond | ||
| let bond = Bond::builder() | ||
| .isin("US912828Z229") | ||
| .coupon_rate(dec!(2.5)) | ||
| .maturity(Date::from_ymd(2030, 5, 15)) | ||
| // A 5% semi-annual bond. Coupon is a decimal (0.05 == 5%); day count and | ||
| // calendar default to 30/360 US and SIFMA. | ||
| let bond = FixedRateBond::builder() | ||
| .cusip_unchecked("912828Z29") | ||
| .coupon_rate(dec!(0.05)) | ||
| .issue_date(Date::from_ymd(2020, 5, 15)?) | ||
| .maturity(Date::from_ymd(2030, 5, 15)?) | ||
| .frequency(Frequency::SemiAnnual) | ||
| .day_count(DayCount::ActAct) | ||
| .build()?; | ||
|
|
||
| // Build a yield curve from market data | ||
| let curve = YieldCurve::bootstrap() | ||
| .add_deposit(dec!(0.015), Period::Months(3)) | ||
| .add_deposit(dec!(0.018), Period::Months(6)) | ||
| .add_bond(dec!(98.50), Date::from_ymd(2027, 5, 15)) | ||
| .add_bond(dec!(97.25), Date::from_ymd(2032, 5, 15)) | ||
| .interpolation(Interpolation::CubicSpline) | ||
| .build()?; | ||
|
|
||
| // Price the bond | ||
| let settlement = Date::today(); | ||
| let price = bond.price(&curve, settlement)?; | ||
|
|
||
| println!("Clean Price: {:.4}", price.clean()); | ||
| println!("Dirty Price: {:.4}", price.dirty()); | ||
| println!("Accrued Interest: {:.4}", price.accrued()); | ||
| // Yield to maturity from a clean price of 98.50 (per 100 face). | ||
| let settlement = Date::from_ymd(2025, 5, 15)?; | ||
| let ytm = yield_to_maturity(&bond, settlement, dec!(98.50), Frequency::SemiAnnual)?; | ||
|
|
||
| // Calculate yield | ||
| let ytm = bond.yield_to_maturity(price.clean(), settlement)?; | ||
| println!("Yield to Maturity: {:.4}%", ytm.as_percentage()); | ||
|
|
||
| // Calculate risk metrics | ||
| let duration = bond.modified_duration(&curve, settlement)?; | ||
| let convexity = bond.convexity(&curve, settlement)?; | ||
| let dv01 = bond.dv01(&curve, settlement)?; | ||
|
|
||
| println!("Modified Duration: {:.4}", duration); | ||
| println!("Convexity: {:.4}", convexity); | ||
| println!("DV01: ${:.2}", dv01); | ||
| println!("Yield to Maturity: {:.4}%", ytm.yield_percent()); | ||
|
|
||
| Ok(()) | ||
| } | ||
|
|
@@ -93,28 +85,36 @@ fn main() -> Result<(), Box<dyn std::error::Error>> { | |
| ### Example: Z-Spread Calculation | ||
|
|
||
| ```rust | ||
| use convex::prelude::*; | ||
| use convex_analytics::spreads::z_spread; | ||
| use convex_bonds::instruments::FixedRateBond; | ||
| use convex_core::types::{Date, Frequency}; | ||
| use convex_curves::curves::DiscountCurveBuilder; | ||
| use rust_decimal_macros::dec; | ||
|
|
||
| fn main() -> Result<(), Box<dyn std::error::Error>> { | ||
| let bond = Bond::builder() | ||
| .isin("US459200KJ18") // IBM Corporate Bond | ||
| .coupon_rate(dec!(3.75)) | ||
| .maturity(Date::from_ymd(2028, 11, 15)) | ||
| // 3.75% semi-annual corporate bond. | ||
| let bond = FixedRateBond::builder() | ||
| .cusip_unchecked("459200KJ1") | ||
| .coupon_rate(dec!(0.0375)) | ||
| .issue_date(Date::from_ymd(2018, 11, 15)?) | ||
| .maturity(Date::from_ymd(2028, 11, 15)?) | ||
| .frequency(Frequency::SemiAnnual) | ||
| .day_count(DayCount::Thirty360) | ||
| .build()?; | ||
|
|
||
| // Government curve for discounting | ||
| let gov_curve = YieldCurve::from_market_data(/* ... */)?; | ||
|
|
||
| // Market price | ||
| let market_price = Price::new(dec!(102.50), Currency::USD); | ||
|
|
||
| // Calculate Z-spread | ||
| let z_spread = bond.z_spread(market_price, &gov_curve, Date::today())?; | ||
|
|
||
| println!("Z-Spread: {:.2} bps", z_spread.as_basis_points()); | ||
| // A flat 4% continuously-compounded discount curve to spread against. | ||
| let settlement = Date::from_ymd(2025, 1, 15)?; | ||
| let rate = 0.04_f64; | ||
| let curve = DiscountCurveBuilder::new(settlement) | ||
| .add_pillar(1.0, (-rate * 1.0).exp()) | ||
| .add_pillar(2.0, (-rate * 2.0).exp()) | ||
| .add_pillar(5.0, (-rate * 5.0).exp()) | ||
| .add_pillar(10.0, (-rate * 10.0).exp()) | ||
| .with_extrapolation() | ||
| .build()?; | ||
|
|
||
| // Z-spread that reprices the bond to a dirty price of 102.50. | ||
| let spread = z_spread(&bond, dec!(102.50), &curve, settlement)?; | ||
| println!("Z-Spread: {:.2} bps", spread.as_bps()); | ||
|
|
||
| Ok(()) | ||
| } | ||
|
|
@@ -182,17 +182,27 @@ spread DV01. | |
|
|
||
| ## Architecture | ||
|
|
||
| Convex is organized into several crates for modularity: | ||
| Convex is organized into focused crates. The five **library** crates are | ||
| published to crates.io; the rest are internal (the facade, language bindings, | ||
| and the pricing-engine/server stack) and ship in-repo only. | ||
|
|
||
| ``` | ||
| convex/ | ||
| ├── convex-core # Core types (Date, Price, Yield, etc.) | ||
| ├── convex-math # Mathematical utilities and solvers | ||
| ├── convex-curves # Yield curve construction and interpolation | ||
| ├── convex-bonds # Bond instruments and definitions | ||
| ├── convex-analytics # Unified analytics (yields, spreads, risk) | ||
| ├── convex-wasm # WebAssembly bindings | ||
| └── convex-ffi # Foreign Function Interface for language bindings | ||
| ├── convex-core # Core types (Date, Price, Yield, calendars, day counts) [published] | ||
| ├── convex-math # Solvers and interpolators (Brent, Newton, LM) [published] | ||
| ├── convex-curves # Yield/credit curves, bootstrapping, multi-curve [published] | ||
| ├── convex-bonds # Bond instruments (fixed, FRN, callable, zero, sinker) [published] | ||
| ├── convex-analytics # Unified analytics: pricing, yields, spreads, risk [published] | ||
| ├── convex # Single-import facade re-exporting the public API (internal) | ||
| ├── convex-portfolio # Portfolio and ETF analytics (internal) | ||
| ├── convex-ffi # C-ABI FFI for language bindings (Java, Excel) (internal) | ||
| ├── convex-wasm # WebAssembly bindings for the browser demo (internal) | ||
| ├── convex-mcp # MCP server for tool/agent integration (internal) | ||
| ├── convex-ports # Hexagonal port traits (market/reference data, storage) (internal) | ||
| ├── convex-engine # Reactive pricing engine with a calculation graph (internal) | ||
| ├── convex-ext-file # File-backed market/reference data adapter (internal) | ||
| ├── convex-ext-redb # redb embedded-storage adapter (internal) | ||
| └── convex-server # REST + WebSocket pricing server (deployed to Fly.io) (internal) | ||
| ``` | ||
|
Comment on lines
189
to
206
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Add a language to this fenced block. This fence triggers markdownlint MD040 as written. Suggested fix-```
+```text
convex/
├── convex-core # Core types (Date, Price, Yield, calendars, day counts) [published]
...
└── convex-server # REST + WebSocket pricing server (deployed to Fly.io) (internal)🧰 Tools🪛 markdownlint-cli2 (0.22.1)[warning] 189-189: Fenced code blocks should have a language specified (MD040, fenced-code-language) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||
|
|
||
| ## Performance | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| //! Mirrors the Quick Start snippet in the repository README so it stays | ||
| //! compile-checked. Run with: `cargo run -p convex-analytics --example readme_quickstart`. | ||
|
|
||
| use convex_analytics::functions::yield_to_maturity; | ||
| use convex_bonds::instruments::FixedRateBond; | ||
| use convex_core::types::{Date, Frequency}; | ||
| use rust_decimal_macros::dec; | ||
|
|
||
| fn main() -> Result<(), Box<dyn std::error::Error>> { | ||
| // A 5% semi-annual bond. Coupon is a decimal (0.05 == 5%); day count and | ||
| // calendar default to 30/360 US and SIFMA. | ||
| let bond = FixedRateBond::builder() | ||
| .cusip_unchecked("912828Z29") | ||
| .coupon_rate(dec!(0.05)) | ||
| .issue_date(Date::from_ymd(2020, 5, 15)?) | ||
| .maturity(Date::from_ymd(2030, 5, 15)?) | ||
| .frequency(Frequency::SemiAnnual) | ||
| .build()?; | ||
|
|
||
| // Yield to maturity from a clean price of 98.50 (per 100 face). | ||
| let settlement = Date::from_ymd(2025, 5, 15)?; | ||
| let ytm = yield_to_maturity(&bond, settlement, dec!(98.50), Frequency::SemiAnnual)?; | ||
|
|
||
| println!("Yield to Maturity: {:.4}%", ytm.yield_percent()); | ||
|
|
||
| Ok(()) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| //! Mirrors the Z-Spread snippet in the repository README so it stays | ||
| //! compile-checked. Run with: `cargo run -p convex-analytics --example readme_zspread`. | ||
|
|
||
| use convex_analytics::spreads::z_spread; | ||
| use convex_bonds::instruments::FixedRateBond; | ||
| use convex_core::types::{Date, Frequency}; | ||
| use convex_curves::curves::DiscountCurveBuilder; | ||
| use rust_decimal_macros::dec; | ||
|
|
||
| fn main() -> Result<(), Box<dyn std::error::Error>> { | ||
| // 3.75% semi-annual corporate bond. | ||
| let bond = FixedRateBond::builder() | ||
| .cusip_unchecked("459200KJ1") | ||
| .coupon_rate(dec!(0.0375)) | ||
| .issue_date(Date::from_ymd(2018, 11, 15)?) | ||
| .maturity(Date::from_ymd(2028, 11, 15)?) | ||
| .frequency(Frequency::SemiAnnual) | ||
| .build()?; | ||
|
|
||
| // A flat 4% continuously-compounded discount curve to spread against. | ||
| let settlement = Date::from_ymd(2025, 1, 15)?; | ||
| let rate = 0.04_f64; | ||
| let curve = DiscountCurveBuilder::new(settlement) | ||
| .add_pillar(1.0, (-rate * 1.0).exp()) | ||
| .add_pillar(2.0, (-rate * 2.0).exp()) | ||
| .add_pillar(5.0, (-rate * 5.0).exp()) | ||
| .add_pillar(10.0, (-rate * 10.0).exp()) | ||
| .with_extrapolation() | ||
| .build()?; | ||
|
|
||
| // Z-spread that reprices the bond to a dirty price of 102.50. | ||
| let spread = z_spread(&bond, dec!(102.50), &curve, settlement)?; | ||
| println!("Z-Spread: {:.2} bps", spread.as_bps()); | ||
|
|
||
| Ok(()) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Drop unnecessary GitHub token exposure from the publish job.
This job only needs repository read access, but checkout currently persists credentials and the job inherits default
GITHUB_TOKENpermissions before runningcargo publish. Tighten it tocontents: readand disable credential persistence so the crates.io token is the only credential available during publish verification.Suggested hardening diff
publish-crates: name: Publish to crates.io runs-on: ubuntu-latest needs: validate + permissions: + contents: read steps: - uses: actions/checkout@v6 with: ref: ${{ github.event.inputs.tag || github.ref }} + persist-credentials: false📝 Committable suggestion
🧰 Tools
🪛 zizmor (1.26.1)
[warning] 127-129: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[warning] 122-166: overly broad permissions (excessive-permissions): default permissions used due to no permissions: block
(excessive-permissions)
🤖 Prompt for AI Agents
Source: Linters/SAST tools