Repository navigation
release: v0.1.0 daemon hardening, wake recovery, and source/Homebrew packaging - #16
Merged
Merged
Conversation
- Daemon (src/): hardened lifecycle — home dir resolution, safer hidutil mapping ownership, atomic apply/rollback, panic-safe cleanup, tighter error handling across config/engine/event_tap, and removed unsafe surface from macos_ffi. - Installers (install.sh, uninstall.sh, install-binary.sh, scripts/lib.sh, scripts/test_lib.sh): fail-closed plist identity checks, race-protected rollback, helper-based path/sed/launchctl utilities, and shell-level test coverage for the install flow. - Raycast extension (raycast-extension/): dependency updates and service/client compatibility adjustments for the hardened daemon. - CI (.github/workflows/release.yml): read-only tagged-release artifact pipeline (no contents: write). - Docs (README.md, RELEASE_SETUP.md, bundle/Info.plist): updated for the new bundle id and release flow.
Adds the v0.1.0 release candidates for source distribution and Homebrew
source-build formula. Layered changes are committed together to preserve
the cross-cutting README/CHANGELOG/RELEASING updates.
Wake recovery (src/):
- src/wake.rs: ReapplyPolicy (pure debounce seam), reapply_remappings,
publish_applied_mappings, and install_power_watcher (production glue
on the main CFRunLoop). Serializes live hidutil transactions through
HIDUTIL_TRANSACTION_LOCK so wake reapplies cannot interleave with
panic/signal cleanup; the panic hook uses try_lock and falls back to
durable state-file reconciliation when the lock is held or poisoned.
- src/main.rs: SHUTTING_DOWN flag, transaction lock, panic-cleanup
helper extracted for testing, and install_power_watcher wired before
event_tap::run.
- src/macos_ffi.rs: PowerWatcher wraps the IOKit system-power lifecycle
per Apple QA1340 with deterministic Drop ordering; CFRunLoopTimer
drives the one-shot debounce. Both extern "C" trampolines catch
panics so panic-across-FFI cannot abort the process.
- src/hidutil.rs: with_isolated_state_test pub(crate) seam for wake.rs
tests; AppliedMappings derives PartialEq/Eq for assertion clarity.
- src/{config,engine}.rs: minor wiring updates.
Source/Homebrew packaging (scripts/, packaging/, rust-toolchain.toml,
bundle/AppIcon-1024.png, install.sh, .gitignore):
- scripts/package.sh: deterministic source + binary archive builder
with isolated CARGO_TARGET_DIR, fresh-private mktemp output under
validated owned parent, absolute-path shell utilities, source/binary
archive byte-identity reproducibility check via dedicated run-2
parent, release-mode hermetic env -i build with private
HOME/CARGO_HOME and --frozen, deterministic manifest bound to the
validated commit SHA, and an explicit REHEARSAL vs RELEASE marker.
- scripts/version-sync.sh: validates Cargo.toml/Info.plist/CHANGELOG/
binary/tag version surfaces against the canonical Cargo version.
- packaging/homebrew/: Formula template (switcheroo.rb.tpl) with
source-build cargo install, switcheroo.rb service do block, sample
config under etc/, ad-hoc signing, and Accessibility caveats;
render_formula.sh builds the production Formula from strict-semver
version + 64-hex SHA-256 (URL constructed internally) with a
rehearsal-mode safe-copy path and explicit DO NOT PUBLISH marker.
- rust-toolchain.toml: pins rustc 1.95.0 for reproducible builds; CI
and local release builds resolve to this toolchain.
- bundle/AppIcon-1024.png: 1024x1024 PNG master used by both the
packaging path and (via sips/iconutil) the install.sh fallback.
- install.sh: --locked build, fail-closed binary-version stamping
of Info.plist from built binary --version (strict-semver verified
against cargo metadata, mismatch aborts).
- .gitignore: dist/, dist-parent/, rendered Homebrew formula.
Documentation (README.md, CHANGELOG.md, RELEASING.md):
- README.md: Homebrew source-build is now Option 0 (recommended public
path); binary archive Option A demoted with explicit nonpublic-
distribution warnings (ad-hoc, unnotarized, integrity-only checksums).
- CHANGELOG.md: 0.1.0 entry with Added, Security, Known Limitations.
- RELEASING.md: maintainer release runbook (version bump, gates,
Homebrew rehearsal, GitHub Actions release workflow, publication
allowlist, later authorized actions, known gaps).
CI hardening (.github/workflows/ci.yml, .github/workflows/release.yml):
- ci.yml: macos-14 pinned, full-SHA-pinned actions, persist-credentials
false, --locked clippy/test/build-all-targets/build-all-features
gates, SHA-pinned cargo-deny advisories check, shellcheck + npm
install audit + raycast lint:ci gate (eslint + prettier).
- release.yml: read-only tagged-release artifact pipeline (contents:
read only), dispatch-from-main gate, validated semver tag with
main-ancestor commit, version surfaces verified at the validated
SHA, scripts/package.sh release mode invoked with the validated
commit SHA, private-validation-artifacts upload only (no Release).
Raycast extension (raycast-extension/):
- lint:ci script added (eslint + prettier), explicit lint workflow,
service.ts/service-pure.mjs/service.test.mjs split for testable
pure helpers with full Homebrew identity validation, exact
executable matching against the two official opt prefixes,
pre-kickstart re-verification, and 22 service unit tests.
- README, eslint.config.js, refreshed command icon, dependency
updates and client/service compatibility for the hardened daemon.
Test gates (carried forward per session):
- cargo fmt --check, clippy --locked --all-targets --all-features,
test --locked (126/126), build (all-targets + all-features).
- shellcheck (lib, test_lib, package, version-sync, install,
install-binary, uninstall, render_formula) clean.
- scripts/test_lib.sh 24/24, scripts/version-sync.sh clean.
- actionlint and zizmor (11 suppressions) clean.
- package.sh dry-run + full rehearsal + --verify-reproducibility
byte-identity.
- Raycast eslint/prettier/tsc/build/--test 22/22 + npm audit 0.
Public distribution is source-only: prebuilt binary archives remain
unnotarized/ad-hoc-signed CI validation artifacts (never attached to
a public GitHub Release); the only public install path is the
Homebrew source-build Formula (post-merge: compute the real SHA-256
of the v0.1.0 tag archive and render the production Formula).
Actual v0.1.0 tag and Formula checksum are NOT created in this
commit — they require the authorized merge-to-main step.
The pinned SHA of `dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772`
(`v1`) declares `toolchain` as `required: true` and does NOT auto-detect
from `rust-toolchain.toml`. The previous commit added `rust-toolchain.toml`
to the repo but did not pass an explicit `toolchain:` input, so all Rust
gates (Format / Clippy / Test / Build / Package dry-run) failed in
`fix-wake-remap-reapply` CI with the action's parse step:
'toolchain' is a required input
Adding `toolchain: 1.95.0` (matching the pinned `rust-toolchain.toml`)
to every `dtolnay/rust-toolchain` invocation in `ci.yml` (6 sites)
and `release.yml` (4 sites) restores the gates. The value matches the
pinned rustc; either source (`rust-toolchain.toml` or `toolchain:`
input) overrides the other, and the explicit input is the safer of the
two because it survives a future removal of the file.
Verified locally with actionlint (0) and zizmor (0, 11 suppressed).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Lands the v0.1.0 candidate for source distribution and the Homebrew
source-build Formula. The previous commit
be8c333(daemon hardening,installers, Raycast ext, CI) is already on
main; this PR layers thewake-from-sleep recovery, source/Homebrew packaging, and Raycast
identity/dep updates on top.
What this PR adds
Wake-from-sleep recovery (
src/wake.rs,src/main.rs,src/macos_ffi.rs,src/hidutil.rs): IOKitIORegisterForSystemPowerwatcher with a ~2 s debounced one-shot timer (
CFRunLoopTimer) thatre-applies Switcheroo's owned modifier remaps through the existing
idempotent hidutil path. All live hidutil transactions are serialized
through
HIDUTIL_TRANSACTION_LOCK; the panic hook usestry_lockand falls back to the durable state file when the lock is held or
poisoned.
extern "C"trampolines catch panics.PowerWatchercleanup follows Apple QA1340's deterministic Drop order.
Source + Homebrew packaging (
scripts/package.sh,scripts/version-sync.sh,packaging/homebrew/,rust-toolchain.toml,bundle/AppIcon-1024.png,install.sh):deterministic source/binary archive builder with isolated
CARGO_TARGET_DIR, fresh-private mktemp output, hermeticrelease-mode
env -ibuild with privateHOME/CARGO_HOMEand--frozen, source/binary byte-identity reproducibility check,deterministic manifest bound to the validated commit SHA. Homebrew
Formula template builds from source via
cargo install *std_cargo_args;render_formula.shbuilds the productionFormula from strict-semver version + 64-hex SHA-256 (URL
constructed internally) with a rehearsal-mode safe-copy path and
DO NOT PUBLISHmarker.rust-toolchain.tomlpins rustc 1.95.0.CI hardening (
.github/workflows/ci.yml,.github/workflows/release.yml):macos-14-pinned runners,full-SHA-pinned actions,
persist-credentials: false,--lockedclippy/test/build-all-targets/build-all-features gates,SHA-pinned
cargo-denyadvisories check, shellcheck + npm audit +raycast
lint:ci(eslint + prettier).release.ymlis aread-only tagged-release artifact pipeline (
contents: readonly)gated on dispatch-from-main, validated semver tag with main-ancestor
commit, version surfaces verified at the validated SHA, and
scripts/package.shrelease mode invoked with the validated commitSHA.
private-validation-artifactsupload only — no GitHubRelease is created.
Raycast extension (
raycast-extension/):lint:ciscriptadded (
eslint+prettier), service.ts / service-pure.mjs /service.test.mjs split for testable pure helpers with full Homebrew
identity validation, exact executable matching against the two
official
opt_prefixpaths, pre-kickstart re-verification, and 22service unit tests.
Documentation (
README.md,CHANGELOG.md,RELEASING.md):Homebrew source-build is now Option 0 (recommended public path);
binary archive Option A demoted with explicit nonpublic-distribution
warnings.
CHANGELOG.mdhas the 0.1.0 entry (Added / Security /Known Limitations).
RELEASING.mdis the maintainer release runbook(version bump, gates, Homebrew rehearsal, GitHub Actions release
workflow, publication allowlist, later authorized actions, known
gaps).
Test gates (carried forward, focused re-verification 2026-09-06)
cargo fmt --checkcargo clippy --locked --all-targets --all-features -- -D warningscargo test --lockedcargo build --locked --all-targets --all-featuresshellcheck(lib, test_lib, package, version-sync, install, install-binary, uninstall, render_formula)scripts/test_lib.shscripts/version-sync.shactionlintzizmor .package.shdry-run + full rehearsal +--verify-reproducibilitybyte-identityeslint+prettier+tsc --noEmit+npm run build+node --testnpm audituses:)contents: readonly)Distribution policy (explicit, per RELEASING.md)
CI for validation only — they are unnotarized, ad-hoc-signed
(
self-consistency only, NOT authenticity), and must NOT beattached to a public GitHub Release. Co-hosted checksums provide
integrity only, not authenticity. The sole public install path is
the Homebrew source-build Formula.
extension
package.jsonprepublishOnlydeliberately fails; nopublish script exists.
v0.1.0tag and Formula checksum are NOT created in thisPR. They require the authorized post-merge step (compute the
SHA-256 of the public tag archive bytes, render
packaging/homebrew/switcheroo.rbviapackaging/homebrew/render_formula.sh, create themitchelljphayes/homebrew-switcherootap, pushFormula/switcheroo.rb).Manual verification notes
verification on the maintainer's machine after sleep/wake cycles),
but the current daemon install/version was not independently
verified end-to-end by this PR's author on a fresh account. The
IOKit lifecycle follows Apple QA1340 and the unit tests cover the
policy/transaction/rollback edges, but a real-world
install → sleep → wake → shutdown cycle on a clean account is
recommended before tagging.
ray lintauthor check is externaland not in scope; the repo-controlled quality gates
(
lint:ci=eslint+prettier,tsc,build,node --test, audit) all pass on the committed code.Out of scope / follow-ups
RELEASE_SETUP.mdprerequisites (release environment, env-scopedsentinel secret, branch protection, immutable
v*tag ruleset,Actions SHA-pin policy, credential incident resolution) are external
governance actions — not code changes — and remain out of scope for
this PR.
deferred (prebuilt binary remains CI-only).
IOServiceAddMatchingNotification) isdeferred; the
ReapplyPolicy/reapply_remappingsseam isreusable for a future
DeviceTrigger.Reviews / security
review.md): PASS for v0.1.0 sourcerelease + Homebrew source formula. Zero code-level release blockers.
security-review.md): no Critical / High /Medium code-level blockers for commit, push, PR. Prior High cleanup
hazard verified fixed. All public-distribution surfaces are
fail-closed.
Files
34 files changed, 4836 insertions(+), 298 deletions(-) in the new
commit
25ad011on top ofbe8c333..githooks/pre-commit,.githooks/post-checkout, and.githooks/pre-commit-user(GitButlerstate) are intentionally NOT included in this PR.