ci: owned-Mac signing accepts only listed glaeda hooks; a refused attempt signs on Blacksmith in the same run
Leo, 2026-10-28, on the release signing jobs: "ok. idk what the "risk" was before but like.. we can just read the code and stuff. anyways, what about that signing on linux boxes thing? all those other ones basically yes but what do i need to do". Leo, 2026-10-24, on nightly freshness: "it is unacceptable to have nightlies >30min out of date. shouldn't be cancelling unless existing run would've literally been busted".
A signing job on an owned Mac needs the whole mini. Some runner directories still carry a glaeda hook from before the leftover-process check (teamleaderleo/glaeda#1738), and that hook can admit a signing job on a mini where an earlier job's processes are still running. glaeda's claim now names the hook that granted it (teamleaderleo/glaeda#1739, GLAEDA_HOOK_SHA256). This PR makes every owned-routed signing job accept only listed hooks, and has the same run sign on Blacksmith whenever the owned attempt is refused.
Claim gate. The first step, before checkout and before any secret:
- On an owned Mac it fails the job unless glaeda wrote
GLAEDA_JOB_CLAIM=exclusiveand aGLAEDA_HOOK_SHA256thatscripts/ci/glaeda-signing-hooks.txtlists. It fails closed on a missing or unlisted sha. - When it passes there, it sets the job output
claim=owned. signing-runner-claim.shchecks the same thing against the file.
Same-run fallback. Each owned-routed signing job gets a Blacksmith twin, <job>-blacksmith:
- The four jobs are nightly
sign-nightly-identity-legacy, releasebuild-sign-notarize, repairrepairand dev-dailysign. - The twin has the same steps, env, outputs, permissions, environment and concurrency, written as YAML aliases.
- It runs only on attempt 1, when the owned pick was offered, the owned job ran, and its claim never passed. That covers both cases: glaeda refused the job, or an unlisted hook admitted it.
- Every job that reads the signing job's result or outputs also needs the twin and reads it first.
- Nothing waits on a manual re-run.
Guard tests (tests/test_ci_signing_owned_mac.py):
- Twin shape: the twin exists and matches its job, its runs-on is Blacksmith only, its
ifis exact, and every reader also reads the twin. - Gate: it refuses
fb0442d3(the hook before the check), a missing sha, a prefix and junk, leavingclaimempty so the twin signs. It passes2695209cand every other listed hook withclaim=owned. - Hook list in step with glaeda: every listed sha is one that glaeda-disk pins in
CMUX_NEXT_DRAIN_HOOK_SHA256, at or after2695209c. It warns when glaeda's newest hook isn't listed yet; signing then stays on Blacksmith for those minis.
Before: a refused owned attempt failed the signing job, and only a re-run moved it to Blacksmith. A nightly's legacy sign could go stale meanwhile. After: the same run signs on Blacksmith.
Owned signing stays off (vars.CI_SIGN_OWNED=0) until this and teamleaderleo/glaeda#1739 land and the hook reaches the minis.
Risk: the four signing workflows change shape. Release, repair and dev-daily jobs keep their status, so a refused owned attempt still shows red in their run while the twin signs. The nightly job already has continue-on-error.
Also: the twins are added to .github/runners.json, the nightly step-coverage registry and the nightly guards. To keep nightly.yml under its size margin, publish-nightly's "Record nightly publication marker outcome" step moves, unchanged, into scripts/ci/nightly-steps/publish-tag-outcome.sh. It runs from .publish-tooling (the workflow commit) like the job's other moved steps.
Tests: the signing-owned-mac, keychain, Xcode-selection, artifact-retention, dev-daily dispatch, step-coverage, sign-runner, routing-table, size and universal-build guards pass locally, and actionlint is clean. Local reds that also fail on base: fleet_rule_lints, cmux_next_tour, sparkle_build_monotonic, delta_since_green, notarize_nightly_dmg, install_cmux_tui_client, ghostty_zig_version_sync, nightly_next_arm64_only.
Agent session: agent session
š¤ Generated with Claude Code