Skip to content

Merge pull request #1563 from TSavo/feat/determinism-species #2742

Merge pull request #1563 from TSavo/feat/determinism-species

Merge pull request #1563 from TSavo/feat/determinism-species #2742

Workflow file for this run

# ProvekIt: cross-language conformance gate
#
# Runs the Linux `make ci` gate on every push and PR, then adds macOS Swift
# and per-kit verifier jobs. Source of truth for "do the kits derive the
# catalog-pinned protocol CIDs today" is committed to the repo, not asserted.
#
# Languages exercised:
# Rust 1.x stable, Go 1.22, .NET 10 (preview), Node 22 + pnpm 10,
# Python 3.12, Java 21 (Temurin) + Maven, clang/clang++ via apt,
# C, C++, Zig, PHP, Ruby, and Swift in the macOS job.
#
# The Makefile is the local contract. CI installs toolchains, calls `make`
# targets for the Linux profile, then runs the extra platform/profile jobs that
# are not part of the default local aggregate.
name: CI
on:
push:
branches: [main]
pull_request:
# Cancel superseded runs on the same branch / PR.
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
actions: read
jobs:
conformance:
name: Cross-language conformance gate
# [self-hosted, Linux, X64] not bare `self-hosted`: otherwise the
# macOS runner registered for #305 will pick up this Linux-only job
# (apt-get) and fail.
runs-on: [self-hosted, Linux, X64]
# `make conformance` + `make test-all` run right up against ~30 min, leaving
# no room for teardown (the Rust-target cache save on push, or even the
# toolchain Post-steps on PRs) -> the job's last step gets clipped and the
# whole job is marked `cancelled`/fail despite both substantive steps
# passing. 45 (matching the macOS-swift conformance job) gives teardown
# headroom so a green run reports green.
timeout-minutes: 45
steps:
- name: Checkout
uses: actions/checkout@v6
# ---------------------------------------------------------------
# Toolchain setup (parallelizable from GH's perspective; ordered
# here for log readability).
# ---------------------------------------------------------------
- name: Set up Rust (stable)
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- name: Restore Rust target dirs
if: github.event_name == 'pull_request'
uses: actions/cache/restore@v5
with:
path: |
~/.cargo/registry
~/.cargo/git
implementations/rust/target
tools/cross-kit-conformance/target
tools/recompute-spec-cids/target
tools/foundation-keygen/target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
- name: Cache Rust target dirs
if: github.event_name != 'pull_request'
uses: actions/cache@v5
with:
path: |
~/.cargo/registry
~/.cargo/git
implementations/rust/target
tools/cross-kit-conformance/target
tools/recompute-spec-cids/target
tools/foundation-keygen/target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
- name: Set up Go 1.22
uses: actions/setup-go@v6
with:
go-version: '1.22'
cache: true
cache-dependency-path: |
implementations/go/provekit-ir-symbolic/go.sum
implementations/go/provekit-self-contracts/go.sum
implementations/go/provekit-lift-go-tests/go.sum
- name: Set up .NET 10 (preview)
uses: actions/setup-dotnet@v5
with:
# net10.0 is pre-stable; setup-dotnet@v4 needs an explicit quality
# tag plus the major version. The 'preview' channel resolves to
# the latest 10.0.x SDK from the official Microsoft feed.
dotnet-version: '10.0.x'
dotnet-quality: 'preview'
- name: Set up pnpm
uses: pnpm/action-setup@v6
with:
version: '10.33.0'
- name: Set up Node 22
uses: actions/setup-node@v6
with:
# NOT Node 25; @ipld/dag-cbor ESM-only + tsx CJS bridge fights
# on Node 25 (see the broken bin/provekit*.cjs launchers). The
# vitest path used by mint-ts is fine on 22.
node-version: '22'
cache: 'pnpm'
- name: Install workspace node_modules (provides tsx for TS lifter)
# The TypeScript contract lifter shells out to `pnpm exec tsx ...`
# which resolves via the workspace root's `node_modules/.bin/tsx`.
# Without this step, supply-chain-rails smoke (and any other test
# that exercises the TS lifter through `provekit package inspect`)
# fails with `ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL Command "tsx" not
# found`. Frozen lockfile to avoid drift in CI.
run: pnpm install --frozen-lockfile
- name: Set up Python 3.12
uses: actions/setup-python@v6
with:
python-version: '3.12'
cache: 'pip'
- name: Install python kit wheels (blake3, pynacl, cbor2)
# mint-python invokes the mint-python-self-contracts orchestrator
# which imports blake3 / pynacl / cbor2 at runtime. The kit ships
# these as deps in its pyproject.toml; an editable install picks
# them up via the wheels in PyPI. Without this step, mint-python
# fails with ModuleNotFoundError once it's wired into all-mint.
# Mirrors how `cache: maven` + setup-java provisions java's deps.
run: pip install -e implementations/python/provekit-lift-py-tests
- name: Install Supply Chain Rails native receipt tools
# The Supply Chain Rails exhibit intentionally invokes real
# conventional receipt verifiers before ProvekIt rejects the poisoned
# package on contract rails. Keep CI on that same path: install the
# native SLSA VSA and in-toto verifiers, then let `provekit package
# inspect` call them through the lifter.
run: |
python -m pip install --user uv
export PATH="${HOME}/.local/bin:${PATH}"
menagerie/supply-chain-rails/authenticated-betrayal/tools/install-native-receipt-tools.sh
"${HOME}/go/bin/slsa-verifier" version
"$(uv tool dir --bin)/in-toto-verify" --version
- name: Set up Java 21 + Maven
# build-java (added by java-agent E2E work) calls `mvn package`.
# pom.xml sets maven.compiler.source/target=17; Temurin 21 is
# LTS and forward-compatible with source level 17.
# cache: maven caches ~/.m2/repository so subsequent runs skip
# the multi-hundred-MB dependency download.
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: '21'
cache: maven
# ---------------------------------------------------------------
# System-level deps: PHP CLI + Composer + b3sum (PHP kit), openssl,
# nlohmann-json, libblake3 (Ruby FFI), libyaml (Ruby psych/bundler
# runtime). Run BEFORE setup-ruby so the bundler-cache step has
# libblake3 and libyaml already on the host when it resolves and
# builds native gems.
#
# cpp BLAKE3 is vendored at tools/blake3-vendored/; the Ruby kit's
# FFI binding to libblake3 needs the system package because no
# rubygem on Ruby 3.x exposes 64-byte XOF mode (per PR #226 notes).
# ---------------------------------------------------------------
- name: Install C++ system deps + z3 + cvc5 + libyaml
# z3 is the SMT solver invoked by the TS workflow producer's
# checkImplication / bridgeEnforcement / circularProof tests
# (`provekit prove` substrate). Without it on PATH, every
# solver invocation returns ENOENT, the producer maps that to
# `undecidable`, and ~10 tests fail with errors of the shape
# `expected 'undecidable' to be 'equivalent'`. The `prove`
# workflow in provekit.yml installed z3; this gate did not.
# Closes task #215 ("CI: TS workflow producer tests failing").
#
# cvc5 joins z3 in the default solver portfolio per
# `.provekit/config.toml` (closes #251). Ubuntu Noble apt ships
# cvc5 at `/usr/bin/cvc5` (verified via packages.ubuntu.com
# filelist).
#
# libyaml-dev is needed for Ruby's psych (YAML) extension;
# bundler-cache fails with `libyaml-0.so.2: cannot open shared
# object file` without it on Ubuntu 24.04 hosted runners.
run: |
sudo apt-get update -y
sudo apt-get install -y --no-install-recommends \
b3sum \
build-essential \
clang \
cmake \
composer \
cvc5 \
libclang-dev \
libsodium-dev \
libssl-dev \
libyaml-dev \
llvm-dev \
maven \
nlohmann-json3-dev \
php-cli \
libsqlite3-dev \
sqlite3 \
unzip \
z3
php -m | grep -q '^sodium$'
- name: Install Vampire (upstream GitHub release)
# Vampire joins the default solver portfolio per
# `.provekit/config.toml` (closes #252 Tier 1). The original
# plan in #252 assumed `apt-get install vampire` on Ubuntu
# Noble, but Vampire is NOT packaged in any Ubuntu suite
# (verified via packages.ubuntu.com search). The upstream
# release at github.com/vprover/vampire ships a static x86_64
# Linux binary in vampire-Linux-X64.zip. We pin to v5.0.1
# (current latest as of 2026-05).
run: |
cd /tmp
EXPECTED_SHA="6ff2f42ea7fb9753ee104efc3e623d5e39443190f7c82a63e1e1517bf9d2cde3"
curl -fsSL --retry 5 -o vampire.zip \
https://github.com/vprover/vampire/releases/download/v5.0.1/vampire-Linux-X64.zip
echo "$EXPECTED_SHA vampire.zip" | sha256sum -c -
unzip -qo vampire.zip
sudo install -m 0755 vampire /usr/local/bin/vampire
vampire --version | head -1
- name: Set up Ruby 3.3
# mint-ruby-self-contracts (PR #234, issue #209) ships a Ruby
# orchestrator that the rust CLI invokes via the lift-plugin-protocol
# RPC. Without ruby on PATH the orchestrator binary errors at exec
# and the contractSetCid pinned-CID test sees the empty-set sentinel.
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.3'
bundler-cache: true
working-directory: implementations/ruby
- name: Install Zig 0.16.0 (pinned upstream stable)
# Issue #283. mint-zig-self-contracts (PR #241, issue #213) ships
# an orchestrator that the rust CLI invokes via lift-plugin-protocol
# RPC. Without zig on PATH the orchestrator binary errors at exec
# and the contractSetCid pinned-CID test sees the empty-set sentinel.
#
# The kit's std.ArrayList unmanaged-by-default usage (PR #168/#171)
# requires zig 0.16's std API. Issue #283 recommended pinning a
# 0.16-dev nightly URL because 0.16 was unreleased at the time it
# was filed; 0.16.0 shipped 2026-04-14, so we pin the stable
# release URL instead -- same pinned-URL property, no nightly-
# regression risk. ziglang.org keeps every release indefinitely.
# Refresh this URL only when the kit's std API requirements change.
#
# Direct ziglang.org downloads are intentionally not bandwidth-
# guaranteed. setup-zig v2 uses the Zig community mirror list and
# verifies tarball signatures while keeping the compiler version pinned.
uses: mlugg/setup-zig@v2
with:
version: 0.16.0
# ---------------------------------------------------------------
# The gate.
# ---------------------------------------------------------------
- name: make help (sanity)
run: make help
- name: make conformance
# Catalog v1.6.2 hash equality, signed protocol verify, live mint
# attestations, self-contract tests, and cross-kit fixtures. Failure
# here means the protocol or a kit conformance surface drifted and
# the PR is not mergeable.
run: make conformance
- name: make test-all
# Per-language native test suites. test-rust runs the Rust
# workspace + the two tools crates; test-go iterates the three
# Go modules; test-ts runs vitest; test-csharp runs xUnit;
# test-python runs pytest after a local pip install.
run: make test-all
conformance-macos-swift:
# Step 5 of the 12/12 conformance epic (#277). The Linux conformance
# gate above intentionally excludes swift from `all-mint` because the
# swift kit is macOS-only (Package.swift `platforms: [.macOS(.v10_15)]`
# plus an OpenSSL libcrypto dep wired through Homebrew prefixes). This
# job exercises `make mint-swift` end-to-end on a macOS runner so the
# swift kit's pinned contractSetCid is verified on every PR alongside
# the Linux 11/12 set, advancing the gate to 12/12.
#
# Scope (kept lean to manage macOS runner minutes):
# - install rust (swift toolchain ships pre-installed on the
# hosted runner, OpenSSL is available under /opt/homebrew on
# Apple Silicon and Package.swift auto-detects the prefix).
# - run `make mint-swift` (loud failure gate: shells out to
# verify-self-contracts against the pinned swift.json envelope).
# - run only the swift-specific pinned-CID integration test
# (`swift_kit_pins_expected_contract_set_cid`), which is gated
# #[cfg_attr(not(target_os = "macos"), ignore)] in the rust
# suite and never executes on the Linux job.
#
# Out of scope: `make conformance` and `make test-all`. Those are
# the Linux job's contract; replicating them here doubles cost
# without adding signal. The cross-kit byte-equivalence claim is
# already protected by the Linux gate's pinned CIDs for the other
# 11 kits, and JCS+BLAKE3 is architecture-independent.
name: Cross-language conformance gate (macOS swift)
# Self-hosted Mac runner registered on the architect's machine
# (label `macOS`, X64). Closes #305: moves the swift jobs off
# GitHub-hosted macos-latest onto our own infra. Toolchains
# (swift, rust) are pre-installed on the runner host.
runs-on: [self-hosted, macOS, X64]
timeout-minutes: 45
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Set up Rust (stable)
# mint-swift depends on the release provekit CLI plus the
# self-contract verifier; it must not build the whole Rust workspace.
uses: dtolnay/rust-toolchain@stable
- name: Restore Rust target dirs
if: github.event_name == 'pull_request'
# Same cache shape as the Linux job; ${{ runner.os }} resolves to
# macOS here so the namespace is distinct (no collision with the
# Linux cargo cache). PRs restore only so post-job cache upload cannot
# turn successful verification into a timeout failure.
uses: actions/cache/restore@v5
with:
path: |
~/.cargo/registry
~/.cargo/git
implementations/rust/target
tools/foundation-keygen/target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
- name: Cache Rust target dirs
if: github.event_name != 'pull_request'
# Same cache shape as the Linux job; ${{ runner.os }} resolves to
# macOS here so the namespace is distinct (no collision with the
# Linux cargo cache). Cold-start without this is ~5-7 minutes of
# crate compilation; with the cache, sub-minute restore.
uses: actions/cache@v5
with:
path: |
~/.cargo/registry
~/.cargo/git
implementations/rust/target
tools/foundation-keygen/target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
# The macOS swift jobs were cold-compiling SwiftPM from scratch every
# run (~15 min) because NOTHING cached `.build` — only cargo was cached
# (wrong language). Restore the SwiftPM build dir always (incl. PRs) so
# source-only changes recompile incrementally off a warm `.build`; save
# only on push so a PR's post-job upload can't time the job out (same
# discipline as the cargo cache above).
- name: Restore SwiftPM build
uses: actions/cache/restore@v5
with:
path: implementations/swift/.build
key: ${{ runner.os }}-${{ runner.name }}-swiftpm-${{ hashFiles('implementations/swift/Package.resolved') }}
restore-keys: |
${{ runner.os }}-${{ runner.name }}-swiftpm-
- name: Save SwiftPM build
# Per-runner key (includes runner.name): a runner only ever restores a
# .build it built itself, so clang's ModuleCache .pcm files — which
# embed the absolute build path — never get restored onto a runner with
# a different _work path (the prove-swift cross-runner failure). Save on
# PRs too (not just push): main-push dispatches to one runner per event,
# so without PR-save the other runner would be permanently cold. With
# the per-runner key, only the first PR on each runner pays the upload;
# the rest cache-hit and skip.
uses: actions/cache@v5
with:
path: implementations/swift/.build
key: ${{ runner.os }}-${{ runner.name }}-swiftpm-${{ hashFiles('implementations/swift/Package.resolved') }}
- name: Verify swift toolchain
# macOS GitHub-hosted runners ship the swift toolchain
# pre-installed (Xcode command-line tools). No setup-swift action
# required. This step is for log readability so a future failure
# mode (Apple changing the runner image baseline) is loud.
run: |
swift --version
xcrun --find swift
- name: make help (sanity)
run: make help
- name: check macOS Swift Rust build scope
run: make check-macos-swift-rust-scope
- name: make mint-swift
# End-to-end: build the release provekit CLI + verifier + Swift slab,
# then dispatch mint --kit=swift to the swift orchestrator
# (mint-swift-self-contracts --rpc), and verify the resulting
# contractSetCid matches the pinned envelope at
# provenance/self-contracts-attest/swift.json. Failure here means the
# swift kit's emission has drifted relative to the rest of the
# protocol (or the swift slab itself has changed) and is a hard stop.
run: make mint-swift
- name: cargo test swift_kit_pins
# Redundant pin verification: the integration test asserts the
# contractSetCid string equals the pinned constant
# SWIFT_CONTRACT_SET_CID (blake3-512:cb743058...) directly,
# whereas `make mint-swift` checks against the JSON envelope.
# The test silently skips when mint fails (run_mint returns ok=false);
# the previous step is the loud gate, this is the in-rust-suite gate
# the integration tests assert by file.
run: cargo test -p provekit-cli --test mint_kit_integration -- swift_kit_pins
working-directory: implementations/rust
- name: swift catalog-pinned fixtures
# Runs the central Rust fixture harness in the macOS-only profile.
# This is the same conformance law as the Linux job, but restricted
# to Swift because the Swift kit is macOS-only.
run: cargo run --release --manifest-path tools/cross-kit-conformance/Cargo.toml -- --profile swift
# ---------------------------------------------------------------------------
# Per-kit conformance gate: provekit prove --kit=<kit>
#
# Each job spawns the kit's lifter via JSON-RPC and runs C1-C8 verifiers
# against the captured RPC messages.
#
# swift runs on macos-latest (Swift toolchain is macOS-only).
# All other kits run on self-hosted Linux.
# ---------------------------------------------------------------------------
prove-linux:
name: "prove-${{ matrix.kit }} (C1-C8 conformance)"
# Linux-only: exclude the macOS runner registered for #305.
runs-on: [self-hosted, Linux, X64]
timeout-minutes: 15
needs: conformance
strategy:
fail-fast: false
matrix:
kit:
- rust
- go
- cpp
- ts
- csharp
- clr-bytecode
- java
- python
- ruby
- zig
- c
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Set up Rust (stable)
uses: dtolnay/rust-toolchain@stable
- name: Restore Rust target dirs
if: github.event_name == 'pull_request'
uses: actions/cache/restore@v5
with:
path: |
~/.cargo/registry
~/.cargo/git
implementations/rust/target
tools/recompute-spec-cids/target
tools/foundation-keygen/target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
- name: Cache Rust target dirs
if: github.event_name != 'pull_request'
uses: actions/cache@v5
with:
path: |
~/.cargo/registry
~/.cargo/git
implementations/rust/target
tools/recompute-spec-cids/target
tools/foundation-keygen/target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
- name: Set up Go 1.22
if: matrix.kit == 'go'
uses: actions/setup-go@v6
with:
go-version: '1.22'
cache: true
cache-dependency-path: |
implementations/go/provekit-ir-symbolic/go.sum
implementations/go/provekit-self-contracts/go.sum
implementations/go/provekit-lift-go-tests/go.sum
- name: Set up .NET 10 (preview)
if: matrix.kit == 'csharp' || matrix.kit == 'clr-bytecode'
uses: actions/setup-dotnet@v5
with:
dotnet-version: '10.0.x'
dotnet-quality: 'preview'
- name: Set up pnpm
if: matrix.kit == 'ts'
uses: pnpm/action-setup@v6
with:
version: '10.33.0'
- name: Set up Node 22
if: matrix.kit == 'ts'
uses: actions/setup-node@v6
with:
node-version: '22'
cache: 'pnpm'
- name: Set up Python 3.12
if: matrix.kit == 'python'
uses: actions/setup-python@v6
with:
python-version: '3.12'
cache: 'pip'
- name: Install python kit deps
if: matrix.kit == 'python'
run: pip install -e implementations/python/provekit-lift-py-tests
- name: Install system deps (cpp)
if: matrix.kit == 'cpp'
run: |
sudo apt-get update -y
sudo apt-get install -y --no-install-recommends \
build-essential \
clang \
libssl-dev \
nlohmann-json3-dev
- name: Install system deps (c)
if: matrix.kit == 'c'
run: |
sudo apt-get update -y
sudo apt-get install -y --no-install-recommends \
build-essential \
libclang-dev \
libsodium-dev \
libssl-dev \
llvm-dev
- name: Set up Java (java)
if: matrix.kit == 'java'
uses: actions/setup-java@v5
with:
java-version: '21'
distribution: 'temurin'
cache: 'maven'
- name: Install Java deps (java)
if: matrix.kit == 'java'
run: |
sudo apt-get update -y
sudo apt-get install -y --no-install-recommends maven
- name: Install Ruby (ruby)
if: matrix.kit == 'ruby'
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.3'
bundler-cache: true
working-directory: implementations/ruby
- name: Install Zig (zig)
if: matrix.kit == 'zig'
uses: mlugg/setup-zig@v2
with:
version: 0.16.0
- name: Install PHP deps (php)
if: matrix.kit == 'php'
run: |
sudo apt-get update -y
sudo apt-get install -y --no-install-recommends \
b3sum \
php-cli
php -m | grep -q '^sodium$'
- name: "prove-${{ matrix.kit }}"
run: make prove-${{ matrix.kit }}
prove-swift:
name: "prove-swift (C1-C8 conformance, macOS)"
# Self-hosted Mac runner (issue #305).
runs-on: [self-hosted, macOS, X64]
# 35 (was 20): the per-runner SwiftPM cache key means the first PR on each
# runner cold-compiles swift-syntax (~15 min) on top of the rust CLI build
# before there's a warm .build to restore. Headroom so that first cold run
# reports green instead of clipping at the deadline.
timeout-minutes: 35
needs: conformance
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Set up Rust (stable)
uses: dtolnay/rust-toolchain@stable
- name: Restore Rust target dirs
if: github.event_name == 'pull_request'
uses: actions/cache/restore@v5
with:
path: |
~/.cargo/registry
~/.cargo/git
implementations/rust/target
tools/recompute-spec-cids/target
tools/foundation-keygen/target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
- name: Cache Rust target dirs
if: github.event_name != 'pull_request'
uses: actions/cache@v5
with:
path: |
~/.cargo/registry
~/.cargo/git
implementations/rust/target
tools/recompute-spec-cids/target
tools/foundation-keygen/target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
# Cache SwiftPM `.build` (restore always, save on push) — see the
# conformance-macos-swift job for the rationale; cold SwiftPM compile
# was the ~15-min tax on this job.
- name: Restore SwiftPM build
uses: actions/cache/restore@v5
with:
path: implementations/swift/.build
key: ${{ runner.os }}-${{ runner.name }}-swiftpm-${{ hashFiles('implementations/swift/Package.resolved') }}
restore-keys: |
${{ runner.os }}-${{ runner.name }}-swiftpm-
- name: Save SwiftPM build
# Per-runner key (includes runner.name): a runner only ever restores a
# .build it built itself, so clang's ModuleCache .pcm files — which
# embed the absolute build path — never get restored onto a runner with
# a different _work path (the prove-swift cross-runner failure). Save on
# PRs too (not just push): main-push dispatches to one runner per event,
# so without PR-save the other runner would be permanently cold. With
# the per-runner key, only the first PR on each runner pays the upload;
# the rest cache-hit and skip.
uses: actions/cache@v5
with:
path: implementations/swift/.build
key: ${{ runner.os }}-${{ runner.name }}-swiftpm-${{ hashFiles('implementations/swift/Package.resolved') }}
- name: prove-swift
run: make prove-swift
spec-cid-lint:
name: Spec CID literal lint
runs-on: [self-hosted, Linux, X64]
timeout-minutes: 2
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Lint blake3-512 CID literals under protocol/specs
run: |
set -euo pipefail
chmod +x tools/spec-cid-lint.sh
tools/spec-cid-lint.sh protocol/specs