All checks were successful
energy meters on real hardware / RAPL and NVML on jetson-hub (pull_request) Successful in 48s
lockfiles / every Cargo.lock is current (pull_request) Successful in 43s
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 18s
tests in CI / every test file is run by a job or listed with its reason (pull_request) Successful in 10s
wai-quantum WebGPU member / does this change touch it (pull_request) Successful in 10s
workflow hygiene / every workflow supersedes its own queue (pull_request) Successful in 10s
standards workspaces build + test / jouleclaw-rs (-nodebug, jouleclaw/jouleclaw-rs, -p jouleclaw-energy --features rapl,nvml, jouleclaw-rs, linux-arm64, -C target-feature=+fp16) (pull_request) Successful in 3m40s
jouleclaw public surface must stay documented / Public-surface audit (jouleclaw-rs) (pull_request) Successful in 1m4s
spec citations resolve / Code cites no section its SPEC lacks (pull_request) Successful in 4s
wai-quantum WebGPU member / member · discrete GPU, Vulkan (jetson-hub) (pull_request) Successful in 10m26s
standards workspaces build + test / aac-rs (aac-conformance, aac/aac-rs, aac-rs, linux-arm64) (pull_request) Successful in 3m13s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 1m30s
standards workspaces build + test / joulecontract-rs (joulecontract/joulecontract-rs, joulecontract-rs, linux-arm64, true) (pull_request) Successful in 58s
standards workspaces build + test / map-rs (map-conformance, map/map-rs, map-rs, linux-arm64) (pull_request) Successful in 1m20s
standards workspaces build + test / mesh-rs (mesh/mesh-rs, -p mesh-engine-gguf -p mesh-openai --features mesh-engine-gguf/gguf,mesh-openai/server, mesh-rs, linux-arm64) (pull_request) Successful in 55s
standards workspaces build + test / openpay (openpay, openpay, linux-arm64) (pull_request) Successful in 2m53s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 5m32s
standards workspaces build + test / jouledesk-rs (jouledesk/jouledesk-rs, jouledesk-rs, macos) (pull_request) Successful in 52s
wai-quantum WebGPU member / member · Metal (macos) (pull_request) Successful in 2m11s
wai-quantum WebGPU member / member · browser WebGPU (macos) (pull_request) Successful in 1m28s
standards workspaces build + test / arl-rs (arl/arl-rs, arl-rs, linux) (pull_request) Successful in 1m15s
standards workspaces build + test / jcp-rs (jcp-conformance, jcp/jcp-rs, -p jcp-energy --features nvml, jcp-rs, linux) (pull_request) Successful in 1m50s
standards workspaces build + test / eoc-rs (eoc/eoc-rs, -p eoc-meter --features cuda, eoc-rs, linux) (pull_request) Successful in 2m9s
standards workspaces build + test / joule-ui-rs (joule-ui/joule-ui-rs, joule-ui-rs, linux) (pull_request) Successful in 1m10s
standards workspaces build + test / joulehook-rs (joulehook/joulehook-rs, joulehook-rs, linux) (pull_request) Successful in 1m12s
standards workspaces build + test / proof-rs (proof-conformance, proof/proof-rs, proof-rs, linux, true) (pull_request) Successful in 1m41s
standards workspaces build + test / sandbox-rs (sandbox-conformance, sandbox/sandbox-rs, sandbox-rs, linux) (pull_request) Successful in 2m26s
standards workspaces build + test / sense-rs (sense/sense-rs, sense-rs, linux) (pull_request) Successful in 1m8s
wai-quantum WebGPU member / shader checks · browser build · wasip2 inertness · MSRV (pull_request) Successful in 3m24s
standards workspaces build + test / smart-byte-rs (smart-byte/smart-byte-rs, smart-byte-rs, linux) (pull_request) Successful in 4m1s
wai-quantum WebGPU member / member · software Vulkan (linux) (pull_request) Successful in 2m5s
wai-quantum WebGPU member / member · DX12 and Vulkan (cbio-windows) (pull_request) Successful in 12m0s
EnergyLedger summed microjoules with no grade. Its `costs` defaults are
fixed figures, and the docs called one "measured". So a total built from
estimates looked the same as one built from readings, in the serializable
snapshot too. Now:
- `record` and `record_batch` take the deposit's `Provenance`. The ledger
keeps the weakest grade it was given (one `fetch_min`, still lock-free).
`provenance()`, `LedgerSnapshot::provenance` and the summary report that
grade; an empty ledger has none, and `reset` clears it. The order of the
grades is pinned to `span::weaker` by a test.
- The `costs` docs say what the figures are: defaults carried over from
another codebase's measurements, calibrated on no host that uses them, so
a deposit computed from one is `Estimator`. "measured" is gone.
- The orderings the defaults must keep are checked when the crate builds
(`const _: () = { assert!(..) }`), not in a test of constants that clippy
flagged as `assertions_on_constants`.
CI: the macOS energy job now runs clippy, with `-D warnings`, on the eight
crates whose meter code compiles only on macOS: jcp-energy, jcp-gateway,
proof-cascade, proof-solvers, eoc-meter, jouleclaw-energy (ioreport),
sandbox-meter and sandbox-proof. No Linux job lints that code, which is how
the lints fixed in #244 went unseen. All eight pass today.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
563 lines
30 KiB
YAML
563 lines
30 KiB
YAML
name: standards workspaces build + test
|
|
|
|
# Build gate for every Rust workspace in the repo.
|
|
#
|
|
# It started as three. Then a toolchain bump compiled the rest for the first
|
|
# time and found FIVE more that did not build — smart-byte (47k LOC), openpay
|
|
# (six separate breakages across five crates), eoc, mesh and joule-ui — all
|
|
# broken by the same handful of upstream migrations (hmac -> KeyInit, reqwest
|
|
# feature-gating, cipher 0.4 -> 0.5, a struct gaining fields), and all invisible
|
|
# because nothing ever compiled them. Partial coverage is how that happens, so
|
|
# the matrix now covers everything.
|
|
#
|
|
# The original case, still the clearest: smart-byte-rs had never been compiled by
|
|
# anything, and it did not build. A single wrong `rand` pin — 0.10 against code
|
|
# written for 0.8, whose crypto stack requires rand_core 0.6 — silently disabled
|
|
# the entire identity pillar, BBS+ included. `cargo check` would have caught it
|
|
# on day one. An unbuilt standard is not a standard, so this workflow is the
|
|
# floor under every claim the specs make.
|
|
#
|
|
# One workflow with a matrix rather than sixteen files: the bootstrap step is
|
|
# identical everywhere, and a shared dependency change should rebuild all of
|
|
# them anyway. `fail-fast: false` so one red workspace does not mask the others.
|
|
|
|
# One run per branch at a time. A push supersedes the run before it.
|
|
#
|
|
# There is one `macos` runner and it is the dev Mac, so every macOS job in this
|
|
# repo queues on it. With no concurrency group, a session pushing four versions
|
|
# of a branch queued four complete sets of runs and every one of them had to
|
|
# drain: on 2026-10-05 a PR's 21 contexts settled none in 70 minutes behind a
|
|
# backlog of 25 runs, and the queue grew faster than it cleared. Only the newest
|
|
# commit's verdict is wanted, so the older runs are cancelled.
|
|
#
|
|
# Verified on this forge (Forgejo 16.0.5) in `ci-test` before being added here:
|
|
# the key parses, and a newer push moves an older run to `cancelled` even while
|
|
# it is still queued, which is the case that matters.
|
|
#
|
|
# `cancel-in-progress: true` also applies to `main`. That is deliberate and
|
|
# safe here: main advances by merge commits whose tree contains the commits
|
|
# before them, so the newer run covers the older one's changes.
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
pull_request:
|
|
|
|
# Deliberately NO `paths:` filter.
|
|
#
|
|
# The first attempt used `**/*.rs` and `**/Cargo.toml`, and the workflow then
|
|
# silently did not fire — pushes touching exactly those files triggered other
|
|
# workflows but not this one, so `**`-prefixed globs evidently do not match here
|
|
# the way they do on GitHub. A build gate that quietly fails to run is worse than
|
|
# no gate at all: it reports nothing and everyone assumes green.
|
|
#
|
|
# This is the repo-wide floor. A push to main builds every workspace. A pull request
|
|
# builds the workspaces its own diff touches (the `plan` job, scripts/
|
|
# standards-touched.py): a workspace's own area, the areas of everything it depends on
|
|
# by path, any file outside them its sources name, and everything every leg shares (this
|
|
# file, the toolchain pin, the cargo configuration). Building all 17 for every pull
|
|
# request filled the one x86_64 runner's queue with builds no change could affect. The
|
|
# plan reads the pull request's diff, not the push delta, so a merge of main into a
|
|
# branch does not rebuild everything, and anything it cannot determine builds all.
|
|
|
|
jobs:
|
|
# Which workspaces this change touches. The list of workspaces is here, one
|
|
# `- { ... }` line each, read by scripts/standards-touched.py; the build job's
|
|
# matrix is the entries it selects.
|
|
plan:
|
|
name: which workspaces this change touches
|
|
runs-on: linux-arm64
|
|
outputs:
|
|
include: ${{ steps.plan.outputs.include }}
|
|
any: ${{ steps.plan.outputs.any }}
|
|
energy: ${{ steps.plan.outputs.energy }}
|
|
env:
|
|
WORKSPACES: |
|
|
- { name: jcp-rs, dir: jcp/jcp-rs, runner: linux, conformance: jcp-conformance, feature_tests: "-p jcp-energy --features nvml" }
|
|
- { name: sandbox-rs, dir: sandbox/sandbox-rs, runner: linux, conformance: sandbox-conformance }
|
|
- { name: smart-byte-rs, dir: smart-byte/smart-byte-rs, runner: linux }
|
|
- { name: openpay, dir: openpay, runner: linux-arm64 }
|
|
- { name: joule-code-rs, dir: joule-code/joule-code-rs, runner: linux-arm64, z3: true }
|
|
- { name: map-rs, dir: map/map-rs, runner: linux-arm64, conformance: map-conformance }
|
|
- { name: aac-rs, dir: aac/aac-rs, runner: linux-arm64, conformance: aac-conformance }
|
|
- { name: joulecontract-rs, dir: joulecontract/joulecontract-rs, runner: linux-arm64, z3: true }
|
|
# Runner placement is by what a workspace actually needs, not by
|
|
# habit. The dev Mac hosts BOTH the `macos` runner and the
|
|
# `linux-arm64` colima VM, so it served 13 of this repo's 21
|
|
# contexts while jetson-hub (`linux`) sat idle — which is why a PR's
|
|
# macOS contexts could wait indefinitely while its `linux` ones
|
|
# returned in minutes.
|
|
#
|
|
# `arl-rs`, `sense-rs`, `joulehook-rs` and `joule-ui-rs` moved to
|
|
# `linux` on that basis: each has zero `target_os = "macos"` cfgs,
|
|
# zero Apple dependencies, and zero Apple crates in its Cargo.lock,
|
|
# so running them on macOS asserted nothing the repo relies on.
|
|
# `jouledesk-rs` stays, because it does need it — `libwebrtc` and 19
|
|
# Apple crates in its lockfile. `linux-arm64` placements stay too:
|
|
# arm64 is the deployment target, and `jouleclaw-rs` needs
|
|
# `+fp16`.
|
|
- { name: jouledesk-rs, dir: jouledesk/jouledesk-rs, runner: macos }
|
|
- { name: arl-rs, dir: arl/arl-rs, runner: linux }
|
|
- { name: sense-rs, dir: sense/sense-rs, runner: linux }
|
|
- { name: proof-rs, dir: proof/proof-rs, runner: linux, z3: true, conformance: proof-conformance }
|
|
- { name: joulehook-rs, dir: joulehook/joulehook-rs, runner: linux }
|
|
- { name: eoc-rs, dir: eoc/eoc-rs, runner: linux, feature_tests: "-p eoc-meter --features cuda" }
|
|
# mesh-openai's `serve_example` test (the reference server seals what
|
|
# its meter read, never a fixture's figure) has required-features
|
|
# `server`, so `cargo test --workspace` builds none of it; naming the
|
|
# package here is what runs it. Selecting mesh-engine-gguf alone
|
|
# compiles mesh-openai with `server` but runs none of its tests.
|
|
- { name: mesh-rs, dir: mesh/mesh-rs, runner: linux-arm64, feature_tests: "-p mesh-engine-gguf -p mesh-openai --features mesh-engine-gguf/gguf,mesh-openai/server" }
|
|
- { name: joule-ui-rs, dir: joule-ui/joule-ui-rs, runner: linux }
|
|
# jouleclaw-rs is the one entry whose runner IS a correctness choice.
|
|
# It reaches candle → ug → gemm, which uses inline aarch64 assembly
|
|
# (`fmla {0:v}.8h, ...`) requiring the ARMv8.2 FP16 extension.
|
|
# `aarch64-apple-darwin` carries target_feature="fp16" in its baseline
|
|
# because Apple Silicon guarantees it; `aarch64-unknown-linux-gnu`
|
|
# baselines at ARMv8.0-A and does not, so the same asm fails to
|
|
# ASSEMBLE — a codegen error `cargo check` never reaches. Verified on a
|
|
# native arm64 Linux container (rustc 1.98.0): without the flag, 12
|
|
# errors; with it, 4457 tests pass.
|
|
#
|
|
# CAPACITY: this is by far the largest entry — its target tree is
|
|
# ~16 GB, against a 120 GB volume on tx-dev-linux-arm64 that already
|
|
# holds ~74 GB of other workspaces' caches. If linux-arm64 jobs start
|
|
# failing for no apparent reason, check `df -h /mnt/lima-colima-txarm`
|
|
# on the txarm VM before suspecting the code: a full cache volume
|
|
# fails whatever happens to be building, not whatever changed.
|
|
- { name: jouleclaw-rs, dir: jouleclaw/jouleclaw-rs, runner: linux-arm64, rustflags: "-C target-feature=+fp16", cache_suffix: "-nodebug", feature_tests: "-p jouleclaw-energy --features rapl,nvml" }
|
|
steps:
|
|
- uses: actions/checkout@v5
|
|
with:
|
|
fetch-depth: 0
|
|
- name: The decision fails closed (self-test)
|
|
run: bash scripts/standards-touched-selftest.sh
|
|
- id: plan
|
|
name: Which workspaces this change touches
|
|
run: |
|
|
set -euo pipefail
|
|
python3 scripts/standards-touched.py --github-output \
|
|
"${{ github.event.pull_request.base.sha }}" \
|
|
"${{ github.event.pull_request.head.sha }}"
|
|
|
|
build:
|
|
name: ${{ matrix.name }}
|
|
needs: plan
|
|
if: needs.plan.outputs.any == 'true'
|
|
# Spread across the fleet's runners rather than pinning every job to `linux`.
|
|
# For most of these the runner is a capacity choice, not a correctness one —
|
|
# and 17 jobs on a single runner queue behind whatever else is using it.
|
|
# `jouleclaw-rs` is the exception: see its matrix entry.
|
|
runs-on: ${{ matrix.runner }}
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include: ${{ fromJSON(needs.plan.outputs.include) }}
|
|
steps:
|
|
- uses: actions/checkout@v5
|
|
|
|
# Three workspaces shell out to `z3`, and the runner image has no solver.
|
|
#
|
|
# This step used to be load-bearing for correctness: without a solver the
|
|
# ladder escalated past its real rungs into a model STAND-IN that reported
|
|
# Proved, so every goal on a solverless machine came back proved. That is
|
|
# now fixed at the source — the real ladders hold decision procedures only
|
|
# and an absent rung yields Unproved (proof-solvers `ladder_is_sound` and
|
|
# `ladder_never_fabricates_a_proof_when_a_rung_is_absent`).
|
|
#
|
|
# So the suites pass with or without z3, and this step is kept purely for
|
|
# COVERAGE: with a solver present the real T1 rung actually runs, and the
|
|
# SMT paths are exercised rather than skipped. The absent-rung semantics
|
|
# stay covered either way — that regression test uses a goal z3 cannot
|
|
# parse, so it asserts the same thing on a machine that has one.
|
|
#
|
|
# joule-code-remediate's formal gate is the third. Its tests assert in both
|
|
# branches, so they pass without a solver, and JOULE_FIX_EXPECT_Z3=1 below
|
|
# makes a missing z3 fail them where this step installed one. Otherwise CI
|
|
# would only ever run the no-solver branch.
|
|
#
|
|
# The z3 jobs are pinned to Linux runners deliberately: the macOS runner is
|
|
# a host executor with no apt. `sudo` may or may not exist depending on
|
|
# whether the container runs as root, so fall back to a bare invocation.
|
|
- name: Install z3
|
|
if: matrix.z3
|
|
shell: bash
|
|
run: |
|
|
SUDO=""; [ "$(id -u)" -ne 0 ] && SUDO="sudo"
|
|
$SUDO apt-get update -qq
|
|
$SUDO apt-get install -y -qq z3
|
|
z3 --version
|
|
|
|
# The docker runner image ships no Rust at all, so rustup is bootstrapped
|
|
# here. dtolnay/rust-toolchain cannot be used: it shells out to `rustup`,
|
|
# which the image lacks. Harmless where cargo already exists.
|
|
- name: Install Rust (workspace pin)
|
|
shell: bash
|
|
run: |
|
|
export PATH="$HOME/.cargo/bin:$PATH"
|
|
command -v rustup >/dev/null 2>&1 || \
|
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal
|
|
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
|
|
|
|
# jouleclaw-rs is the CAPACITY entry above. Linux embeds full DWARF in every
|
|
# test binary, so with default debuginfo its aarch64 test target outgrows
|
|
# the ~15 GB left on the shared txarm volume and the link fails with
|
|
# "No space left on device" (reproduced with the same toolchain, flags and
|
|
# image). Without debuginfo the same target is ~6 GB and the whole suite
|
|
# passes. Panic messages keep their file:line: that comes from the panic
|
|
# location, not from DWARF.
|
|
#
|
|
# Set through $GITHUB_ENV rather than a matrix field on purpose: a matrix
|
|
# field would hand every other job CARGO_PROFILE_DEV_DEBUG="", which cargo
|
|
# can reject. `cache_suffix` gives this job a fresh cache namespace so it
|
|
# cannot restore a full-debuginfo target and hold both builds at once;
|
|
# for every other job the suffix is empty and its key string is unchanged.
|
|
- name: Trim debuginfo (arm64 jouleclaw)
|
|
if: matrix.name == 'jouleclaw-rs'
|
|
shell: bash
|
|
run: echo "CARGO_PROFILE_DEV_DEBUG=0" >> "$GITHUB_ENV"
|
|
|
|
# aac-rs builds its page sets in its tests (tools/pagesets: build.py,
|
|
# phrases/build.py) and checks them against the committed catalogue
|
|
# (catalog.py --check, below). Those need Python 3.11+ with PyYAML; the
|
|
# runner image has Python 3.10 and no PyYAML, and with neither the
|
|
# tests would only report that they could not build anything. uv puts
|
|
# a Python 3.12 and PyYAML in a venv of the job's own, on Linux or
|
|
# macOS; the tests use it through AAC_PYTHON and fail, not skip, when
|
|
# CI is set and there is none. Verified in catthehacker/ubuntu:act-22.04
|
|
# on arm64: Python 3.12.15, PyYAML 6.0.3.
|
|
- name: Python 3.11+ and PyYAML for the page sets
|
|
if: matrix.name == 'aac-rs'
|
|
shell: bash
|
|
run: |
|
|
export PATH="$HOME/.local/bin:$HOME/.cargo/bin:$PATH"
|
|
command -v uv >/dev/null 2>&1 || curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
uv venv -q --python 3.12 "$HOME/.pagesets-py"
|
|
uv pip install -q --python "$HOME/.pagesets-py/bin/python" pyyaml
|
|
"$HOME/.pagesets-py/bin/python" -c 'import sys, yaml; assert sys.version_info >= (3, 11); print(sys.version, "PyYAML", yaml.__version__)'
|
|
echo "AAC_PYTHON=$HOME/.pagesets-py/bin/python" >> "$GITHUB_ENV"
|
|
|
|
- name: Cache cargo registry + target
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: |
|
|
~/.cargo/registry
|
|
~/.cargo/git
|
|
${{ matrix.dir }}/target
|
|
key: standards-${{ matrix.name }}${{ matrix.cache_suffix }}-${{ hashFiles(format('{0}/Cargo.lock', matrix.dir), 'rust-toolchain.toml') }}
|
|
restore-keys: |
|
|
standards-${{ matrix.name }}${{ matrix.cache_suffix }}-
|
|
|
|
# --all-targets is load-bearing: a plain `cargo check` does not build test
|
|
# targets, and smart-byte's dev-dependencies drifted independently of its
|
|
# normal ones (rand_chacha 0.10 against rand 0.8). The library compiled
|
|
# while the tests did not.
|
|
- name: Check (all targets)
|
|
working-directory: ${{ matrix.dir }}
|
|
env:
|
|
RUSTFLAGS: ${{ matrix.rustflags }}
|
|
run: cargo check --workspace --all-targets
|
|
|
|
- name: Test
|
|
working-directory: ${{ matrix.dir }}
|
|
env:
|
|
RUSTFLAGS: ${{ matrix.rustflags }}
|
|
# Where z3 was installed above, its absence is a failure, not a skip.
|
|
JOULE_FIX_EXPECT_Z3: ${{ matrix.z3 && '1' || '' }}
|
|
run: cargo test --workspace
|
|
|
|
# The NVML backends sit behind features no workspace member enables
|
|
# (jcp-energy `nvml`, eoc-meter `cuda`, jouleclaw-energy `nvml`), so the
|
|
# step above never compiles them, let alone runs their tests. Here they
|
|
# build and their tests run, as do mesh-rs's feature-gated tests (the
|
|
# gguf engines; mesh-openai's `server` and its serve_example test). The container has no GPU and Docker masks
|
|
# powercap, so the hardware tests take their "unavailable, and the
|
|
# oracle agrees" branch; the rule's unit tests run in full.
|
|
- name: Feature-gated energy tests
|
|
if: matrix.feature_tests
|
|
working-directory: ${{ matrix.dir }}
|
|
env:
|
|
RUSTFLAGS: ${{ matrix.rustflags }}
|
|
run: cargo test ${{ matrix.feature_tests }}
|
|
|
|
# The page-set packages are not committed; their catalogue is, naming
|
|
# each package by its content. Any change to what they are built from —
|
|
# the sources, the app's translations and gloss audits, the symbol
|
|
# index and sprites, the navigation words, vocabulary.rs, i18n.rs, the
|
|
# language packs — changes the packages, and the committed catalogue
|
|
# must change with them (catalog.py --write), or the app would offer
|
|
# packages it would then refuse to use.
|
|
- name: Page-set catalogue is current
|
|
if: matrix.name == 'aac-rs'
|
|
working-directory: ${{ matrix.dir }}
|
|
shell: bash
|
|
run: |
|
|
"$AAC_PYTHON" tools/pagesets/catalog.py --check
|
|
"$AAC_PYTHON" tools/pagesets/phrases/build.py --check
|
|
|
|
# Run each standard's conformance suite.
|
|
#
|
|
# These suites live in a binary's `main()`, not in `#[test]`, so
|
|
# `cargo test` never executes them — verified, not assumed:
|
|
# `cargo test -p aac-conformance` runs **0 tests** while
|
|
# `cargo run -p aac-conformance` runs **40 checks**; map is 1 test vs 15
|
|
# checks. Same shape for jcp, proof and sandbox. The suites include
|
|
# negative vectors (`C-neg`, `Cneg`), which are the ones that catch a
|
|
# verifier ACCEPTING what it must reject.
|
|
#
|
|
# This is the `main()` instance of the same bug as a feature-gated test:
|
|
# the code compiles under `--all-targets`, reports nothing, and looks
|
|
# covered. Compiling a suite is not running it.
|
|
#
|
|
# `--bin` is explicit because these crates also ship a `gen-vectors`
|
|
# binary; `cargo run -p <crate>` is ambiguous with two binaries and fails
|
|
# rather than choosing — which is how this step would silently never run.
|
|
#
|
|
# No path argument: these binaries take none. They check the
|
|
# implementation against in-code expectations, which is a different claim
|
|
# from validating the published `conformance/v1` files on disk.
|
|
#
|
|
# That second claim is made elsewhere, by tests inside the reference
|
|
# implementations that read `conformance/v1` off disk, which the `Test`
|
|
# step above already runs — so it needs no entry here and a missing
|
|
# `conformance:` key is not evidence a standard's pack is unchecked.
|
|
#
|
|
# Deliberately not "a `tests/pack.rs` in each conformance crate": that
|
|
# is the convention in several of them, not a rule, and some crates read
|
|
# the pack from a test named something else or from another crate
|
|
# entirely. So counting files by that name does not audit pack coverage,
|
|
# and finding a crate without one is not a finding. The question is
|
|
# which tests read the published bytes:
|
|
#
|
|
# grep -rl --include=*.rs -E 'conformance/v[0-9]' <standard>/
|
|
#
|
|
# That mistake has been made here twice, both times concluding standards
|
|
# were uncovered when they were not.
|
|
#
|
|
# `joule-code` is deliberately absent from that key. Its verifier does read
|
|
# the published pack, but it requires a `verify <pack-dir>` argument and
|
|
# prints usage with a non-zero exit when given none, so it cannot be
|
|
# invoked by this step's no-argument form. It has its own workflow,
|
|
# `joule-code-conformance.yml`, plus a `tests/pack.rs` that the `Test` step
|
|
# above runs — the latter because that workflow is `paths:`-filtered to
|
|
# `joule-code/**`, while the crate depends on `jcp-*` across workspaces.
|
|
- name: Conformance suite
|
|
if: matrix.conformance
|
|
working-directory: ${{ matrix.dir }}
|
|
env:
|
|
RUSTFLAGS: ${{ matrix.rustflags }}
|
|
run: cargo run -q -p ${{ matrix.conformance }} --bin ${{ matrix.conformance }}
|
|
|
|
# ── The macOS energy backends are exercised NOWHERE otherwise ─────────────
|
|
#
|
|
# Five crates carry an Apple Silicon IOReport energy meter: jcp-energy,
|
|
# proof-cascade, eoc-meter, jouleclaw-energy and sandbox-meter. Each is
|
|
# behind `#[cfg(target_os = "macos")]` (jouleclaw-energy also behind its
|
|
# `ioreport` feature), and every workspace holding one is pinned to a linux
|
|
# runner above. A platform-gated test on a single-platform runner is zero
|
|
# coverage, and it is indistinguishable from passing.
|
|
#
|
|
# What this job checks, and why it used to flake. On macOS 27 an unprivileged
|
|
# reader does not see the IOReport "Energy Model" CPU rails (`CPU Energy`,
|
|
# `DIE_<n>_CPU Energy`) move under load. They are republished only in rare
|
|
# lumps: on the M3 Ultra both dies held for over 22 minutes, then jumped
|
|
# about 1.3 kJ each in a single 100 ms sample. On the M5 Max the GPU rail
|
|
# stays live. The old jcp-energy test asserted that the *summed* rails
|
|
# advanced during a CPU burn, and the GPU rail satisfied that whenever
|
|
# something drew on screen. It passed by day and failed at night. The
|
|
# 2026-09-19 zero that "healed" by 09-21 was this effect, not a recovery.
|
|
#
|
|
# Every meter now uses IOReport only when the CPU rail itself keeps
|
|
# advancing while the probe burns CPU: in at least 5 sample intervals, at
|
|
# least 4 in every 5, over at least 300 ms. A lump is published in steps (one
|
|
# was caught as +61.8 kJ, then +3.4 J 64 ms later), so two advancing
|
|
# intervals are still just a lump. Otherwise the meter falls back to its
|
|
# estimator rung. Its tests assert against an independent oracle that reads
|
|
# the CPU rail directly, so this job is deterministic:
|
|
# - green while each meter declines a rail that does not keep moving;
|
|
# - red if any meter selects one;
|
|
# - on a host whose rail is live, red unless CPU work meters non-zero.
|
|
#
|
|
# Each step fails unless its test printed the `ioreport liveness:` line. That
|
|
# line is the only proof the platform-gated test ran and asserted a branch,
|
|
# rather than a filter, a feature or a cfg leaving zero tests to pass. It goes
|
|
# to stderr and is captured on success, hence `--nocapture` and `2>&1`.
|
|
macos-energy-backend:
|
|
name: macOS energy backends
|
|
# Runs when the change touches a workspace whose energy backend it tests (the plan's
|
|
# `energy`), and on every push to main.
|
|
needs: plan
|
|
if: needs.plan.outputs.energy == 'true'
|
|
runs-on: macos
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Install Rust (workspace pin)
|
|
shell: bash
|
|
run: |
|
|
export PATH="$HOME/.cargo/bin:$PATH"
|
|
command -v rustup >/dev/null 2>&1 || \
|
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal
|
|
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
|
|
|
|
- name: Cache cargo registry + targets
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: |
|
|
~/.cargo/registry
|
|
~/.cargo/git
|
|
jcp/jcp-rs/target
|
|
proof/proof-rs/target
|
|
eoc/eoc-rs/target
|
|
jouleclaw/jouleclaw-rs/target
|
|
sandbox/sandbox-rs/target
|
|
joule-code/joule-code-rs/target
|
|
key: standards-macos-energy-${{ hashFiles('jcp/jcp-rs/Cargo.lock', 'proof/proof-rs/Cargo.lock', 'eoc/eoc-rs/Cargo.lock', 'jouleclaw/jouleclaw-rs/Cargo.lock', 'sandbox/sandbox-rs/Cargo.lock', 'joule-code/joule-code-rs/Cargo.lock', 'rust-toolchain.toml') }}
|
|
restore-keys: |
|
|
standards-macos-energy-
|
|
|
|
# Each crate's lib tests plus its integration tests. Each crate has a
|
|
# tests/ file that checks the liveness probe runs exactly once per process,
|
|
# and that check only holds in a fresh process. `--lib` would skip it.
|
|
- name: jcp-energy
|
|
shell: bash
|
|
working-directory: jcp/jcp-rs
|
|
run: |
|
|
set -o pipefail
|
|
log=$(mktemp)
|
|
cargo test -p jcp-energy -- --nocapture 2>&1 | tee "$log"
|
|
grep -q 'ioreport liveness:' "$log"
|
|
|
|
- name: proof-cascade
|
|
shell: bash
|
|
working-directory: proof/proof-rs
|
|
run: |
|
|
set -o pipefail
|
|
log=$(mktemp)
|
|
cargo test -p proof-cascade -- --nocapture 2>&1 | tee "$log"
|
|
grep -q 'ioreport liveness:' "$log"
|
|
|
|
- name: eoc-meter
|
|
shell: bash
|
|
working-directory: eoc/eoc-rs
|
|
run: |
|
|
set -o pipefail
|
|
log=$(mktemp)
|
|
cargo test -p eoc-meter -- --nocapture 2>&1 | tee "$log"
|
|
grep -q 'ioreport liveness:' "$log"
|
|
|
|
# Double-gated: `cfg(all(feature = "ioreport", target_os = "macos"))`.
|
|
# A macOS runner alone reaches none of it.
|
|
- name: jouleclaw-energy (feature AND platform gated)
|
|
shell: bash
|
|
working-directory: jouleclaw/jouleclaw-rs
|
|
run: |
|
|
set -o pipefail
|
|
log=$(mktemp)
|
|
cargo test -p jouleclaw-energy --features ioreport -- --nocapture 2>&1 | tee "$log"
|
|
grep -q 'ioreport liveness:' "$log"
|
|
|
|
- name: sandbox-meter
|
|
shell: bash
|
|
working-directory: sandbox/sandbox-rs
|
|
run: |
|
|
set -o pipefail
|
|
log=$(mktemp)
|
|
cargo test -p sandbox-meter -- --nocapture 2>&1 | tee "$log"
|
|
grep -q 'ioreport liveness:' "$log"
|
|
# The tree meter's real-process tests (tests/tree_energy.rs) are
|
|
# gated to Apple Silicon macOS too; each prints this line.
|
|
grep -q 'tree meter:' "$log"
|
|
|
|
# jcp-gateway meters each gated call on the upstream server's own
|
|
# process tree with that meter. The jcp-rs matrix entry runs on Linux,
|
|
# where there is no tree meter and every call is metered machine-wide,
|
|
# so its real-process test (tests/upstream_tree.rs, Apple Silicon macOS
|
|
# only) runs nowhere else. Each call it meters on a real server's tree
|
|
# prints this line (the machine-wide fallback test prints another), so
|
|
# the grep fails if a cfg or a filter left none of them to run.
|
|
- name: jcp-gateway (upstream process tree)
|
|
shell: bash
|
|
working-directory: jcp/jcp-rs
|
|
run: |
|
|
set -o pipefail
|
|
log=$(mktemp)
|
|
cargo test -p jcp-gateway -- --nocapture 2>&1 | tee "$log"
|
|
grep -q 'upstream tree:' "$log"
|
|
|
|
# proof-solvers reads each resident z3 decision off z3's own process
|
|
# tree with that meter (tests/z3_tree.rs, Apple Silicon macOS only, on a
|
|
# real z3: Homebrew's, on this runner's PATH). The proof-rs matrix entry
|
|
# runs on Linux, where there is no tree meter, so these run nowhere
|
|
# else. Each decision they meter prints this line; a missing z3, or a
|
|
# per-process counter that is not live, prints another, and the grep
|
|
# fails.
|
|
- name: proof-solvers (resident z3 process tree)
|
|
shell: bash
|
|
working-directory: proof/proof-rs
|
|
run: |
|
|
set -o pipefail
|
|
log=$(mktemp)
|
|
cargo test -p proof-solvers -- --nocapture 2>&1 | tee "$log"
|
|
grep -q 'z3 tree meter:' "$log"
|
|
|
|
# sandbox-proof's local inference proof meters the engine on its own
|
|
# process tree, spawned suspended and read before it is reaped. Its
|
|
# real-engine tests are gated to Apple Silicon macOS; each run they
|
|
# meter prints this line.
|
|
- name: sandbox-proof (local engine process tree)
|
|
shell: bash
|
|
working-directory: sandbox/sandbox-rs
|
|
run: |
|
|
set -o pipefail
|
|
log=$(mktemp)
|
|
cargo test -p sandbox-proof -- --nocapture 2>&1 | tee "$log"
|
|
grep -q 'local engine tree:' "$log"
|
|
|
|
# joule-fix meters its L3 model on the model's own process tree. The
|
|
# joule-code-rs matrix entry runs on linux-arm64, where there is no tree
|
|
# meter, so its oracle test (the model's reading against the stand-in's
|
|
# own energy count) takes its fallback branch there and runs nowhere
|
|
# else. It prints this line only where it checked the reading.
|
|
- name: joule-code-remediate (L3 model process tree)
|
|
shell: bash
|
|
working-directory: joule-code/joule-code-rs
|
|
run: |
|
|
set -o pipefail
|
|
log=$(mktemp)
|
|
cargo test -p joule-code-remediate l3_ -- --nocapture 2>&1 | tee "$log"
|
|
grep -q 'l3 model process:' "$log"
|
|
|
|
# Feature AND platform gated: `fabric-meter`, which no other job passes,
|
|
# and macOS. The fabric calibration rows' macOS stamp, and a CPU member
|
|
# priced on this process's own counters against its own energy count
|
|
# (tests/process_calibration.rs), each print their line only there.
|
|
- name: jouleclaw-loader-gguf (fabric-meter, process calibration)
|
|
shell: bash
|
|
working-directory: jouleclaw/jouleclaw-rs
|
|
run: |
|
|
set -o pipefail
|
|
log=$(mktemp)
|
|
cargo test -p jouleclaw-loader-gguf --features "rayon fabric-meter" marginal_energy -- --nocapture 2>&1 | tee "$log"
|
|
cargo test -p jouleclaw-loader-gguf --features "rayon fabric-meter" --test process_calibration -- --nocapture 2>&1 | tee -a "$log"
|
|
grep -q 'calibration row stamped on macOS:' "$log"
|
|
grep -q 'process calibration: CPU member' "$log"
|
|
|
|
# The meter code the steps above test compiles only on macOS (IOReport,
|
|
# proc_pid_rusage, the process-tree readers), so no Linux job lints it.
|
|
# Lints in it went unseen until a macOS sweep found them (#244). Each
|
|
# crate is linted here, on its own platform and with the features its
|
|
# tests use, and a warning fails the step.
|
|
- name: clippy on the macOS meter code
|
|
shell: bash
|
|
run: |
|
|
set -eu
|
|
lint() { (cd "$1" && shift && cargo clippy --all-targets --no-deps "$@" -- -D warnings); }
|
|
lint jcp/jcp-rs -p jcp-energy -p jcp-gateway
|
|
lint proof/proof-rs -p proof-cascade -p proof-solvers
|
|
lint eoc/eoc-rs -p eoc-meter
|
|
lint jouleclaw/jouleclaw-rs -p jouleclaw-energy --features ioreport
|
|
lint sandbox/sandbox-rs -p sandbox-meter -p sandbox-proof
|