diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..d986339 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,92 @@ +name: CI + +# This repository had no automated checks. Two releases' worth of a credential +# library - including `authority::verify_chain`, which is the part that decides +# whether a holder acquired authority they were never granted - merged on local +# runs alone. That is the gap this closes. + +on: + push: + branches: [main] + pull_request: + +env: + CARGO_TERM_COLOR: always + +jobs: + fmt: + name: Format + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - run: cargo fmt --all --check + + clippy: + name: Clippy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: dtolnay/rust-toolchain@stable + with: + components: clippy + - uses: Swatinem/rust-cache@v2 + # affinidi-tdk (a dev-dependency) reaches the OS keyring, which links + # dbus and pcsclite. Only the jobs that build dev-dependencies need these + # — `cargo check` and `cargo package` do not, and stay lean. + - name: System dependencies + run: sudo apt-get update && sudo apt-get install -y libpcsclite-dev libdbus-1-dev + - run: cargo clippy --all-targets --all-features -- -D warnings + + test: + name: Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + # affinidi-tdk (a dev-dependency) reaches the OS keyring, which links + # dbus and pcsclite. Only the jobs that build dev-dependencies need these + # — `cargo check` and `cargo package` do not, and stay lean. + - name: System dependencies + run: sudo apt-get update && sudo apt-get install -y libpcsclite-dev libdbus-1-dev + - run: cargo test --all-features + + # The signing backend is optional and `default = ["affinidi-signing"]`, so the + # default build never exercises the feature-off path. A consumer who disables + # default features is the one who finds out. + no-default-features: + name: No default features + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + # affinidi-tdk (a dev-dependency) reaches the OS keyring, which links + # dbus and pcsclite. Only the jobs that build dev-dependencies need these + # — `cargo check` and `cargo package` do not, and stay lean. + - name: System dependencies + run: sudo apt-get update && sudo apt-get install -y libpcsclite-dev libdbus-1-dev + - run: cargo test --no-default-features + + msrv: + name: Minimum Supported Rust Version + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: dtolnay/rust-toolchain@1.95.0 + - run: cargo check --all-features + + # A crate that cannot be packaged cannot be released, and finding that out at + # tag time means the tag is already wrong. `--locked` is deliberate: it is what + # the publish job uses, so a lockfile that has drifted fails here first. + package: + name: Package + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - run: cargo package --locked diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..5e591ae --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,78 @@ +name: Publish + +# Publish `dtg-credentials` to crates.io on a `vX.Y.Z` tag push. +# +# This repository had no release path at all: every version on crates.io got +# there by someone running `cargo publish` from a laptop. That is why 0.6.0 - +# the release carrying the VAC and VDC, which the data-rooms work in +# `verifiable-trust-infrastructure` depends on - sat merged and unpublished +# while the consumer could not compile against it. A tag is now the release. +# +# The trigger is the tag push rather than `release: published` on purpose: a +# Release created with the default GITHUB_TOKEN does not cascade-trigger other +# workflows, so a `release:` handler would silently never fire. +# +# Auth is crates.io Trusted Publishing (OIDC), so there is no long-lived token +# in this repository. ONE-TIME SETUP, on crates.io under the crate's Settings -> +# Trusted Publishing: add owner `OpenVTC`, repository `dtg-credentials`, +# workflow `publish.yml`. Until that exists the job fails at the auth step with +# a message naming exactly this, which is the right failure - better than a +# token sitting in the repo for the one day a year it is used. +# +# The run is idempotent: a tag re-pushed after a failed release job finds the +# crate already on crates.io at that version and skips rather than dying on +# "crate version already uploaded", which would otherwise force a version bump +# nothing needed. +on: + push: + tags: ["v*.*.*"] + workflow_dispatch: + +permissions: + id-token: write # OIDC token for crates.io Trusted Publishing + contents: read + +jobs: + publish: + name: Publish to crates.io + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + + - uses: dtolnay/rust-toolchain@stable + + # The tag says one version and Cargo.toml says another exactly once - the + # time someone tags before the bump lands - and the result is a release + # whose number means nothing. Cheap to check, unrecoverable to fix. + - name: Tag must match the crate version + if: startsWith(github.ref, 'refs/tags/v') + run: | + set -euo pipefail + tag="${GITHUB_REF_NAME#v}" + crate=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[0].version') + if [ "$tag" != "$crate" ]; then + echo "::error::tag v${tag} does not match Cargo.toml version ${crate}" + exit 1 + fi + echo "publishing dtg-credentials ${crate}" + + - name: Authenticate to crates.io + uses: rust-lang/crates-io-auth-action@v1 + id: auth + + - name: Publish + env: + CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }} + run: | + set -euo pipefail + version=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[0].version') + + # The sparse index is newline-delimited JSON, one object per version. + # A 404 (name never published) is a clean "not there". + if curl -sSf "https://index.crates.io/dt/g-/dtg-credentials" 2>/dev/null \ + | jq -se --arg v "$version" 'any(.[]; .vers == $v)' >/dev/null; then + echo "::notice::dtg-credentials ${version} is already on crates.io — nothing to do" + exit 0 + fi + + cargo publish --locked diff --git a/CHANGELOG.md b/CHANGELOG.md index bc6f68f..aa2e135 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- Both examples now declare `required-features = ["affinidi-signing"]`. A + `--no-default-features` build previously failed on them while the library + itself compiled fine, and the error named `.sign()` rather than the disabled + backend + ## [0.6.0] - 2026-09-02 Adds the two credentials that confer rather than assert: the **VAC** (verifiable authority diff --git a/Cargo.toml b/Cargo.toml index d08834a..ca25805 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,3 +36,15 @@ tokio = "1" # the sake of an example would be the wrong trade. chacha20poly1305 = "0.10" rand = "0.8" + +# Both examples sign, and signing lives behind `affinidi-signing`. Without this +# a `--no-default-features` build fails on the examples while the library it is +# meant to be testing compiles perfectly - the failure names `.sign()`, not the +# feature, so it reads as a missing method rather than a disabled backend. +[[example]] +name = "sign_and_verify" +required-features = ["affinidi-signing"] + +[[example]] +name = "data_room" +required-features = ["affinidi-signing"]