Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:

# Least privilege: CI only reads the checked-out repo (GHAS/CodeQL requirement).
permissions:
contents: read

env:
CARGO_TERM_COLOR: always

jobs:
test:
name: Test Suite
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
components: rustfmt, clippy

- name: Cache dependencies
uses: Swatinem/rust-cache@v2

- name: Install cargo-nextest
uses: taiki-e/install-action@nextest

- name: Check formatting
run: cargo fmt --all -- --check

- name: Check clippy
run: cargo clippy --all-targets --all-features -- -D warnings

# nextest runs each test as its own process and natively detects+reports
# flaky tests (failed-then-passed-on-retry) instead of silently hiding
# them. `--test-threads=1` mirrors the prior serial run (some integration
# tests assume serial execution).
- name: Run tests (nextest, flaky-aware)
run: cargo nextest run --all-features --retries 2 --test-threads=1

# nextest does NOT run doctests, so the headline `lib.rs` example is ungated
# by the step above. Run it explicitly so a doc example can never rot.
- name: Run doctests
run: cargo test --doc --all-features

- name: Check documentation
run: cargo doc --no-deps --all-features

coverage:
name: Coverage (>=80% lines, gated)
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
components: llvm-tools-preview

- name: Cache dependencies
uses: Swatinem/rust-cache@v2

- name: Install cargo-llvm-cov
uses: taiki-e/install-action@cargo-llvm-cov

- name: Install cargo-nextest
uses: taiki-e/install-action@nextest

# Measure line coverage and FAIL the build if it drops below 80%.
# `cargo llvm-cov nextest` runs the flaky-aware nextest runner under
# coverage instrumentation so coverage is still collected (running
# `cargo nextest run` and `cargo llvm-cov` as separate steps collects
# zero coverage and always fails the gate). `--test-threads 1` mirrors
# the serial run used by the test suite (some integration tests assume
# serial execution) — passed as a native nextest flag, NOT after `--`
# (nextest rejects `--test-threads=1` forwarded to the test binary). A
# summary report is printed; the gate is enforced by
# `--fail-under-lines 80`.
- name: Run coverage with 80% line gate
run: cargo llvm-cov nextest --all-features --workspace --fail-under-lines 80 --retries 2 --test-threads 1
44 changes: 44 additions & 0 deletions .github/workflows/commitlint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Enforce Conventional Commits on every PR to the default branch (and the PR title).
# Reads commitlint.config.mjs at the repo root (extends @commitlint/config-conventional).
# No local install needed — the action bundles commitlint. See CLAUDE.md §3.2.
name: Commitlint

on:
pull_request:
branches:
- main
types: [opened, edited, synchronize, reopened]

concurrency:
group: ${{ github.ref }}-${{ github.workflow }}
cancel-in-progress: true

permissions:
contents: read
pull-requests: read

jobs:
commitlint:
name: Lint commit messages
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Lint PR commits
uses: wagoid/commitlint-github-action@v6
with:
configFile: commitlint.config.mjs

- name: Lint PR title
if: github.event_name == 'pull_request'
env:
PR_TITLE: ${{ github.event.pull_request.title }}
run: |
set -euo pipefail
# Install both @commitlint/cli AND the extended shareable config so the
# `extends: ['@commitlint/config-conventional']` in commitlint.config.mjs resolves.
echo "$PR_TITLE" | npx --yes -p @commitlint/cli -p @commitlint/config-conventional \
commitlint --config commitlint.config.mjs
110 changes: 110 additions & 0 deletions .github/workflows/ensure-version-increment.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# Ensure the project version is incremented in every PR before it can merge to the default branch.
# Looks for BOTH package.json and Cargo.toml:
# - only package.json present -> its version must increase vs the base branch
# - only Cargo.toml present -> its version must increase vs the base branch
# - BOTH present -> both must increase AND equal each other on this branch
# - neither present -> nothing to enforce (passes)
# Version files added new on this branch (absent on base) pass. Modeled on Chia-Network/cadt.
name: Check Version Increment

on:
pull_request:
branches:
- main

# Least privilege: the version check only reads the checked-out branches (GHAS/CodeQL requirement).
permissions:
contents: read

concurrency:
group: ${{ github.ref }}-${{ github.workflow }}-${{ github.event_name }}
cancel-in-progress: true

jobs:
check-version:
name: Check version increment
runs-on: ubuntu-latest
steps:
- name: Checkout PR branch
uses: actions/checkout@v4
with:
path: branch-repo

- name: Checkout base branch
uses: actions/checkout@v4
with:
ref: main
path: base-repo

- name: Check version increment
shell: bash
run: |
set -euo pipefail

