mirror of
https://github.com/bdbrown4/peltier.io.git
synced 2026-11-07 10:58:28 +00:00
On a Windows host the preflight's job is to deliver a structured refusal
(measurement is Linux/POSIX-only). But a Windows-native harness with no
POSIX shell never got that far: preflight.sh fails to LAUNCH, and an agent
staring at "sh: not found" is exactly the agent that hand-rolls a timing
loop. The refusal doctrine has to be as portable as the skill.
- scripts/preflight.ps1: faithful port of the preflight contract, runs on
Windows PowerShell 5.1 and pwsh 7+ on any OS. Host check first (so
Windows refuses before touching anything), same walk-up resolution, same
always-build rule, same key=value output, same exit codes. The source is
pure ASCII with the shared em dashes composed at runtime — Windows
PowerShell 5.1 misdecodes BOM-less UTF-8, and that exact bug class
already bit this machine once (the francis-watchdog parse failure).
- Two implementations of one contract is a drift risk, so it is gated like
every other claim in this repo: the skill-preflight CI job now runs BOTH
scripts on the same ubuntu host and byte-diffs their stdout on all three
paths (in-checkout resolve, no-checkout refusal, PELTIER_HOME from a
foreign repo). Disagreement is a red build, not a footnote.
- New skill-preflight-windows job (windows-latest): asserts the native
refusal end-to-end under Windows PowerShell 5.1 AND pwsh (STATUS=refuse,
nonzero exit, the 'unsupported host' reason), plus Git Bash sh for good
measure. Verified live on a real Windows 11 box before shipping: 5.1
parses the pure-ASCII source clean and refuses with exit 1.
- SKILL.md / references/verify.md: document the second entry point ("use
whichever your host can launch; never both, never neither"); book chapter
and CLAUDE.md updated to match. .agents mirror regenerated (identical).
The ZeroClaw variant is unaffected: rm -rf scripts/ drops the ps1 too,
and its audit still passes (7 files scanned).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
265 lines
12 KiB
YAML
265 lines
12 KiB
YAML
name: ci
|
|
on:
|
|
pull_request:
|
|
push:
|
|
branches: [main]
|
|
|
|
jobs:
|
|
trust-layer:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: dtolnay/rust-toolchain@stable
|
|
- uses: Swatinem/rust-cache@v2
|
|
- run: cargo test --workspace
|
|
- run: cargo clippy --workspace --all-targets -- -D warnings
|
|
- run: cargo fmt --all --check
|
|
|
|
# Phase 1: target gates run mechanically, and the bench workload
|
|
# executes inside a NO-NETWORK container (SPEC §3.1/§10). The runner
|
|
# is a shared VM — fine for gates; bench numbers for verdicts come
|
|
# from calibrated hardware, not this job.
|
|
target-gates-no-net:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: dtolnay/rust-toolchain@stable
|
|
- uses: Swatinem/rust-cache@v2
|
|
- name: Toolchains for C/C++ targets
|
|
run: sudo apt-get update -qq && sudo apt-get install -y -qq clang cmake
|
|
- name: Fetch pinned targets
|
|
run: |
|
|
sh targets/fetch.sh comrak
|
|
sh targets/fetch.sh tokei
|
|
sh targets/fetch.sh cjson
|
|
- name: Generate + verify pinned corpora
|
|
run: |
|
|
sh corpora/comrak/gen-corpus.sh
|
|
sh corpora/tokei/gen-corpus.sh
|
|
sh corpora/cjson/gen-corpus.sh
|
|
- name: Build tokei baseline
|
|
run: cargo build --release --manifest-path targets/tokei/workspace/Cargo.toml
|
|
- name: Gates — tokei (Rust; corpus pin -> upstream tests -> golden replay)
|
|
run: cargo run -p diff-test -- tokei
|
|
- name: Gates — cjson (C; clang build -> Unity/CTest -> golden replay)
|
|
run: cargo run -p diff-test -- cjson
|
|
- name: Bench workload inside no-network container
|
|
run: |
|
|
docker run --rm --network=none \
|
|
-v "$PWD:/w" -w /w \
|
|
debian:bookworm-slim \
|
|
sh -c "targets/tokei/workspace/target/release/tokei --sort code corpora/tokei/input | sha256sum"
|
|
|
|
# Informational: measure THIS runner's noise floor with the A/A +
|
|
# injection harness and archive the evidence. A shared VM failing the
|
|
# bars is expected — the artifact is the point (trust layer judges
|
|
# environments empirically; verdict benches belong on bench-metal).
|
|
runner-calibration:
|
|
runs-on: ubuntu-latest
|
|
continue-on-error: true
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: dtolnay/rust-toolchain@stable
|
|
- uses: Swatinem/rust-cache@v2
|
|
- name: Calibrate (reduced sessions, synthetic CPU-bound workload)
|
|
run: |
|
|
mkdir -p calib
|
|
cargo run -p bench-runner -- calibrate \
|
|
--cmd "head -c 3000000 /dev/urandom | sha256sum" \
|
|
--sessions 20 --out calib/gh-runner-calibration.json \
|
|
| tee calib/summary.txt
|
|
- uses: actions/upload-artifact@v4
|
|
if: always()
|
|
with:
|
|
name: runner-calibration
|
|
path: calib/
|
|
|
|
# Informational: does perf work here, and with which event sources?
|
|
perf-probe:
|
|
runs-on: ubuntu-latest
|
|
continue-on-error: true
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: Probe perf availability
|
|
run: |
|
|
sudo apt-get -qq update && sudo apt-get -qq install -y linux-tools-common linux-tools-generic || true
|
|
PERF=$(ls /usr/lib/linux-tools/*/perf 2>/dev/null | head -1 || command -v perf || true)
|
|
echo "perf binary: ${PERF:-none}"
|
|
[ -n "$PERF" ] || exit 0
|
|
sudo sysctl -w kernel.perf_event_paranoid=1 || true
|
|
echo "--- software clock sampling:"
|
|
$PERF record -e cpu-clock -o /tmp/p.data -- sh -c 'i=0; while [ $i -lt 2000000 ]; do i=$((i+1)); done' && $PERF report -i /tmp/p.data --stdio | head -5 || echo "cpu-clock sampling unavailable"
|
|
echo "--- hardware cycle counter:"
|
|
$PERF stat -e cycles true 2>&1 | tail -3 || true
|
|
|
|
# The peltier skill's contract: preflight must resolve the trust layer when
|
|
# one is reachable, and REFUSE when it is not. The refusal is the
|
|
# safety-critical path — a skill that silently degrades to a hand-rolled
|
|
# timing loop is worse than no skill — so it is asserted here, not assumed.
|
|
# Statistical outcomes are NOT asserted: a shared runner is too noisy for
|
|
# that, which is the whole reason the skill calibrates before it claims.
|
|
skill-preflight:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: dtolnay/rust-toolchain@stable
|
|
- uses: Swatinem/rust-cache@v2
|
|
|
|
- name: SKILL.md conforms to the Agent Skills spec (both copies)
|
|
run: |
|
|
python3 - <<'PY'
|
|
import re, sys, yaml, pathlib
|
|
for skill in (".claude/skills/peltier", ".agents/skills/peltier"):
|
|
p = pathlib.Path(skill) / "SKILL.md"
|
|
text = p.read_text(encoding="utf-8")
|
|
if not text.startswith("---\n"):
|
|
sys.exit(f"{p}: must open with a --- frontmatter block")
|
|
fm = yaml.safe_load(text.split("---", 2)[1]) # raises on malformed YAML
|
|
body = text.split("---", 2)[2]
|
|
for key in ("name", "description"):
|
|
if not isinstance(fm.get(key), str) or not fm[key].strip():
|
|
sys.exit(f"{p}: frontmatter '{key}' must be a non-empty string")
|
|
# agentskills.io spec: 1-64 chars, [a-z0-9-], no edge/double hyphens,
|
|
# name == parent dir; description <= 1024 chars; body <= 500 lines.
|
|
n = fm["name"]
|
|
if not re.fullmatch(r"[a-z0-9]+(-[a-z0-9]+)*", n) or len(n) > 64:
|
|
sys.exit(f"{p}: name '{n}' violates the Agent Skills name rules")
|
|
if n != p.parent.name:
|
|
sys.exit(f"{p}: name '{n}' != directory '{p.parent.name}'")
|
|
if len(fm["description"]) > 1024:
|
|
sys.exit(f"{p}: description exceeds 1024 chars")
|
|
if len(body.splitlines()) > 500:
|
|
sys.exit(f"{p}: body exceeds 500 lines")
|
|
if "metadata" in fm and not (
|
|
isinstance(fm["metadata"], dict)
|
|
and all(isinstance(k, str) and isinstance(v, str) for k, v in fm["metadata"].items())
|
|
):
|
|
sys.exit(f"{p}: metadata must be a string-to-string map")
|
|
print(f"{p}: spec OK (name={n}, description={len(fm['description'])} chars)")
|
|
PY
|
|
|
|
- name: .agents mirror is byte-identical to .claude canonical
|
|
run: |
|
|
diff -r .claude/skills/peltier .agents/skills/peltier
|
|
echo "mirror identical"
|
|
|
|
- name: Installer stamps both layouts + the ZeroClaw variant correctly
|
|
run: |
|
|
if sh scripts/install-skill.sh /tmp/no-such-dir 2>/dev/null; then
|
|
echo "FAIL: installer accepted a nonexistent target"; exit 1
|
|
fi
|
|
mkdir -p /tmp/consumer && sh scripts/install-skill.sh /tmp/consumer
|
|
diff -r .claude/skills/peltier /tmp/consumer/.claude/skills/peltier
|
|
diff -r .claude/skills/peltier /tmp/consumer/.agents/skills/peltier
|
|
sh scripts/install-skill.sh --zeroclaw-variant /tmp/zc-variant
|
|
test ! -e /tmp/zc-variant/scripts # script-less: ZeroClaw blocks script files
|
|
test -f /tmp/zc-variant/SKILL.md
|
|
diff -r .claude/skills/peltier/references /tmp/zc-variant/references
|
|
echo "installer OK: both layouts identical to canonical; zeroclaw variant has no scripts/"
|
|
|
|
- name: Preflight resolves inside a peltier checkout
|
|
run: |
|
|
sh .claude/skills/peltier/scripts/preflight.sh | tee /tmp/pf.txt
|
|
grep -q '^STATUS=ok$' /tmp/pf.txt
|
|
grep -q "^PELTIER_HOME=$PWD$" /tmp/pf.txt
|
|
BR=$(sed -n 's/^BENCH_RUNNER=//p' /tmp/pf.txt); test -x "$BR"
|
|
|
|
- name: Preflight REFUSES with no trust layer reachable
|
|
run: |
|
|
# The skill must be copied OUT of the checkout: preflight also walks
|
|
# up from its own location, so running it in-place would find this
|
|
# very repo and (correctly) resolve — testing nothing.
|
|
cp -r .claude/skills/peltier /tmp/skillcopy
|
|
cd /tmp && mkdir -p not-a-repo && cd not-a-repo
|
|
set +e
|
|
env -u PELTIER_HOME sh /tmp/skillcopy/scripts/preflight.sh > out.txt 2>&1
|
|
rc=$?
|
|
set -e
|
|
cat out.txt
|
|
test "$rc" -ne 0 || { echo "FAIL: preflight did not refuse"; exit 1; }
|
|
grep -q '^STATUS=refuse$' out.txt
|
|
|
|
- name: Preflight works when consumed from another repo (PELTIER_HOME)
|
|
run: |
|
|
cd /tmp && mkdir -p other-project && cd other-project
|
|
PELTIER_HOME="$GITHUB_WORKSPACE" \
|
|
sh "$GITHUB_WORKSPACE/.claude/skills/peltier/scripts/preflight.sh" | tee pf.txt
|
|
grep -q '^STATUS=ok$' pf.txt
|
|
|
|
- name: sh and ps1 preflights are byte-equivalent (two entry points, one contract)
|
|
run: |
|
|
# preflight.ps1 exists for hosts with no POSIX shell. Two
|
|
# implementations of the same contract can drift, so both run here
|
|
# on the same host and their stdout is byte-diffed on every path.
|
|
# 1. in-checkout resolve
|
|
sh .claude/skills/peltier/scripts/preflight.sh > /tmp/eq-sh.txt
|
|
pwsh -NoProfile -File .claude/skills/peltier/scripts/preflight.ps1 > /tmp/eq-ps.txt
|
|
diff /tmp/eq-sh.txt /tmp/eq-ps.txt
|
|
# 2. refusal with no checkout reachable (copied out of the repo)
|
|
cp -r .claude/skills/peltier /tmp/eqskill
|
|
mkdir -p /tmp/eq-not-a-repo && cd /tmp/eq-not-a-repo
|
|
set +e
|
|
env -u PELTIER_HOME sh /tmp/eqskill/scripts/preflight.sh > sh.txt 2>&1; shrc=$?
|
|
env -u PELTIER_HOME pwsh -NoProfile -File /tmp/eqskill/scripts/preflight.ps1 > ps.txt 2>&1; psrc=$?
|
|
set -e
|
|
test "$shrc" -ne 0 && test "$psrc" -ne 0
|
|
diff sh.txt ps.txt
|
|
# 3. resolve via PELTIER_HOME from a foreign repo
|
|
mkdir -p /tmp/eq-foreign && cd /tmp/eq-foreign
|
|
PELTIER_HOME="$GITHUB_WORKSPACE" sh /tmp/eqskill/scripts/preflight.sh > sh2.txt
|
|
PELTIER_HOME="$GITHUB_WORKSPACE" pwsh -NoProfile -File /tmp/eqskill/scripts/preflight.ps1 > ps2.txt
|
|
diff sh2.txt ps2.txt
|
|
echo "equivalence holds on all three paths"
|
|
|
|
- name: The harness the skill drives actually runs (plumbing, not verdict)
|
|
run: |
|
|
BR=$(sed -n 's/^BENCH_RUNNER=//p' /tmp/pf.txt)
|
|
cp .claude/skills/peltier/assets/accept.template.toml /tmp/accept.toml
|
|
"$BR" --config /tmp/accept.toml compare \
|
|
--baseline 'sleep 0.02' --candidate 'sleep 0.02' | tee /tmp/cmp.txt
|
|
grep -q 'speedup (baseline/candidate): median .*95% CI \[' /tmp/cmp.txt
|
|
grep -q '^verdict: ' /tmp/cmp.txt
|
|
|
|
- name: Noise floor of this runner (informational — A/A must find nothing)
|
|
continue-on-error: true
|
|
run: |
|
|
BR=$(sed -n 's/^BENCH_RUNNER=//p' /tmp/pf.txt)
|
|
"$BR" --config /tmp/accept.toml aa --cmd 'sleep 0.02'
|
|
|
|
# The refusal doctrine must survive hosts with no POSIX shell: on a real
|
|
# Windows runner, BOTH preflight entry points must refuse with a structured
|
|
# STATUS=refuse — never a launch failure an agent could misread as license
|
|
# to hand-roll a timing loop. Windows PowerShell 5.1 is tested separately
|
|
# from pwsh because 5.1 misdecodes BOM-less UTF-8 (the ps1 source is pure
|
|
# ASCII for exactly that reason).
|
|
skill-preflight-windows:
|
|
runs-on: windows-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: ps1 refuses natively (Windows PowerShell 5.1)
|
|
shell: powershell
|
|
run: |
|
|
$out = & powershell -NoProfile -ExecutionPolicy Bypass -File .claude\skills\peltier\scripts\preflight.ps1
|
|
$out | Write-Output
|
|
if ($LASTEXITCODE -eq 0) { throw "preflight did not refuse on Windows" }
|
|
$joined = $out -join "`n"
|
|
if ($joined -notlike "*STATUS=refuse*") { throw "no STATUS=refuse in output" }
|
|
if ($joined -notlike "*unsupported host 'Windows'*") { throw "missing refusal reason" }
|
|
- name: ps1 refuses natively (pwsh)
|
|
shell: pwsh
|
|
run: |
|
|
$out = & pwsh -NoProfile -File .claude\skills\peltier\scripts\preflight.ps1
|
|
$out | Write-Output
|
|
if ($LASTEXITCODE -eq 0) { throw "preflight did not refuse under pwsh" }
|
|
if (($out -join "`n") -notlike "*STATUS=refuse*") { throw "no STATUS=refuse in output" }
|
|
- name: sh entry point also refuses here (Git Bash)
|
|
shell: bash
|
|
run: |
|
|
set +e
|
|
sh .claude/skills/peltier/scripts/preflight.sh > out.txt 2>&1
|
|
rc=$?
|
|
set -e
|
|
cat out.txt
|
|
test "$rc" -ne 0
|
|
grep -q '^STATUS=refuse$' out.txt
|