Skip to content

fix(release): re-codesign macOS binaries after strip — v0.5.73 - #63

Merged
githubrobbi merged 1 commit into
mainfrom
fix/macos-codesign-post-strip
Apr 25, 2026
Merged

fix(release): re-codesign macOS binaries after strip — v0.5.73#63
githubrobbi merged 1 commit into
mainfrom
fix/macos-codesign-post-strip

Conversation

@githubrobbi

Copy link
Copy Markdown
Collaborator

Summary

Fixes a release-only bug that ships broken macOS arm64 binaries to every user on macOS 26+.

[profile.release].strip = "symbols" in Cargo.toml strips the Mach-O symbol table after the linker has emitted an ad-hoc (linker-signed) CodeDirectory. Strip changes the on-disk hash the CodeDirectory was computed over, leaving the embedded signature internally inconsistent with the file. macOS 26+'s hardened taskgated then refuses to launch the binary at exec time:

SIGKILL (Code Signature Invalid)
namespace=CODESIGNING
indicator="Taskgated Invalid Signature"

In v0.5.72 this hits uffsmcp and uffsd deterministically (downloaded from the GitHub Release ZIP); uffs and uffs_mft survive only by binary-layout chance — a fragile guarantee that wouldn't hold on the next rebuild.

Fix

Add a Re-codesign macOS binaries (post-strip) step to .github/workflows/release.yml that re-stamps the ad-hoc signature with codesign --force --sign - on every shipping apple-darwin binary after cargo build --release finishes. Each re-signed binary is then verified with codesign --verify --verbose=2 so a regression here fails the workflow loudly instead of shipping broken artifacts.

The step is gated on contains(matrix.target, 'apple-darwin') so Windows / Linux artifact paths are untouched.

We deliberately stay ad-hoc: there is no Apple Developer ID for this project, so the resulting binaries are still unnotarized and users see the standard "open anyway" Gatekeeper prompt on first launch. But they no longer get killed at exec time.

Workaround for users on v0.5.72

codesign --force --sign - ~/bin/uffsmcp ~/bin/uffsd

Re-signs in place; macOS picks up the refreshed CodeDirectory on the next exec and the binaries launch normally.

Validation

  • cargo check --workspace --all-targets — clean
  • cargo fmt --all --check — clean
  • cargo clippy --workspace --all-targets -- -D warnings — clean
  • ✅ Pre-push gate (fmt, vet, typos, reuse, cargo-check, lint-prod, lint-tests, lint-ci, rustdoc, doc-tests, tests, smoke, deny, check-windows) — green, 119 s
  • ✅ Locally verified workaround: codesign --force --sign - on the v0.5.72 downloaded ~/bin/uffsmcp and ~/bin/uffsd makes both launch successfully (uffsmcp 0.5.72).

Release flow

After merge to main:

  1. auto-tag-release.yml detects the [workspace.package] version change 0.5.72 → 0.5.73.
  2. Dispatches release.yml with version=v0.5.73.
  3. release.yml builds, strips (per [profile.release]), then re-codesigns the four shipping binaries on aarch64-apple-darwin, and verifies the signature.
  4. Publishes the GitHub Release with all three platform artifacts.

Scope

No source code changes — release-pipeline only. Recommended upgrade for every Mac user on macOS 26+.

`[profile.release].strip = "symbols"` in Cargo.toml strips the Mach-O
symbol table AFTER the linker has emitted an ad-hoc (linker-signed)
`CodeDirectory`.  Strip changes the on-disk hash the CodeDirectory was
computed over, leaving the embedded signature internally inconsistent
with the file.  macOS 26+'s hardened taskgated then refuses to launch
the binary at exec time with:

    SIGKILL (Code Signature Invalid)
    namespace=CODESIGNING
    indicator="Taskgated Invalid Signature"

In v0.5.72 this hit `uffsmcp` and `uffsd` deterministically; `uffs`
and `uffs_mft` survived by binary-layout chance — a fragile guarantee
that wouldn't hold on the next rebuild.

Fix: add a `Re-codesign macOS binaries (post-strip)` step to
`.github/workflows/release.yml` that re-stamps the ad-hoc signature
with `codesign --force --sign -` on every shipping `apple-darwin`
binary AFTER `cargo build --release` finishes (and therefore after
strip has run).  The step is gated on `contains(matrix.target,
'apple-darwin')` so Windows / Linux artifact paths stay untouched.
Each re-signed binary is then verified with `codesign --verify
--verbose=2` so a regression here fails the workflow loudly instead
of shipping broken artifacts.

We deliberately stay ad-hoc: there is no Apple Developer ID for this
project, so the resulting binaries are still unnotarized and users
will see the standard "open anyway" Gatekeeper prompt on first
launch.  But they no longer get killed at exec time.

Workaround for users still on a v0.5.72 download:

    codesign --force --sign - ~/bin/uffsmcp ~/bin/uffsd

Re-signs in place; macOS picks up the refreshed `CodeDirectory` on
the next exec and the binaries launch normally.

Bumps workspace version 0.5.72 → 0.5.73.  On merge to `main`,
`auto-tag-release.yml` will dispatch `release.yml` to rebuild and
publish properly-signed binaries for x86_64-pc-windows-msvc,
aarch64-apple-darwin, and x86_64-unknown-linux-gnu.

Validation:
* `cargo check --workspace --all-targets` — clean.
* `cargo fmt --all --check` — clean.
* `cargo clippy --workspace --all-targets -- -D warnings` — clean.

No source code changes; release-only fix.  Recommended upgrade for
every Mac user on macOS 26+.
@githubrobbi
githubrobbi enabled auto-merge (squash) April 25, 2026 21:05
@githubrobbi
githubrobbi merged commit bf8681c into main Apr 25, 2026
16 checks passed
@githubrobbi
githubrobbi deleted the fix/macos-codesign-post-strip branch April 25, 2026 21:18
githubrobbi added a commit that referenced this pull request Apr 25, 2026
…space root (#66)

Phase R2 of `docs/architecture/release-automation-plan.md`.  Adds a
`git-cliff` configuration template at the workspace root that produces
Keep-a-Changelog-compatible output from conventional-commit subjects.
Stays purely additive; `CHANGELOG.md` is NOT touched (per plan §R2 step 4).

## What changed

- **`cliff.toml`** — new file (~195 lines including documentation).
  Configures `git-cliff` to:
  - Render the existing UFFS `CHANGELOG.md` header verbatim (SPDX,
    Keep-a-Changelog + SemVer cite).
  - Use a Tera body template that emits `## [X.Y.Z] - YYYY-MM-DD`
    followed by `### Section` blocks per commit type group.
  - Map the 11 Conventional Commits types to changelog sections,
    matching `.github/workflows/commitlint.yml`'s regex AND
    `CONTRIBUTING.md → Commit message conventions`:
    - `feat:` → `### Added`
    - `feat!:` / `fix!:` / `BREAKING CHANGE:` → `### Breaking Changes`
    - `fix(security):` / `chore(security):` / `security:` → `### Security`
      (the third tolerates the non-standard `security:` prefix observed
      in PRs #31, #33, #34 during R1a's observation window)
    - `fix:` → `### Fixed`
    - `perf:` → `### Performance`
    - `refactor`, `docs`, `test`, `build`, `ci`, `chore`, `style`,
      `revert` → suppressed
  - `filter_unconventional = true`: drop commits that don't match
    Conventional Commits format outright (so the malformed
    `chore(dev + ci): ...` from PR #41 doesn't sneak under
    `### Changed`).
  - `tag_pattern = "v[0-9]+\\.[0-9]+\\.[0-9]+"` matching what
    `auto-tag-release.yml` emits today and what release-plz will emit
    in R4.  Same anchor in both regimes — no migration needed.
  - `[remote.github]` block points at `skyllc-ai/UltraFastFileSearch`
    so the footer comparison links resolve correctly.

- **`docs/architecture/release-automation-baseline.md`** — new §8
  addendum captures R2 validation:
  - Output statistics (508 lines generated, 18 versions, 18 footer
    links — vs. 907 lines / 9 versions in the hand-curated original).
  - Type-mapping spot-checks against real commits in history.
  - Notes on two iteration issues caught and resolved during template
    tuning: extra blank line after `## [version]` headers (fixed via
    Tera whitespace controls), and duplicate PR links from manually
    rendering `[#NN](URL)` on top of the squash-subject's existing
    `(#NN)` (fixed by removing the manual link — GFM auto-links).

- **`docs/architecture/release-automation-plan.md`** — dashboard
  flips R2 row to 🟡 with date and pointer to this PR.

## Validation

- ✅ `cargo install git-cliff --locked` — installed `git-cliff 2.12.0`.
- ✅ `git-cliff --config cliff.toml -o /tmp/uffs-cliff-full.md` — full
  history renders cleanly; 508 lines covering all 18 tagged releases
  from `v0.5.1` to `v0.5.73` with correct section grouping.
- ✅ `git-cliff --config cliff.toml --unreleased` — produces a clean
  empty placeholder (every commit since v0.5.73 is `chore:`, all
  suppressed by parsers).
- ✅ Spot-checked type → section mapping for `feat:` (#45), `fix:`
  (#63), `perf:` (#38), `chore(security):` parser registered, and
  `security:` non-standard prefix correctly captured under Security
  (#31, #33, #34).
- ✅ Verified `chore(dev + ci):` (PR #41, malformed scope) is dropped
  by `filter_unconventional = true` — `grep -i "dev + ci"` against
  the generated output returns no match.
- ✅ Footer comparison links: 17 use `compare/{prev}...{ver}`, 1 (the
  initial release v0.5.1) uses `releases/tag/{ver}` — correct
  Keep-a-Changelog convention.

## Why this PR is purely additive

Per plan §R2 step 4: **R2 does NOT overwrite `CHANGELOG.md`**.  The
hand-maintained file stays exactly as-is.  Phase R3-R4's release-plz
will write the **next** release section above the existing
`## [0.5.73]` header, leaving all prior entries intact.  This means
the loss of prose richness (multi-paragraph essays vs. single-line
bullets) only applies to entries written by automation FROM NOW ON —
historical entries are preserved verbatim.

## What this PR does NOT do

- Install `git-cliff` in CI — R2 is a per-developer tool.  Phase R3+
  release-plz embeds git-cliff natively as a library; no CI install
  step is added until then.
- Touch `CHANGELOG.md` — see above.
- Configure `release-plz.toml` — that's R3.

## Rollback

`git revert` this PR.  `git-cliff` installation is per-developer and
orthogonal; nothing in CI depends on `cliff.toml` yet.

## Refs

- Plan: `docs/architecture/release-automation-plan.md` Phase R2
- git-cliff docs: https://git-cliff.org/docs/configuration
- Tightly-coupled trio: `cliff.toml` + `.github/workflows/commitlint.yml` +
  `CONTRIBUTING.md` → "Commit message conventions" (adding a type means
  updating all three in the same PR)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant