Skip to content
Closed
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
7 changes: 7 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,13 @@ jobs:
# each planted defect before it is trusted to report a section green.
- name: fat_driver case table must still turn RED
run: python3 scripts/ci/fat_driver.py self-test
# PMAT-4510: every secrets.<NAME> a section reads must be plumbed above as
# FAT_SECRET_<NAME> under its full name; #4441 cut one short and the
# receipt signer ran with an empty key on every PR. Text-only, no build.
- name: FAT_SECRET plumbing guard case table
run: setsid --wait bash scripts/check_ci_fat_secrets_plumbed.sh --self-test
- name: every section secret is plumbed under its full name
run: setsid --wait bash scripts/check_ci_fat_secrets_plumbed.sh
- name: Sections (the determinism raster returns first; the rest keep running)
run: >-
python3 scripts/ci/fat_driver.py run
Expand Down
127 changes: 33 additions & 94 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@
# job runs on the fleet. x86_64 builds on yoga and aarch64 natively on gx10, both
# inside rust:1.93.0-bullseye (binary-release.yml's CPU-lane pattern), so the
# nightly's glibc floor is 2.31; the ubuntu-latest build needed GLIBC_2.39 and did
# not run on lambda's 22.04. Windows is gone: no self-hosted Windows runner is
# registered and there is no hosted fallback. macOS came back with a box for it
# (#3204): aarch64-apple-darwin builds host-native on mini-m4 (build-darwin).
# not run on lambda's 22.04. The macOS and Windows targets are gone: no self-hosted
# macOS or Windows runner is registered in the org, and there is no hosted
# fallback. They come back when a box for them does.

name: Nightly

Expand Down Expand Up @@ -68,9 +68,16 @@ jobs:
runs-on: [self-hosted, Linux, X64, clean-room]
outputs:
decision: ${{ steps.gate.outputs.decision }}
sha: ${{ steps.gate.outputs.sha }}
steps:
# The nightly ships main, never the dispatching ref: a `workflow_dispatch` from
# a PR branch would otherwise publish that branch as "nightly" (operator
# 2026-09-28). gate resolves main's head ONCE; build and publish check out that
# exact sha, so a push to main mid-run cannot split one nightly across two heads.
- name: Checkout
uses: actions/checkout@v7
with:
ref: main

- name: Case table
run: python3 scripts/nightly_manifest.py --self-test
Expand All @@ -81,11 +88,13 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
NIGHTLY_SHA=$(git rev-parse HEAD)
echo "sha=$NIGHTLY_SHA" >> "$GITHUB_OUTPUT"
# A missing manifest (first run) is "no previous verdict", not an error.
bash scripts/nightly_fetch_manifest.sh prev-manifest.json
d=$(python3 scripts/nightly_manifest.py gate --sha "$GITHUB_SHA" --prev prev-manifest.json)
d=$(python3 scripts/nightly_manifest.py gate --sha "$NIGHTLY_SHA" --prev prev-manifest.json)
echo "decision=$d" >> "$GITHUB_OUTPUT"
echo "### nightly gate for \`${GITHUB_SHA:0:9}\`: **$d**" >> "$GITHUB_STEP_SUMMARY"
echo "### nightly gate for \`${NIGHTLY_SHA:0:9}\`: **$d**" >> "$GITHUB_STEP_SUMMARY"

# ── Build: Linux x86_64 on yoga, aarch64 natively on gx10 ──
build:
Expand All @@ -103,9 +112,13 @@ jobs:
host: gx10
labels: '["self-hosted", "Linux", "ARM64", "cuda", "gx10"]'
runs-on: ${{ fromJSON(matrix.labels) }}
env:
NIGHTLY_SHA: ${{ needs.gate.outputs.sha }}
steps:
- name: Checkout
uses: actions/checkout@v7
with:
ref: ${{ needs.gate.outputs.sha }}

# A sibling container through the mounted socket, as in binary-release.yml's
# CPU lanes: every bind-mount path must exist on the HOST (the work root is
Expand All @@ -130,6 +143,10 @@ jobs:
BIN_ARGS=$(python3 scripts/nightly_manifest.py bins --metadata "$RUNNER_TEMP/metadata.json" --format cargo)
echo "NIGHTLY_BINS=$NIGHTLY_BINS" >> "$GITHUB_ENV"
echo "Shipping $(echo "$NIGHTLY_BINS" | tr ',' '\n' | wc -l) bins: $NIGHTLY_BINS"
# Inside the container git cannot read the mounted checkout, so without the
# override every CPU bin printed +no-git and record() failed version-no-sha
# (run 36366064406). Both builds (CPU and cuda) get the same override.
APR_SHA=$(git rev-parse --short HEAD)
$DOCKER run --rm \
-v "$GITHUB_WORKSPACE:/workspace" \
-v "$CACHE/registry:/usr/local/cargo/registry" \
Expand All @@ -139,6 +156,7 @@ jobs:
-e CARGO_INCREMENTAL=0 \
-e T="$T" \
-e BIN_ARGS="$BIN_ARGS" \
-e APR_GIT_SHA_OVERRIDE="$APR_SHA" \
rust:1.93.0-bullseye \
sh -c 'set -e; ldd --version | head -1; cargo build --locked --release $BIN_ARGS --target "$T"'
mkdir -p "target/$T/release" dist
Expand All @@ -162,7 +180,6 @@ jobs:
# The driver is dlopen'd at run time (aprender-gpu: dep:libloading), so
# the bullseye image needs no toolkit -- binary-release.yml's cuda lane.
# Record proves the feature took (libcuda.so in the executable).
APR_SHA=$(git rev-parse --short=9 HEAD)
mkdir -p "$CACHE/target-cuda"
$DOCKER run --rm \
-v "$GITHUB_WORKSPACE:/workspace" \
Expand Down Expand Up @@ -200,7 +217,7 @@ jobs:
# NIGHTLY_BINS is unset only when the build step died before deriving
# it; build.outcome is then failure and record returns build-failed
# without reading --bins, so "none" is never probed as a bin.
python3 scripts/nightly_manifest.py record --target "${{ matrix.target }}" --sha "$GITHUB_SHA" \
python3 scripts/nightly_manifest.py record --target "${{ matrix.target }}" --sha "$NIGHTLY_SHA" \
--bins "${NIGHTLY_BINS:-none}" --bin-dir "target/${{ matrix.target }}/release" --dist dist \
--variants apr:cuda \
--build-outcome "${{ steps.build.outcome }}" --version "$V" > "dist/fragment-${{ matrix.target }}.json"
Expand All @@ -214,68 +231,18 @@ jobs:
path: dist/*
if-no-files-found: warn

# ── Build apr: macOS aarch64 natively on mini-m4 (#3204) ──
# Host-native: there is no docker on macOS and no glibc floor to pin, so the
# toolchain is rust-toolchain.toml via the runner's rustup, as in ci.yml's
# mac-check. A LITERAL runs-on list, not fromJSON, so check_runner_labels.sh
# sees the apple-silicon label. Gated on the same `gate` decision as the Linux
# matrix (car's `check-activity` job does not exist on this branch -- folded
# into `gate`'s reused/build/red-ci/ci-pending verdict, #4189). One box with
# 16 GB: this leg must not hold the Linux nightly hostage -- `publish-darwin`
# below only uploads when this job is green, and the darwin tarball is
# simply absent that night otherwise; it never blocks or is blocked by
# `publish` (that job's per-arch manifest tracks Linux targets only).
build-darwin:
needs: gate
if: needs.gate.outputs.decision == 'build'
name: apr aarch64-apple-darwin on mini
runs-on: [self-hosted, macOS, ARM64, apple-silicon, m4, mini]
timeout-minutes: 90
env:
CARGO_INCREMENTAL: 0
CARGO_TERM_COLOR: never
T: aarch64-apple-darwin
steps:
- name: Checkout
uses: actions/checkout@v7

- name: Build apr (host-native, pinned toolchain)
run: |
set -euo pipefail
rustup show active-toolchain
cargo build --release --locked -p apr-cli --target "$T"
"target/$T/release/apr" --version

- name: Package
run: |
set -euo pipefail
ARCHIVE="apr-$T"
rm -rf "$ARCHIVE" "$ARCHIVE.tar.gz" "$ARCHIVE.tar.gz.sha256"
mkdir -p "$ARCHIVE"
cp "target/$T/release/apr" "$ARCHIVE/"
for f in README.md LICENSE LICENSE-MIT COPYING UNLICENSE; do
if [ -f "$f" ]; then cp "$f" "$ARCHIVE/"; fi
done
tar czf "${ARCHIVE}.tar.gz" "$ARCHIVE"
shasum -a 256 "${ARCHIVE}.tar.gz" > "${ARCHIVE}.tar.gz.sha256"
echo "Packaged: ${ARCHIVE}.tar.gz ($(du -h "${ARCHIVE}.tar.gz" | cut -f1))"

- name: Upload artifact
uses: actions/upload-artifact@v7
with:
name: apr-aarch64-apple-darwin
path: |
apr-aarch64-apple-darwin.tar.gz
apr-aarch64-apple-darwin.tar.gz.sha256

# ── Publish per arch, then the manifest ──────────────────
publish:
needs: [gate, build]
if: always() && needs.gate.result == 'success'
runs-on: [self-hosted, Linux, X64, clean-room]
env:
NIGHTLY_SHA: ${{ needs.gate.outputs.sha }}
steps:
- name: Checkout
uses: actions/checkout@v7
with:
ref: ${{ needs.gate.outputs.sha }}

# A runner that died uploaded nothing; merge records that arch red, so
# an empty download must not stop the other arch from publishing.
Expand All @@ -294,7 +261,7 @@ jobs:
mkdir -p dist
bash scripts/nightly_fetch_manifest.sh prev-manifest.json
python3 scripts/nightly_manifest.py merge --prev prev-manifest.json --fragments dist \
--sha "$GITHUB_SHA" --decision "${{ needs.gate.outputs.decision }}" \
--sha "$NIGHTLY_SHA" --decision "${{ needs.gate.outputs.decision }}" \
--run-id "$GITHUB_RUN_ID" --run-url "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" \
> nightly-manifest.json
cat nightly-manifest.json
Expand All @@ -304,43 +271,15 @@ jobs:
- name: Publish green arches, then nightly-manifest.json
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: python3 scripts/nightly_manifest.py publish --manifest nightly-manifest.json --dist dist --sha "$GITHUB_SHA"
run: python3 scripts/nightly_manifest.py publish --manifest nightly-manifest.json --dist dist --sha "$NIGHTLY_SHA"

- name: Fail the run on a red verdict (the arbiter tickets it from the manifest)
env:
DECISION: ${{ steps.merge.outputs.decision }}
run: |
echo "### nightly: **$DECISION** at \`${GITHUB_SHA:0:9}\`" >> "$GITHUB_STEP_SUMMARY"
echo "### nightly: **$DECISION** at \`${NIGHTLY_SHA:0:9}\`" >> "$GITHUB_STEP_SUMMARY"
case "$DECISION" in
built|reused) exit 0 ;;
ci-pending) echo "::warning::required checks not finished on ${GITHUB_SHA:0:9}; last green kept"; exit 0 ;;
*) echo "::error::nightly $DECISION at ${GITHUB_SHA:0:9}; see nightly-manifest.json"; exit 1 ;;
ci-pending) echo "::warning::required checks not finished on ${NIGHTLY_SHA:0:9}; last green kept"; exit 0 ;;
*) echo "::error::nightly $DECISION at ${NIGHTLY_SHA:0:9}; see nightly-manifest.json"; exit 1 ;;
esac

# ── Attach the darwin asset to the nightly release ────────
# `publish`'s manifest system (scripts/nightly_manifest.py) tracks the Linux
# targets only; it neither knows about nor deletes unlisted assets, so this
# upload is additive and independent -- a red/skipped darwin leg leaves the
# Linux assets alone, and a red Linux leg (this job needs `publish` only so
# the "nightly" release already exists) does not block the mac asset.
publish-darwin:
needs: [gate, build-darwin, publish]
if: always() && needs.gate.outputs.decision == 'build' && needs.build-darwin.result == 'success'
runs-on: [self-hosted, Linux, X64, clean-room]
steps:
- name: Download darwin artifact
uses: actions/download-artifact@v4
with:
name: apr-aarch64-apple-darwin
path: dist-darwin

- name: Upload to the nightly release
uses: softprops/action-gh-release@v3
with:
tag_name: nightly
name: Nightly Build
prerelease: true
make_latest: false
files: |
dist-darwin/apr-aarch64-apple-darwin.tar.gz
dist-darwin/apr-aarch64-apple-darwin.tar.gz.sha256
15 changes: 13 additions & 2 deletions .github/workflows/pr-review-quorum.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@
# scripts/check_receipt_gate_base_owned.sh records rather than hides.
#
# SECURITY (pull_request_target has a write-capable token by default):
# - permissions are pinned to contents: read; nothing here writes.
# - permissions are pinned to contents: read; nothing here writes. The one
# job adds checks: read, job-scoped (B1, #4512): the receipt signature is a
# check run on the head sha, never a commit, and Arm 4 reads it back.
# - no step checks out or executes head code; the head's tree is read with
# `git archive <sha> evidence/pr-review/<pr>` only.
# - the fork guard on the signer (ci.yml pr-review-sign) is unchanged.
Expand Down Expand Up @@ -61,6 +63,10 @@ jobs:
# pin was inherited, not derived.
runs-on: [self-hosted, Linux, clean-room]
timeout-minutes: 20
# B1 (#4512): read the pr-review-signature check run. Job-scoped, read-only.
permissions:
contents: read
checks: read
if: github.event_name == 'pull_request_target' || github.event_name == 'merge_group'
env:
# pull_request_target carries the PR; merge_group carries the queue ref
Expand Down Expand Up @@ -129,7 +135,7 @@ jobs:
bash scripts/install_pr_review_tools.sh "$RUNNER_TEMP/pr-review-tools/bin"
echo "$RUNNER_TEMP/pr-review-tools/bin" >> "$GITHUB_PATH"

- name: "Arm 4 case table: 23 rows, both polarities of the cutoff and the patch-id binding"
- name: "Arm 4 case table: 33 rows, both polarities of the cutoff, the patch-id binding and the check-run signature"
shell: bash
run: setsid --wait bash scripts/check_pr_review_arm4.sh --self-test

Expand All @@ -138,4 +144,9 @@ jobs:
env:
PR_NUMBER: ${{ steps.resolve.outputs.pr }}
PR_HEAD_SHA: ${{ steps.resolve.outputs.head }}
# B1: an unsigned-on-disk receipt is judged by the pr-review-signature
# check run on the PR head. The head is resolved above, so Arm 4 needs
# no pulls API read; the check-runs read uses this token (checks: read).
ARM4_PR_HEAD_SHA: ${{ steps.resolve.outputs.head }}
GH_TOKEN: ${{ github.token }}
run: setsid --wait bash scripts/check_pr_review_arm4.sh
51 changes: 37 additions & 14 deletions ci/sections.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2892,8 +2892,11 @@ jobs:
runs-on: [self-hosted, Linux, clean-room] # arch-neutral: intel, yoga or gx10 (#3100)
timeout-minutes: 15
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
# B1: the signature is posted as a check run, so the one write this section needs
# is checks. It no longer pushes, so contents is read-only.
permissions:
contents: write
contents: read
checks: write
steps:
- name: Checkout the PR head
uses: actions/checkout@v7
Expand All @@ -2919,48 +2922,68 @@ jobs:
shell: bash
run: bash scripts/pr_review_sign_receipt.sh --self-test

- name: Sign this PR's receipt, if it has an unsigned one
# B1 (operator 2026-09-28 14:52Z): the signature is a CHECK RUN on the head sha,
# never a commit. A commit by the signer made the PR head a bot commit, and under
# the approval policy a bot-actor head is action_required with 0 jobs and never
# fires pull_request_target (Arm 4) at all. The trusted comment binds
# `pr= head= pid=` under the signature; Arm 4 requires head= to be the PR head it
# judges and pid= the diff it computes, so a signature cannot be replayed onto
# another head or another diff.
- name: Sign this PR's unsigned receipt(s), bound to the head sha
shell: bash
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
PR_REVIEW_SIGNING_KEY_B64: ${{ secrets.PR_REVIEW_SIGNING_KEY_B64 }}
run: |
set -euo pipefail
# Sign exactly the head the event names, not whatever the branch moved to.
git checkout -q --detach "$PR_HEAD_SHA"
root="evidence/pr-review/$PR_NUMBER"
sigs="$RUNNER_TEMP/pr-review-signatures.json"
printf '{}' > "$sigs"
if [ ! -d "$root" ]; then
echo "no receipt directory $root — nothing to sign."
echo "The review is still owed; the S13.11 shadow lane records its absence."
exit 0
fi
signed_any=0
# A receipt with a document and no signature is the only thing this touches.
# A receipt with a document and no COMMITTED signature is the only thing this touches.
while IFS= read -r rcpt; do
d=$(dirname "$rcpt")
[ -f "$rcpt.minisig" ] && { echo "already signed: $d"; continue; }
[ -f "$rcpt.minisig" ] && { echo "already signed (committed .minisig): $d"; continue; }
if [ -z "${PR_REVIEW_SIGNING_KEY_B64:-}" ]; then
echo "::error::$d carries an UNSIGNED receipt and PR_REVIEW_SIGNING_KEY_B64 is empty."
echo "::error::A receipt that cannot be signed cannot be verified by Arm 4."
exit 1
fi
bash scripts/pr_review_sign_receipt.sh "$d"
pid=$(jq -r '.predicate.diff_patch_id // "none"' "$rcpt")
PR_REVIEW_SIGN_BIND="pr=$PR_NUMBER head=$PR_HEAD_SHA pid=$pid" \
bash scripts/pr_review_sign_receipt.sh "$d"
jq --arg k "$(basename "$d")" --rawfile v "$rcpt.minisig" '.[$k] = $v' "$sigs" > "$sigs.new"
mv "$sigs.new" "$sigs"
# The signature leaves as a check run; it is never staged, committed or pushed.
rm -f "${rcpt:?}.minisig"
signed_any=1
done < <(find "$root" -name receipt.intoto.jsonl -type f)
echo "signed_any=$signed_any" >> "$GITHUB_ENV"

- name: Commit the signature back to the PR branch
- name: Post the signature as a check run on the head sha (never a commit)
if: env.signed_any == '1'
shell: bash
env:
PR_HEAD_REF: ${{ github.event.pull_request.head.ref }}
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
git config user.name "aprender-pr-review-signer"
git config user.email "noreply@anthropic.com"
git add evidence/pr-review
git commit -m "chore(pr-review): sign this PR's receipt (PR-REVIEW-SKILL-002 v2 §4.3 CI signer)"
# Never --force. A race with the author's own push fails this step and the
# next run signs it; a force-push would silently discard their commit.
git push origin "HEAD:$PR_HEAD_REF"
sigs="$RUNNER_TEMP/pr-review-signatures.json"
jq -n --arg h "$PR_HEAD_SHA" --rawfile t "$sigs" '{
name: "pr-review-signature", head_sha: $h,
status: "completed", conclusion: "success",
output: { title: "PR-REVIEW-SKILL-002 v2 §4.3 receipt signature(s)",
summary: "minisign signatures keyed by receipt directory; the trusted comment binds pr= head= pid=. Verified by Arm 4 in the pr-review-quorum check.",
text: $t } }' \
| gh api -X POST "repos/$GITHUB_REPOSITORY/check-runs" --input - --jq '"posted check run \(.id) on \(.head_sha)"'

# ---------------------------------------------------------------------------
# RECEIPT PRESENCE, AS A FAST REQUIRED CHECK.
Expand Down
Loading
Loading