# Extract a version from package.json (.version) or Cargo.toml
# ([package].version or [workspace.package].version). Empty string if
# the file or the field is absent.
pkg_version() {
local f="$1/package.json"
[ -f "$f" ] || { echo ""; return; }
jq -r '.version // ""' "$f" 2>/dev/null || echo ""
}
# [package].version, or [workspace.package].version for workspace roots /
# members that inherit ({ version.workspace = true }). Empty if absent.
cargo_version() {
local f="$1/Cargo.toml"
[ -f "$f" ] || { echo ""; return; }
python3 -c 'import sys,tomllib; d=tomllib.load(open(sys.argv[1],"rb")); v=d.get("package",{}).get("version") or d.get("workspace",{}).get("package",{}).get("version") or ""; print(v if isinstance(v,str) else "")' "$f" 2>/dev/null || echo ""
}

# strictly-greater semver-ish compare using sort -V; true when $2 > $1
greater() { [ "$1" != "$2" ] && [ "$(printf '%s\n%s\n' "$1" "$2" | sort -V | tail -1)" = "$2" ]; }

B=base-repo
H=branch-repo
has_pkg=false; has_cargo=false
[ -f "$H/package.json" ] && has_pkg=true
[ -f "$H/Cargo.toml" ] && has_cargo=true

if [ "$has_pkg" = false ] && [ "$has_cargo" = false ]; then
echo "No package.json or Cargo.toml at repo root — no version gate to enforce."
exit 0
fi

fail=0

if [ "$has_pkg" = true ]; then
bp=$(pkg_version "$B"); hp=$(pkg_version "$H")
echo "package.json: base='$bp' head='$hp'"
if [ -n "$bp" ]; then
if ! greater "$bp" "$hp"; then
echo "::error::package.json version must be incremented ($bp -> $hp) before merging."
fail=1
fi
else
echo "package.json is new on this branch (no base version) — OK."
fi
fi

if [ "$has_cargo" = true ]; then
bc=$(cargo_version "$B"); hc=$(cargo_version "$H")
echo "Cargo.toml: base='$bc' head='$hc'"
if [ -n "$bc" ]; then
if ! greater "$bc" "$hc"; then
echo "::error::Cargo.toml version must be incremented ($bc -> $hc) before merging."
fail=1
fi
else
echo "Cargo.toml is new on this branch (no base version) — OK."
fi
fi

if [ "$has_pkg" = true ] && [ "$has_cargo" = true ]; then
hp=$(pkg_version "$H"); hc=$(cargo_version "$H")
if [ -n "$hp" ] && [ -n "$hc" ] && [ "$hp" != "$hc" ]; then
echo "::error::package.json ($hp) and Cargo.toml ($hc) versions must match each other."
fail=1
fi
fi

exit $fail
97 changes: 97 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
name: Publish to crates.io

# Tag-driven release: pushing a version tag `vX.Y.Z` runs the gates, publishes to crates.io, and
# cuts a GitHub Release. A normal push to `main` runs the gates only (see ci.yml) — it does NOT
# publish. Mirrors the sibling DIG crates so the same org secrets apply.
# dig-session's dependencies (dig-keystore, dig-identity, chia-bls, zeroize, thiserror) are all
# crates.io-published versions — no git/path deps. Release-first ordering: dig-keystore and
# dig-identity must be published (they are: v0.4.1 / v0.4.2) before dig-session publishes.

on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
version:
description: 'Version to publish (e.g., v0.4.0)'
required: true
type: string

permissions:
contents: read

env:
CARGO_TERM_COLOR: always

jobs:
# No fmt/clippy/test/coverage re-run here: the PR that produced this tagged commit already went
# through ci.yml's full gate set before it was allowed to merge. This workflow is build + package +
# publish only.
publish:
name: Publish to crates.io
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable

- name: Cache dependencies
uses: Swatinem/rust-cache@v2

- name: Verify the package builds
run: cargo build --release

- name: Verify the package can be packaged
run: cargo package --locked

- name: Check CARGO_REGISTRY_TOKEN is set
run: |
if [ -z "${{ secrets.CARGO_REGISTRY_TOKEN }}" ]; then
echo "CARGO_REGISTRY_TOKEN secret is not set in repository settings"
exit 1
fi

- name: Publish to crates.io
run: cargo publish --locked --token ${{ secrets.CARGO_REGISTRY_TOKEN }}
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}

create-release:
name: Create GitHub Release
runs-on: ubuntu-latest
needs: publish
if: startsWith(github.ref, 'refs/tags/v')
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Extract version from tag
id: extract_version
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> "$GITHUB_OUTPUT"

- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
name: "dig-session v${{ steps.extract_version.outputs.VERSION }}"
body: |
## dig-session v${{ steps.extract_version.outputs.VERSION }}

DIG session/keystore layer: unlock identity signing keys and inject signing primitives.
Composes dig-keystore (encrypted key storage) + dig-identity (canonical BLS identity
derivation) behind one curated, custody-safe facade.

### Installation
```toml
[dependencies]
dig-session = "${{ steps.extract_version.outputs.VERSION }}"
```
draft: false
prerelease: false
Loading
Loading