Skip to content

ci: use shared WrightKit Rust quality workflow #361

ci: use shared WrightKit Rust quality workflow

ci: use shared WrightKit Rust quality workflow #361

Workflow file for this run

name: CI
# ---------------------------------------------------------------------------
# Trigger rules
# ---------------------------------------------------------------------------
# Push to main: full convergence run (all capability checks).
# PR: capability checks are selected by changed paths. Broad/core changes
# (Cargo workspace, scripts, compatibility fixtures, CI itself) expand to the
# full check set; narrow path changes run only the relevant capability.
# Docs-only changes skip CI entirely.
on:
push:
branches: ["main"]
paths-ignore:
- "**/*.md"
- "docs/**"
pull_request:
paths-ignore:
- "**/*.md"
- "docs/**"
permissions:
contents: read
# ---------------------------------------------------------------------------
# Path filter: determines which capability jobs run on PRs.
# On push-to-main every filter is forced to true so all jobs run.
# ---------------------------------------------------------------------------
jobs:
paths:
name: Detect changed paths
runs-on: ubuntu-latest
outputs:
rust_core: ${{ steps.f.outputs.rust_core }}
opy: ${{ steps.f.outputs.opy }}
del: ${{ steps.f.outputs.del }}
lpp: ${{ steps.f.outputs.lpp }}
cli: ${{ steps.f.outputs.cli }}
opy_adapter: ${{ steps.f.outputs.opy_adapter }}
dist: ${{ steps.f.outputs.dist }}
steps:
- name: Check out repository
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Compute path filters
id: f
shell: bash
run: |
set -euo pipefail
# On push-to-main run everything unconditionally.
if [[ "${{ github.event_name }}" == "push" ]]; then
echo "rust_core=true" >> "$GITHUB_OUTPUT"
echo "opy=true" >> "$GITHUB_OUTPUT"
echo "del=true" >> "$GITHUB_OUTPUT"
echo "lpp=true" >> "$GITHUB_OUTPUT"
echo "cli=true" >> "$GITHUB_OUTPUT"
echo "opy_adapter=true" >> "$GITHUB_OUTPUT"
echo "dist=true" >> "$GITHUB_OUTPUT"
exit 0
fi
# Compute files changed in this PR against the merge-base.
base="${{ github.event.pull_request.base.sha }}"
head="${{ github.event.pull_request.head.sha }}"
changed="$(git diff --name-only "${base}...${head}")"
# Helper: returns "true" if any path matches the given grep pattern.
matches() { echo "$changed" | grep -qE "$1" && echo "true" || echo "false"; }
# Core Rust workspace changes expand all Rust-based checks.
broad="(^Cargo\.(toml|lock)$|^(src|crates|compatibility|scenarios)/|^scripts/|^\.github/workflows/)"
broad_match="$(matches "$broad")"
echo "rust_core=${broad_match}" >> "$GITHUB_OUTPUT"
echo "opy=${broad_match}" >> "$GITHUB_OUTPUT"
echo "del=${broad_match}" >> "$GITHUB_OUTPUT"
echo "lpp=${broad_match}" >> "$GITHUB_OUTPUT"
echo "cli=${broad_match}" >> "$GITHUB_OUTPUT"
# OPY adapter is TypeScript-only; only changes in adapter/ trigger it
# unless a broad change already forces everything.
opy_adapter_match="$(matches "(^adapter/|${broad})")"
echo "opy_adapter=${opy_adapter_match}" >> "$GITHUB_OUTPUT"
# Distribution validation additionally triggers on dist/ manifest changes.
dist_match="$(matches "(^dist/|${broad})")"
echo "dist=${dist_match}" >> "$GITHUB_OUTPUT"
# -------------------------------------------------------------------------
# [1] RUST QUALITY & TESTS (fundamental gate)
# All other Rust-based jobs depend on this.
# -------------------------------------------------------------------------
rust-quality:
name: Rust quality (${{ matrix.toolchain }})
needs: paths
if: needs.paths.outputs.rust_core == 'true'
strategy:
fail-fast: false
matrix:
toolchain:
- stable
- 1.85.0
uses: wrightkit/.github/.github/workflows/rust-quality.yml@9a91b5e32cc6d90b79b2b9b47a62b835ab94ec86
with:
toolchain: ${{ matrix.toolchain }}
cache-shared-key: linux-quality
all-features: true
# -------------------------------------------------------------------------
# [1a] WORKSHOP REAL-PROJECT INTEGRATION (Wright's released Workshop consumer)
# Runs the owner-pinned real-project inputs through the actual `wright`
# check/lint commands. The source snapshots and residual expectations remain
# owned by the released workshop-rs corpus.
# -------------------------------------------------------------------------
workshop-integration:
name: Workshop real-project integration
needs: [paths, rust-quality]
if: needs.paths.outputs.rust_core == 'true'
runs-on: ubuntu-latest
steps:
- name: Check out Wright
uses: actions/checkout@v7
- name: Check out released workshop-rs corpus
uses: actions/checkout@v7
with:
repository: wrightkit/workshop-rs
ref: 3d61bd4423924ca005d67f7c141a1866580f76fa # v0.1.9
path: workshop-rs-pinned
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: 1.85.0
- name: Restore Rust cache
uses: Swatinem/rust-cache@v2
with:
shared-key: linux-quality
cache-targets: true
cache-all-crates: false
cache-workspace-crates: false
cache-bin: false
save-if: false
cache-on-failure: false
- name: Run owner-contract cross-validation
env:
WRIGHTKIT_WORKSHOP_CORPUS_DIR: ${{ github.workspace }}/workshop-rs-pinned/crates/workshop-rs/tests/fixtures/real-projects
run: cargo test --locked -p wright-driver --test workshop_contract -- --ignored
- name: Run Wright check and lint on real projects
env:
WRIGHTKIT_WORKSHOP_CORPUS_DIR: ${{ github.workspace }}/workshop-rs-pinned/crates/workshop-rs/tests/fixtures/real-projects
run: cargo test --locked -p wright-cli --test workshop_real_projects -- --ignored --nocapture
# -------------------------------------------------------------------------
# [2] OPY INTEGRATION (Wright's consumer contract with OverPy/OPY)
# Covers:
# - Committed compatibility evidence integrity (no live oracle needed)
# - N-level gate: Wright compile vs recorded reference snapshots
# - Native-vs-reference differential suite (opy-rs ownership consumption)
# -------------------------------------------------------------------------
opy-integration:
name: OPY integration (N-level + differential)
needs: [paths, rust-quality]
if: needs.paths.outputs.opy == 'true'
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v7
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- name: Restore Rust cache
uses: Swatinem/rust-cache@v2
with:
shared-key: linux-quality
cache-targets: true
cache-all-crates: false
cache-workspace-crates: false
cache-bin: false
save-if: false
cache-on-failure: false
- name: Build wright (debug)
run: cargo build --locked -p wright-cli
- name: Validate committed compatibility evidence
run: python3 -m unittest discover -s compatibility/tests
- name: Run N-level gate (Wright compile vs recorded snapshots)
id: n_level
run: python3 scripts/v1-gates.py
- name: Run native-vs-reference differential suite
id: differential
if: always()
run: cargo test --locked -p wright-opy --test differential
- name: Upload OPY integration reports
if: always()
uses: actions/upload-artifact@v7
with:
name: opy-integration-reports
path: |
target/v1-gates-report.json
target/wright-differential-report.json
- name: Write step summary
if: always()
shell: bash
run: |
set -euo pipefail
{
echo "## OPY Integration"
echo ""
# N-level gate summary derived from the JSON report
if [[ -f target/v1-gates-report.json ]]; then
python3 - <<'PYEOF'
import json
r = json.load(open("target/v1-gates-report.json"))
s = r.get("summary", {})
passed = s.get("passed", 0)
total = s.get("total", 0)
state = "PASS" if passed == total else "FAIL"
icon = "✅" if passed == total else "❌"
print(f"### N-level gate: {icon} {state} ({passed}/{total} fixtures)")
print("")
failures = [(fid, f) for fid, f in r.get("fixtures", {}).items()
if f.get("status") != "pass"]
if failures:
print("**Failures:**")
for fid, f in failures:
print(f"- `{fid}`: {f.get('status', 'unknown')}")
else:
print("All fixtures passed.")
PYEOF
else
echo "### N-level gate: ⚠️ report not generated"
fi
echo ""
# Differential suite result
if [[ "${{ steps.differential.outcome }}" == "success" ]]; then
echo "### OPY differential suite: ✅ PASS"
else
echo "### OPY differential suite: ❌ FAIL (see run log for details)"
fi
if [[ -f target/wright-differential-report.json ]]; then
echo ""
echo "_Full machine-readable reports available as workflow artifacts._"
fi
} >> "$GITHUB_STEP_SUMMARY"
# -------------------------------------------------------------------------
# [3] DEL/OSTW INTEGRATION (Wright's consumer contract with OSTW/del-rs)
# Keeps Wright's forward-compilation differential test against del-rs output.
# Semantic fixes belong in del-rs; this job validates the Wright integration.
# -------------------------------------------------------------------------
del-integration:
name: DEL/OSTW integration (differential)
needs: [paths, rust-quality]
if: needs.paths.outputs.del == 'true'
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v7
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- name: Restore Rust cache
uses: Swatinem/rust-cache@v2
with:
shared-key: linux-quality
cache-targets: true
cache-all-crates: false
cache-workspace-crates: false
cache-bin: false
save-if: false
cache-on-failure: false
- name: Run OSTW forward-compilation differential suite
id: ostw_diff
run: cargo test --locked -p wright-ostw --test differential
- name: Upload DEL/OSTW differential report
if: always()
uses: actions/upload-artifact@v7
with:
name: del-ostw-differential-report
path: target/wright-ostw-differential-report.json
- name: Write step summary
if: always()
shell: bash
run: |
set -euo pipefail
{
echo "## DEL/OSTW Integration"
echo ""
if [[ "${{ steps.ostw_diff.outcome }}" == "success" ]]; then
echo "### OSTW differential suite: ✅ PASS"
else
echo "### OSTW differential suite: ❌ FAIL (see run log for details)"
fi
if [[ -f target/wright-ostw-differential-report.json ]]; then
echo ""
echo "_Full machine-readable report available as workflow artifact._"
fi
} >> "$GITHUB_STEP_SUMMARY"
# -------------------------------------------------------------------------
# [4] LPP INTEGRATION (Wright's Language Provider Protocol client contract)
# -------------------------------------------------------------------------
lpp-integration:
name: LPP client integration (#142, #139)
needs: [paths, rust-quality]
if: needs.paths.outputs.lpp == 'true'
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v7
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- name: Restore Rust cache
uses: Swatinem/rust-cache@v2
with:
shared-key: linux-quality
cache-targets: true
cache-all-crates: false
cache-workspace-crates: false
cache-bin: false
save-if: false
cache-on-failure: false
# The mock provider is built from the pinned language-provider-protocol
# commit the integration tests were validated against (merged LPP v1,
# PR wrightkit/language-provider-protocol#2 plus docs-only follow-ups).
- name: Check out language-provider-protocol (pinned)
uses: actions/checkout@v7
with:
repository: wrightkit/language-provider-protocol
ref: 416b293e26e6fb2d29061608a493a7aecd2ce14f
path: language-provider-protocol
- name: Build the pinned LPP mock provider
run: cargo build --locked -p lpp-mock-provider
working-directory: language-provider-protocol
# REQUIRED: these suites self-skip when LPP_MOCK_PROVIDER is absent,
# so the variable must be set here for them to run at all.
- name: Run LPP client integration tests (required)
env:
LPP_MOCK_PROVIDER: ${{ github.workspace }}/language-provider-protocol/target/debug/lpp-mock-provider
run: |
cargo test --locked -p wright-lpp --test mock_provider
cargo test --locked -p wright-driver --test lpp
cargo test --locked -p wright-driver --test provider_edit
# -------------------------------------------------------------------------
# [5] CLI BEHAVIOR (Wright CLI GitHub Actions renderer smoke test)
# Validates CLI output format in a real GitHub Actions environment.
# Related to #164 (user-facing GHA renderer); this job validates the
# repository CI integration, not the renderer semantics layer.
# -------------------------------------------------------------------------
cli-behavior:
name: Wright CLI behavior (GitHub Actions smoke)
needs: [paths, rust-quality]
if: needs.paths.outputs.cli == 'true'
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v7
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- name: Build wright CLI
run: cargo build --locked -p wright-cli
- name: Exercise auto GitHub Actions renderer
shell: bash
run: |
set -euo pipefail
fixture="$RUNNER_TEMP/wright-gha-smoke.ws"
stdout="$RUNNER_TEMP/wright-gha-smoke.stdout"
stderr="$RUNNER_TEMP/wright-gha-smoke.stderr"
cat > "$fixture" <<'EOF'
rule ("gha smoke") {
event {
Ongoing - Global;
}
actions {
Disable Inspector Recording;
}
}
EOF
target/debug/wright check "$fixture" > "$stdout" 2> "$stderr"
test ! -s "$stdout"
grep -Fq '::group::wright check' "$stderr"
grep -Fq '::endgroup::' "$stderr"
! grep -Fq 'title=Wright summary' "$stderr"
grep -Fq 'Wright `check`: **PASS** (exit 0)' "$GITHUB_STEP_SUMMARY"
# -------------------------------------------------------------------------
# [6] SCENARIO VALIDATION (end-to-end compile-time scenario regression)
# Proves Wright compile-time WIR/emission contracts for declared scenarios.
# Depends on rust-quality (fundamental gate).
# -------------------------------------------------------------------------
scenario-validation:
name: Scenario validation (end-to-end)
needs: [paths, rust-quality]
if: needs.paths.outputs.opy == 'true'
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v7
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- name: Restore Rust cache
uses: Swatinem/rust-cache@v2
with:
shared-key: linux-quality
cache-targets: true
cache-all-crates: false
cache-workspace-crates: false
cache-bin: false
save-if: false
cache-on-failure: false
- name: Build wright (debug)
run: cargo build --locked -p wright-cli
- name: Run compile-time scenarios
id: scenarios
run: python3 scripts/run-scenarios.py
- name: Upload scenario report
if: always()
uses: actions/upload-artifact@v7
with:
name: scenario-report
path: target/scenarios-report.json
- name: Write step summary
if: always()
shell: bash
run: |
set -euo pipefail
{
echo "## Scenario Validation"
echo ""
if [[ -f target/scenarios-report.json ]]; then
python3 - <<'PYEOF'
import json
r = json.load(open("target/scenarios-report.json"))
s = r.get("summary", {})
passed = s.get("passed", 0)
total = s.get("total", 0)
icon = "✅" if passed == total else "❌"
state = "PASS" if passed == total else "FAIL"
print(f"### Scenarios: {icon} {state} ({passed}/{total})")
print("")
failures = [(sid, sc) for sid, sc in r.get("scenarios", {}).items()
if not sc.get("passed")]
if failures:
print("**Failed scenarios:**")
for sid, sc in failures:
failed_checks = [c["check"] for c in sc.get("checks", []) if not c["passed"]]
print(f"- `{sid}`: {', '.join(failed_checks) or 'compile failed'}")
else:
print("All scenarios passed.")
PYEOF
else
echo "### Scenarios: ⚠️ report not generated"
fi
echo ""
echo "_Full machine-readable report available as workflow artifact._"
} >> "$GITHUB_STEP_SUMMARY"
# -------------------------------------------------------------------------
# [7] BENCHMARK (performance evidence)
# Non-blocking on PRs; records regression-detectable output as artifact.
# Depends on rust-quality so a fundamental failure skips benchmark time.
# -------------------------------------------------------------------------
benchmark:
name: Performance benchmarks
needs: [paths, rust-quality]
if: needs.paths.outputs.opy == 'true'
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v7
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- name: Restore Rust cache
uses: Swatinem/rust-cache@v2
with:
shared-key: linux-quality
cache-targets: true
cache-all-crates: false
cache-workspace-crates: false
cache-bin: false
save-if: false
cache-on-failure: false
- name: Build wright and bench (debug)
run: cargo build --locked -p wright-cli -p wright-bench
- name: Run benchmarks
id: bench
run: target/debug/wright-bench
- name: Upload benchmark report
if: always()
uses: actions/upload-artifact@v7
with:
name: benchmark-report
path: target/wright-bench-report.json
- name: Write step summary
if: always()
shell: bash
run: |
set -euo pipefail
{
echo "## Performance Benchmarks"
echo ""
if [[ "${{ steps.bench.outcome }}" == "success" ]]; then
echo "### Benchmarks: ✅ completed"
else
echo "### Benchmarks: ❌ FAIL"
fi
if [[ -f target/wright-bench-report.json ]]; then
python3 - <<'PYEOF'
import json
r = json.load(open("target/wright-bench-report.json"))
# Emit a compact table if the report has benchmark entries.
benches = r.get("benchmarks") or r.get("results") or []
if benches:
print("")
print("| Benchmark | Duration |")
print("| --- | --- |")
for b in benches[:20]: # cap at 20 rows
name = b.get("name") or b.get("id", "?")
dur = (b.get("duration_ms") or b.get("elapsed_ms") or b.get("ns"))
unit = "ms" if ("duration_ms" in b or "elapsed_ms" in b) else ("ns" if "ns" in b else "")
print(f"| {name} | {dur}{unit} |")
if len(benches) > 20:
print(f"_... and {len(benches) - 20} more (see artifact)_")
PYEOF
echo ""
echo "_Full machine-readable report available as workflow artifact._"
fi
} >> "$GITHUB_STEP_SUMMARY"
# -------------------------------------------------------------------------
# [8] OPY ADAPTER (OverPy-to-HIR TypeScript adapter)
# Independent of Rust quality; only triggered by adapter/ or broad changes.
# -------------------------------------------------------------------------
opy-adapter:
name: OverPy-to-HIR adapter
needs: paths
if: needs.paths.outputs.opy_adapter == 'true'
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v7
- name: Install pnpm
uses: pnpm/action-setup@v6
with:
version: 11.10.0
- name: Install Node.js
uses: actions/setup-node@v7
with:
node-version: 24
cache: pnpm
cache-dependency-path: adapter/pnpm-lock.yaml
- name: Install adapter dependencies
run: pnpm install --frozen-lockfile
working-directory: adapter
- name: Run adapter tests
run: pnpm test
working-directory: adapter
# -------------------------------------------------------------------------
# [9] DISTRIBUTION VALIDATION (cross-platform package/binary smoke)
# Gated on rust-quality + opy-integration + lpp-integration so a
# fundamental or integration failure prevents wasting cross-platform runner
# time on packages whose source has already failed.
# When dist-validation is skipped due to upstream failures, a summary step
# explains which gate caused the skip.
# -------------------------------------------------------------------------
dist-validation:
name: Distribution validation (${{ matrix.os }})
needs:
- paths
- rust-quality
- opy-integration
- lpp-integration
if: |
needs.paths.outputs.dist == 'true' &&
needs.rust-quality.result == 'success' &&
needs.opy-integration.result == 'success' &&
needs.lpp-integration.result == 'success'
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- macos-15
- windows-latest
steps:
- name: Check out repository
uses: actions/checkout@v7
- name: Set up Python
uses: actions/setup-python@v7
with:
python-version: "3.12"
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- name: Restore Rust cache
uses: Swatinem/rust-cache@v2
with:
shared-key: dist-validation-${{ runner.os }}
cache-targets: false
cache-all-crates: false
cache-workspace-crates: false
cache-bin: false
save-if: ${{ github.ref == 'refs/heads/main' }}
cache-on-failure: false
- name: Set up Node.js
uses: actions/setup-node@v7
with:
node-version: 22
- name: Build wright CLI and LSP
run: cargo build --locked -p wright-cli -p wright-lsp
- name: Validate package-manager metadata and install script
run: python scripts/verify-dist.py
# #183 moved the npm tarball determinism/executable-bit regressions
# (#123) to scripts/tests and declared them part of distribution
# validation; this step is that wiring.
- name: Run npm packaging unit regressions (#123)
run: python -m unittest discover -s scripts/tests
- name: Run installer functional tests
if: runner.os != 'Windows'
run: scripts/test-install.sh
- name: Run npm distribution smoke tests
run: python scripts/test-npm.py --binaries-dir target/debug