From eabf71ed65bd18f13d39daee99b56eaa94224b6c Mon Sep 17 00:00:00 2001 From: Glenn Gore Date: Wed, 2 Sep 2026 09:49:26 +0200 Subject: [PATCH 1/2] ci: add checks and a release path This repository had no .github directory. Two releases' worth of a credential library merged on local runs alone, and every version on crates.io got there by someone running cargo publish from a laptop. That is not hypothetical cost. 0.6.0 - the release carrying the VAC and VDC - has been merged and unreleased since the day it landed, because there was no path that turned a merge into a published crate. The data-rooms work in verifiable-trust-infrastructure needs authority::verify_chain and cannot compile against it, and the workspace there deliberately has no [patch.crates-io], so a git dependency is not an option either. A tag is now the release. ci.yml runs fmt, clippy (-D warnings), tests, the MSRV check, and cargo package --locked - the last because a crate that cannot be packaged cannot be released, and learning that at tag time means the tag is already wrong. publish.yml goes on a vX.Y.Z tag push rather than release: published, because a Release created with the default GITHUB_TOKEN does not cascade-trigger other workflows and that handler would silently never fire. Auth is crates.io Trusted Publishing, so no long-lived token lives here; the one-time crates.io setup is written into the workflow header, and until it exists the job fails at the auth step naming exactly that. It checks the tag against Cargo.toml first - the two disagree exactly once, when someone tags before the bump lands, and the result is a release number that means nothing - and skips a version already on crates.io so a re-pushed tag recovers instead of dying on 'already uploaded'. The no-default-features job found a real break on its first run: both examples call .sign(), which lives behind affinidi-signing, so a consumer disabling default features hit a missing-method error naming the method rather than the feature. They now declare required-features. The library and its tests were always fine - 53 of the 58 tests run without the backend. Signed-off-by: Glenn Gore --- .github/workflows/ci.yml | 77 ++++++++++++++++++++++++++++++++++ .github/workflows/publish.yml | 78 +++++++++++++++++++++++++++++++++++ CHANGELOG.md | 7 ++++ Cargo.toml | 12 ++++++ 4 files changed, 174 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/publish.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..18e8cf2 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,77 @@ +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 + - 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 + - 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 + - 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"] From a3c665b410b03b0e3768380fd9feea2a25d649dd Mon Sep 17 00:00:00 2001 From: Glenn Gore Date: Wed, 2 Sep 2026 09:54:45 +0200 Subject: [PATCH 2/2] ci: install the system libraries the dev-dependencies link The first CI run failed three jobs on 'failed to run custom build command for libdbus-sys'. affinidi-tdk is a dev-dependency and reaches the OS keyring, which links dbus and pcsclite; the GitHub runner image carries neither header. It bites exactly the three jobs that build dev-dependencies - clippy --all-targets, and the two test jobs. cargo check and cargo package do not build them and stay lean without the install step. Signed-off-by: Glenn Gore --- .github/workflows/ci.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 18e8cf2..d986339 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,6 +33,11 @@ jobs: 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: @@ -42,6 +47,11 @@ jobs: - 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 @@ -54,6 +64,11 @@ jobs: - 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: