Skip to content

docs(release): recommend Homebrew on macOS; publish bundles, checksums, and tap formula - #30

Merged
HuiJun merged 2 commits into
mainfrom
devin/1786056455-macos-gatekeeper
Aug 6, 2026
Merged

docs(release): recommend Homebrew on macOS; publish bundles, checksums, and tap formula#30
HuiJun merged 2 commits into
mainfrom
devin/1786056455-macos-gatekeeper

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Aug 6, 2026

Copy link
Copy Markdown

Summary

The macOS "developer cannot be verified" prompt is quarantine, not a missing signature — this PR lands the mitigations that need no Apple credentials, makes Homebrew the recommended macOS install path (the maintainer-accepted stopgap), and records what notarization would cost.

Evidence for the root cause (measured on Linux with Go 1.25.0, cross-compiling cmd/sysml exactly as build-release does, parsing the Mach-O load commands):

Target LC_CODE_SIGNATURE CodeDirectory flags
darwin/arm64 present (57 KB, superblob 0xfade0cc0) 0x20002 = adhoc, linker-signed
darwin/amd64 absent n/a (x86-64 needs none)

Go's linker already ad-hoc signs darwin/arm64 when cross-compiling from Linux (golang/go#42684 reports the same flags=0x20002(adhoc,linker-signed) from codesign -dv), which is what Apple silicon requires to execute. What blocks users is com.apple.quarantine, set by the browser on the downloaded tarball and propagated to the extracted binaries. Adding codesign -s - in CI would therefore change nothing. curl/wget/go install/brew install never set the attribute, which is why those paths are prompt-free (Homebrew quarantines casks only — hence the cask-only --no-quarantine flag — not formulae).

What changed

  • README.md / docs/QUICKSTART.md: brew tap Open-MBEE/tap && brew install systemica is the recommended macOS path; direct curl download plus checksum-verify-then-xattr -d com.apple.quarantine is the documented fallback; go install for anyone with a Go toolchain. Also corrected: sysml-grpc was listed as a release binary, but build-release does not build it.
  • .circleci/config.yml (build-release): additive only — after the existing per-binary tarball loop, stage sysml + sysml-lsp under their plain names into systemica-<os>-<arch>.tar.gz/.zip, then sha256sum ./*.tar.gz ./*.zip > SHA256SUMS.txt. publish-github-release moves the checksum file into dist/release/ so ghr uploads it with the archives.
  • packaging/homebrew/Formula/systemica.rb: the formula the tap will carry — desc/homepage/license, on_macos/on_linux × on_arm/on_intel url + sha256, bin.install "sysml", "sysml-lsp", and a test do that asserts --version for both binaries and evaluates an expression (sysml -e '5 + 3'= 8, verified against a locally built binary).
  • scripts/render-homebrew-formula.sh <tag> [SHA256SUMS.txt]: substitutes the five per-release values from the release's SHA256SUMS.txt (downloading it if not passed), strips the maintainer header comment, and fails if any placeholder remains.
  • docs/MACOS_DISTRIBUTION.md: the decision record — options with cost/benefit, and a concrete "what the maintainer must provide" list for notarization.

Homebrew tap: what the maintainer must do

The documented brew commands do not work yet. Open-MBEE/homebrew-tap does not exist; nothing here creates or publishes it, and nothing was deployed anywhere. The docs say so explicitly in both README.md and docs/QUICKSTART.md. One-time setup (also in packaging/homebrew/README.md):

  1. Create a public repo named exactly Open-MBEE/homebrew-tap (the homebrew- prefix is what makes brew tap Open-MBEE/tap resolve).
  2. Render and commit the formula as Formula/systemica.rb:
    # in Systemica
    ./scripts/render-homebrew-formula.sh v0.3.0 > /tmp/systemica.rb
    # in homebrew-tap
    mkdir -p Formula && cp /tmp/systemica.rb Formula/systemica.rb
    git add Formula/systemica.rb && git commit -m "systemica 0.3.0" && git push
    The tag must be a release built after this PR lands, since only those carry the systemica-* bundles and SHA256SUMS.txt.
  3. brew tap Open-MBEE/tap && brew install --verbose systemica && brew test systemica && brew audit --strict --online Open-MBEE/tap/systemica.

Per release the artifact names are stable, so only five values change (version + four sha256): tag vX.Y.Z, let CircleCI publish, then in the tap run ./scripts/render-homebrew-formula.sh vX.Y.Z > Formula/systemica.rb, commit, push. This can be automated later — a tag-triggered job could render and push the tap commit — but that needs a token with write access to Open-MBEE/homebrew-tap as a CI secret, which is a maintainer decision and is deliberately not set up here.

Notarization (not implemented, decision recorded)

Needs an Apple Developer Program membership ($99/yr), a Developer ID Application cert (.p12 + password), an App Store Connect API key for notarytool, and a team ID — plus a macOS runner. CircleCI macOS VMs are 200 credits/min and unavailable on the Free plan (the 400k OSS credits are Linux/Arm/Docker only); GitHub Actions macos-latest is a standard runner and free for public repos, so the recommendation is a tag-triggered GHA job for the darwin artifacts. Stapling is impossible for bare executables per Apple's docs; a .pkg/.dmg would be needed for that.

Why the CI change is safe for tag builds

Existing per-binary archive names and the ghr upload are untouched, so current download links keep working. New archives are built from copies in a stage/ dir that is deleted before sha256sum runs; the for binary in sysml-* glob is evaluated before the new systemica-* files exist (and would not match them anyway). circleci config validate passes, and I dry-ran the exact shell of both jobs against dummy files, confirming the dist/release/ listing and that systemica-darwin-arm64.tar.gz contains sysml and sysml-lsp.

Untested / not verified

I am on Linux and could not run Gatekeeper, codesign, notarytool, or brew. Every macOS runtime claim is cited to Apple/Go/Homebrew docs; the Mach-O table above is the only thing measured directly. The formula was not installed or audited by Homebrew (no macOS host); the .circleci release path only runs on a v* tag and was not executed end-to-end.

Out of scope, mentioned only: Windows artifacts are unsigned too, so SmartScreen shows "Windows protected your PC". Documented, not fixed.

Verification — run locally; CI was NOT used (repo CI is down)

$ gofmt -l .
(no output)

$ go build ./... && go vet ./...
OK

$ go test ./...
ok  github.com/Open-MBEE/Systemica/examples ... all packages ok (no FAIL)

$ go test -race ./...
ok  .../internal/core/libs      5.247s
ok  .../internal/core/model   215.745s
ok  .../internal/core/parser    1.049s
ok  .../internal/core/runtime   1.152s
ok  .../internal/lsp           18.830s
ok  .../internal/repl           5.435s      (all 18 packages with tests: ok, none FAIL)

$ make lint
Running staticcheck...
Running gosec...
✓ Lint passed

$ go test -run TestExecutionConformance ./internal/core/runtime
ok  github.com/Open-MBEE/Systemica/internal/core/runtime  0.007s

$ go test -run TestGolden ./internal/core/parser
ok  github.com/Open-MBEE/Systemica/internal/core/parser  0.005s

$ go test -run TestStdlibConformance ./internal/core/libs
ok  github.com/Open-MBEE/Systemica/internal/core/libs  0.059s

$ go test ./internal/lsp/...
ok  github.com/Open-MBEE/Systemica/internal/lsp

$ ./scripts/download-training-examples.sh   # corpus fetched
$ go test -v ./internal/core/model/ -run TestTrainingExamples
    training_examples_test.go:97: 81/100 training files clean
--- PASS: TestTrainingExamplesSemanticErrors (12.77s)
ok  github.com/Open-MBEE/Systemica/internal/core/model  12.779s

$ circleci config validate .circleci/config.yml
Config file at ".circleci/config.yml" is valid.

Corpus gate matches the 81/100 baseline; training_examples_expected.txt was not touched (no Go code changed in this PR).

Link to Devin session: https://nasa-jpl-demo.devinenterprise.com/sessions/f3e06cc1e62e40f2811fa71002d14f73
Requested by: @HuiJun

…s and checksums

Root cause is the com.apple.quarantine attribute, not a missing signature:
Go's linker already emits an ad-hoc (linker-signed) signature for
darwin/arm64 even when cross-compiling from Linux.

- README/QUICKSTART: curl and go install paths, quarantine escape hatch
- build-release: systemica-<os>-<arch> bundles + SHA256SUMS.txt
- packaging/homebrew: formula template + render script (no tap created)
- docs/MACOS_DISTRIBUTION.md: notarization decision record

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
@HuiJun HuiJun self-assigned this Aug 6, 2026
@devin-ai-integration

Copy link
Copy Markdown
Author
Original prompt from Devin Bot

# Task: investigate and improve the macOS execution experience for Systemica releases

Repo: JPL-Devin/Systemica (Go module github.com/Open-MBEE/Systemica), clone with
git clone https://github.com/JPL-Devin/Systemica.git.

#``# The problem, as reported by the user

On macOS, running the released sysml / sysml-lsp binaries currently requires the end user to
grant permission ("cannot be opened because the developer cannot be verified" / Privacy & Security
-> "Open Anyway"). The user wants this either eliminated or made materially easier for end users.

#``# What to investigate first (report findings, do not guess)

  1. Reproduce the root cause precisely. Read .circleci/config.yml (build-release and
    publish-github-release jobs) and .github/workflows/. Today darwin/amd64 and darwin/arm64
    sysml and sysml-lsp binaries are cross-compiled on a Linux CircleCI executor, tarred, and
    uploaded to GitHub Releases with ghr. Nothing is code-signed or notarized.
    Determine which of these is actually biting users, and say which with evidence:
    • the com.apple.quarantine extended attribute applied by the browser/Finder to a downloaded
      archive and propagated to the extracted binaries (Gatekeeper prompt), versus
    • a missing or invalid code signature (note that macOS on Apple silicon requires at least an
      ad-hoc signature for a binary to execute at all; check what signature, if any, Go's linker
      emits for darwin/arm64 binaries cross-compiled from Linux — verify with codesign -dv output
      you actually obtain, or with authoritative Go/Apple documentation, rather than assuming).
  2. Enumerate the options with honest cost/benefit, at minimum:
    • Apple Developer ID signing + notarization + stapling (codesign --options runtime,
      notarytool submit --wait, stapler staple) — the only path that removes the prompt entirely
      for a downloaded archive. Requires a paid Apple Developer Program account, a Developer ID
      Appl... (3799 chars truncated...)

@devin-ai-integration

Copy link
Copy Markdown
Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

…t tap formula

- packaging/homebrew/Formula/systemica.rb: real formula (renderable) with
  per-platform urls/checksums, install of both binaries, and a test block
- packaging/homebrew/README.md: exact steps to create Open-MBEE/homebrew-tap
  and to bump the formula each release
- render script: reads the formula source, fails on leftover placeholders
- README/QUICKSTART: brew tap Open-MBEE/tap first, direct download + xattr fallback

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
@devin-ai-integration devin-ai-integration Bot changed the title docs(release): document macOS Gatekeeper install path; publish bundles and checksums docs(release): recommend Homebrew on macOS; publish bundles, checksums, and tap formula Aug 6, 2026

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

@HuiJun
HuiJun merged commit bac9ff5 into main Aug 6, 2026
3 checks passed
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