Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
c5556a9
docs(compliance): re-count quality gates; test(grpc): add Layer 2 con…
devin-ai-integration[bot] Aug 6, 2026
291920e
feat(ast,resolve): make if/else branch bodies real namespaces
devin-ai-integration[bot] Aug 6, 2026
55d11d1
fix(parser,resolve): register the payload a message declares in its o…
devin-ai-integration[bot] Aug 6, 2026
c8fff49
docs(release): document macOS Gatekeeper install path; publish bundle…
devin-ai-integration[bot] Aug 6, 2026
ac28546
fix(parser,passes): satisfy references a requirement usage, not a type
devin-ai-integration[bot] Aug 6, 2026
85d6a1b
feat(parser): add textual notation for history, entry/exit points and…
devin-ai-integration[bot] Aug 6, 2026
4780428
feat(semantics): implicit redefinition of behavior and step parameters
devin-ai-integration[bot] Aug 6, 2026
5a57e45
feat(semantics): resolve members contributed by reference subsetting
devin-ai-integration[bot] Aug 6, 2026
b1b3892
docs(packaging): make brew the recommended macOS path; ready-to-commi…
devin-ai-integration[bot] Aug 6, 2026
a2d4e11
Merge pull request #27 from JPL-Devin/devin/1786056576-docs-drift-grp…
HuiJun Aug 6, 2026
0fcdc71
fix(lower): report a defer written in a region body instead of droppi…
devin-ai-integration[bot] Aug 6, 2026
e5c7ffd
Merge pull request #28 from JPL-Devin/devin/1786056809-if-branch-name…
HuiJun Aug 6, 2026
3a66b8f
fix(passes): resolve aliases for satisfy reference targets
devin-ai-integration[bot] Aug 6, 2026
7ef0a42
fix(lsp): resolve perform references outside the perform statement's …
devin-ai-integration[bot] Aug 6, 2026
4eb14af
fix(parser): give a declared flow payload its own source span
devin-ai-integration[bot] Aug 6, 2026
09cc23b
chore: merge main into devin/1786058129-perform-reference-subsetting
devin-ai-integration[bot] Aug 6, 2026
0c6bf8d
Merge pull request #29 from JPL-Devin/devin/1786056851-flow-payload-m…
HuiJun Aug 6, 2026
bac9ff5
Merge pull request #30 from JPL-Devin/devin/1786056455-macos-gatekeeper
HuiJun Aug 6, 2026
4c4c491
chore: merge main into state textual notation branch
devin-ai-integration[bot] Aug 6, 2026
4cdb2d7
Merge origin/main into devin/1786056922-satisfy-usage-reference
devin-ai-integration[bot] Aug 6, 2026
ddaa3bb
chore: merge main into implicit parameter redefinition branch
devin-ai-integration[bot] Aug 6, 2026
383ff50
chore: merge main into devin/1786058129-perform-reference-subsetting
devin-ai-integration[bot] Aug 6, 2026
b63d7a3
fix(runtime,lower): run the action a bare perform statement names
devin-ai-integration[bot] Aug 7, 2026
e24381e
Merge pull request #31 from JPL-Devin/devin/1786056922-satisfy-usage-…
HuiJun Aug 7, 2026
272a805
chore: merge main into implicit parameter redefinition branch
devin-ai-integration[bot] Aug 7, 2026
9b3a8fa
Merge pull request #32 from JPL-Devin/devin/1786056437-state-textual-…
HuiJun Aug 7, 2026
837c45f
chore: merge main into devin/1786058129-perform-reference-subsetting
devin-ai-integration[bot] Aug 7, 2026
fa81b42
fix(resolve,symbols,semantics): prefer declared names over effective …
devin-ai-integration[bot] Aug 7, 2026
c397dd3
fix(passes): match a redeclared parameter to the inherited one by pos…
devin-ai-integration[bot] Aug 7, 2026
c9e30a6
fix(resolve,symbols): let effective names never shadow or ambiguate a…
devin-ai-integration[bot] Aug 7, 2026
b884848
fix(passes): number parameter positions over every directed feature
devin-ai-integration[bot] Aug 7, 2026
ad3bca0
fix(semantics): inherit the general parameters no owned parameter red…
devin-ai-integration[bot] Aug 7, 2026
d8af8d0
fix(resolve): hide only a reference's own borrowed name, not its whol…
devin-ai-integration[bot] Aug 7, 2026
6bf594c
fix(passes): order the parameter list as the semantic model does
devin-ai-integration[bot] Aug 7, 2026
3cb40e7
fix(lsp,parser): resolve a feature chain's member through its operand
devin-ai-integration[bot] Aug 7, 2026
6ff0383
Merge pull request #33 from JPL-Devin/devin/1786058129-perform-refere…
HuiJun Aug 7, 2026
a143e9e
fix(passes): claim the inherited parameter at the declaration's position
devin-ai-integration[bot] Aug 7, 2026
b6704f5
chore: merge main into implicit parameter redefinition branch
devin-ai-integration[bot] Aug 7, 2026
ecc605e
fix(semantics,passes): keep an inherited parameter a declaration cann…
devin-ai-integration[bot] Aug 7, 2026
32503c7
fix(semantics): keep the standard library base of an implicitly redef…
devin-ai-integration[bot] Aug 7, 2026
6bd3ea4
Merge pull request #34 from JPL-Devin/devin/1786058029-implicit-param…
HuiJun Aug 7, 2026
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
23 changes: 23 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,28 @@ jobs:
tar czf "${binary}.tar.gz" "$binary"
fi
done

# Bundle archives: both binaries under their plain names
# (sysml, sysml-lsp), which is the layout a Homebrew formula or a
# PATH install expects. The per-binary archives above are kept
# unchanged so existing download links keep working.
for platform in linux-amd64 linux-arm64 darwin-amd64 darwin-arm64; do
stage="stage/${platform}"
mkdir -p "$stage"
cp "sysml-${platform}" "${stage}/sysml"
cp "sysml-lsp-${platform}" "${stage}/sysml-lsp"
tar czf "systemica-${platform}.tar.gz" -C "$stage" sysml sysml-lsp
done
mkdir -p stage/windows-amd64
cp sysml-windows-amd64.exe stage/windows-amd64/sysml.exe
cp sysml-lsp-windows-amd64.exe stage/windows-amd64/sysml-lsp.exe
(cd stage/windows-amd64 && zip -q ../../systemica-windows-amd64.zip sysml.exe sysml-lsp.exe)
rm -rf stage

# Checksums over every published archive, so a Homebrew formula
# (or a careful user) can verify a download.
sha256sum ./*.tar.gz ./*.zip | sed 's|\./||' > SHA256SUMS.txt
cat SHA256SUMS.txt

- store_artifacts:
path: dist/
Expand Down Expand Up @@ -196,6 +218,7 @@ jobs:
# Move only tarballs to a release directory
mkdir -p dist/release
mv dist/*.tar.gz dist/*.zip dist/release/ 2>/dev/null || true
mv dist/SHA256SUMS.txt dist/release/
echo "Release artifacts:"
ls -la dist/release/

Expand Down
28 changes: 27 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,14 @@ A complete, production-grade SysML v2 implementation in Go—providing language
wget https://github.com/Open-MBEE/Systemica/releases/latest/download/sysml-linux-amd64.tar.gz
tar xzf sysml-linux-amd64.tar.gz && sudo mv sysml-linux-amd64 /usr/local/bin/sysml

# macOS (see Quick Start for all platforms)
# macOS (Intel or Apple Silicon) — see the note below; requires the tap to exist
brew tap Open-MBEE/tap && brew install systemica
```

**With a Go toolchain (no download, never quarantined):**
```bash
go install github.com/Open-MBEE/Systemica/cmd/sysml@latest
go install github.com/Open-MBEE/Systemica/cmd/sysml-lsp@latest
```

**Or build from source:**
Expand All @@ -23,6 +30,19 @@ make build
./bin/sysml
```

> **macOS — use Homebrew.** The released binaries are not Developer ID signed or notarized,
> so a tarball downloaded *in a browser* carries `com.apple.quarantine` and Gatekeeper shows
> "cannot be opened because the developer cannot be verified". Homebrew downloads with
> `curl`, which never sets that attribute, so `brew install` avoids the prompt entirely.
> Fallback if you download the tarball directly (`curl -fL ... systemica-darwin-arm64.tar.gz`,
> then `xattr -d com.apple.quarantine`): see
> [Quick Start](docs/QUICKSTART.md#macos-gatekeeper). Signing/notarization is the eventual
> fix — [docs/MACOS_DISTRIBUTION.md](docs/MACOS_DISTRIBUTION.md).
>
> The tap is not published yet: `brew tap Open-MBEE/tap` works once the maintainer creates
> `Open-MBEE/homebrew-tap` ([how](packaging/homebrew/README.md)). Until then use `go install`
> or the direct download.

### Try it

**Interactive modeling:**
Expand Down Expand Up @@ -210,6 +230,12 @@ Pre-built binaries for Linux, macOS, and Windows are available on the [Releases
- Every commit: Build + test
- Tagged releases (`v*`): Multi-platform binaries published to GitHub Releases

**Release artifacts:** per-binary archives (`sysml-<os>-<arch>.tar.gz`,
`sysml-lsp-<os>-<arch>.tar.gz`), `systemica-<os>-<arch>.tar.gz` bundles containing both
binaries, and `SHA256SUMS.txt`. macOS binaries are not Developer ID signed or notarized and
Windows binaries are not Authenticode signed — see
[docs/MACOS_DISTRIBUTION.md](docs/MACOS_DISTRIBUTION.md).

## Building

```bash
Expand Down
4 changes: 2 additions & 2 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ Parse + model all behavioral bodies with unified fallback grammar:
- Golden trace recording for transitions/entry/exit
- APIs: `ProcessNextEvent()`, `CurrentState()`, `EventQueue()`, `StateData()`, `SetTrace()`
- Deferred events: an event no active transition handles is retained while a state deferring it is active, and delivered afterwards in arrival order
- **Known limitations:** history, entry and exit point pseudostates and deferred events have no textual notation, so they can only be built programmatically; CallEvent matches any call (`matchesEvent:437` TODO)
- **Known limitations:** CallEvent matches any call (`matchesEvent:437` TODO)

3. **Context Integration** — Public runtime APIs
- `InvokeCalc(symbol, args)` — Invoke calculation with arguments, return result
Expand Down Expand Up @@ -609,7 +609,7 @@ Every behavioral feature must have:
- Test case(s) exercising the feature
- Status: ✅ Faithful / ⚠️ Approximate / ❌ Not Yet Implemented / 🚧 Known Failure

**Current coverage:** ~98% faithful implementation. Calc/constraint/requirement fully functional. Action/state executor infrastructure complete (fork/join/decision, TimeEvent/ChangeEvent, guards, hierarchy, orthogonal regions all tested); all 29 conformance cases pass. Fork/join, shallow/deep history and deferred events are implemented but have no textual notation for history or deferral — see SPEC_COMPLIANCE.md.
**Current coverage:** ~98% faithful implementation. Calc/constraint/requirement fully functional. Action/state executor infrastructure complete (fork/join/decision, TimeEvent/ChangeEvent, guards, hierarchy, orthogonal regions all tested); all 29 conformance cases pass. Fork/join, shallow/deep history, entry/exit points and deferred events are implemented and reachable from source text — see SPEC_COMPLIANCE.md and docs/grammar/README.md.

---

Expand Down
215 changes: 215 additions & 0 deletions docs/MACOS_DISTRIBUTION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
# macOS Distribution: Gatekeeper, Signing, and Notarization

Status: **decision record** — records what was measured, what was changed, and what a
maintainer must provide to remove the macOS security prompt entirely.
Last updated: 2026-08-06.

## 1. The symptom

A user who downloads `sysml-darwin-arm64.tar.gz` (or the Intel/`sysml-lsp` equivalents)
from GitHub Releases in a browser, extracts it, and runs it gets:

> "sysml-darwin-arm64" cannot be opened because the developer cannot be verified.

and has to go to System Settings → Privacy & Security → **Open Anyway**.

## 2. Root cause: quarantine, not a missing signature

There are two distinct macOS mechanisms that can block a downloaded binary. Only one of
them is biting us.

### 2.1 Executability (code signature) — already satisfied

macOS on Apple silicon refuses to execute arm64 code with no valid signature attached
([Apple Platform Security, "Rosetta 2 on a Mac with Apple silicon"](https://support.apple.com/guide/security/rosetta-2-on-a-mac-with-apple-silicon-secebb113be1/web)).
An *ad-hoc* signature is enough to satisfy this.

Go's linker emits that ad-hoc signature itself, including when cross-compiling from Linux
(see [golang/go#42684](https://github.com/golang/go/issues/42684) and
[`cmd/internal/codesign`](https://go.dev/src/cmd/internal/codesign/codesign.go)).

Measured on a Linux host with Go 1.25.0, cross-compiling this repository's `cmd/sysml`
exactly as the `build-release` CircleCI job does, then parsing the Mach-O load commands:

| Target | `LC_CODE_SIGNATURE` | CodeDirectory flags |
| --- | --- | --- |
| `GOOS=darwin GOARCH=arm64` | present (57 KB blob, `0xfade0cc0` superblob) | `0x20002` = `adhoc, linker-signed`, identifier `a.out` |
| `GOOS=darwin GOARCH=amd64` | absent | n/a (x86-64 does not require one) |

Those are the same flags `codesign -dv` reports as `flags=0x20002(adhoc,linker-signed)` in
the Go issue above. **Untested claim (no macOS host available here):** that the binary
therefore launches on Apple silicon once quarantine is cleared — this follows from the
Apple/Go documentation cited, but it was not executed on a Mac as part of this work.

Consequence: adding our own ad-hoc signing step (`codesign -s -`) would change nothing. It
does not, and cannot, satisfy Gatekeeper for a quarantined download — an ad-hoc signature
carries no Developer ID and no notarization ticket.

### 2.2 Gatekeeper / quarantine — this is the prompt users see

Programs that download files on behalf of a user (Safari, Chrome, Mail, and anything else
that opts into `LSFileQuarantineEnabled`) tag the downloaded file with the
`com.apple.quarantine` extended attribute. Archive utilities propagate that attribute to
the files they extract, so the extracted `sysml-darwin-arm64` is quarantined too. On first
launch, Gatekeeper evaluates a quarantined binary and blocks anything that is not signed
with a Developer ID *and* notarized — which is exactly the prompt in §1
(Apple: ["Safely open apps on your Mac"](https://support.apple.com/en-us/102445),
["Notarizing macOS software before distribution"](https://developer.apple.com/documentation/security/notarizing-macos-software-before-distribution)).

Two consequences worth knowing:

- The attribute is set by the *downloader*, not by the file. `curl`, `wget`, `git`, and
`go install` do not set it, so binaries obtained those ways never trigger the prompt.
This is why `brew install` is prompt-free (Homebrew applies quarantine only to *casks* —
hence the cask-only `--no-quarantine` flag — not to formulae).
- `xattr -d com.apple.quarantine <file>` removes it, which is why that workaround "works".

## 3. Options considered

| Option | Removes the prompt for a browser download? | Cost | Requires credentials we don't have |
| --- | --- | --- | --- |
| Developer ID signing + notarization (stapling only if we ship `.pkg`/`.dmg`, see §5.3) | **Yes** — the only option that does | $99/yr Apple Developer Program + macOS CI minutes | Yes |
| Homebrew tap/formula (**accepted stopgap**) | Yes, for users who install via `brew` (curl download, no quarantine) | Free (a tap repo) | No, but needs a new repo |
| `go install .../cmd/sysml@latest` | Yes (built locally, never quarantined) | Free; user needs a Go toolchain | No |
| Ad-hoc signing in CI (`codesign -s -`) | **No** — see §2.1; also already effectively done by the Go linker | Needs a macOS runner | No |
| Documented `xattr -d com.apple.quarantine` | Yes, but by having the user disable a security check | Free | No |

Notes on the last row: telling users to strip quarantine is a poor default because it
trains them to bypass Gatekeeper on any download, and it gives them no way to tell our
archive from a tampered one. It is documented in `docs/QUICKSTART.md` as an escape hatch
*next to* the checksum verification step, not as the recommended path.

## 4. What this change actually lands

1. **Docs** (`README.md`, `docs/QUICKSTART.md`): a macOS section that recommends
`brew tap Open-MBEE/tap && brew install systemica`, with `curl`/`go install` and the
quarantine-clearing + checksum-verification commands as fallbacks.
2. **Release artifacts** (`.circleci/config.yml`): in addition to the existing per-binary
archives (unchanged, so existing links keep working), the `build-release` job now
publishes `systemica-<os>-<arch>.tar.gz`/`.zip` bundles containing `sysml` and
`sysml-lsp` under their plain names, plus a `SHA256SUMS.txt` covering every archive.
Both are prerequisites for a Homebrew formula and for users who want to verify a
download.
3. **Homebrew formula** (`packaging/homebrew/Formula/systemica.rb`) plus
`scripts/render-homebrew-formula.sh`, which renders it for a tag from `SHA256SUMS.txt`.
Homebrew is the accepted macOS install path until notarization exists. **No tap
repository was created**: the maintainer creates `Open-MBEE/homebrew-tap` and pushes the
rendered formula — steps in `packaging/homebrew/README.md`.

## 5. Decision record: notarization

**Decision: not implemented in this change, because it cannot be done without credentials
the maintainer must obtain. Homebrew is the accepted stopgap in the meantime** (maintainer
decision, 2026-08-06). Nothing about the change above conflicts with adding it later; the
notarization step would run after the existing build step and re-tar the signed binaries.

### 5.1 What the maintainer must provide

| # | Item | How to obtain | Where it goes |
| --- | --- | --- | --- |
| 1 | Apple Developer Program membership | https://developer.apple.com/programs/ — $99/year, organization enrollment needs a D-U-N-S number and takes days-to-weeks | prerequisite for 2–4 |
| 2 | **Developer ID Application** certificate (not "Apple Development", not "Mac App Distribution") | Certificates, Identifiers & Profiles → create a CSR on a Mac → download `.cer` → export as `.p12` with a password | CI secret `MACOS_CERT_P12` (base64) + `MACOS_CERT_PASSWORD` |
| 3 | App Store Connect **API key** for `notarytool` (preferred over an Apple ID + app-specific password: no 2FA interaction, revocable) | App Store Connect → Users and Access → Integrations → Keys → download the `.p8` **once** | CI secrets `APPLE_API_KEY_P8` (base64), `APPLE_API_KEY_ID`, `APPLE_API_ISSUER_ID` |
| 4 | Team ID | Apple Developer account membership page | CI secret `APPLE_TEAM_ID` |
| 5 | A decision on where it runs (see §5.2) | — | — |

Without item 2 specifically, nothing works: only a Developer ID certificate produces a
signature Gatekeeper accepts for software distributed outside the App Store.

### 5.2 Which CI provider it would run on

`codesign`, `notarytool`, and `stapler` are Xcode command-line tools and only run on macOS.
The current release pipeline is CircleCI (`.circleci/config.yml`, `release` workflow on
`v*` tags), which cross-compiles on the Linux `cimg/go:1.25` executor; GitHub Actions is
used only for PR checks (`.github/workflows/pr.yml`), which explicitly does not mirror the
release workflow.

- **CircleCI macOS VM**: `macos: {xcode: ...}` with `resource_class: m4pro.medium` costs
**200 credits/minute**, and macOS VMs are not available on the Free plan; CircleCI's
400,000 free open-source credits/month apply to Linux, Arm, and Docker only, so macOS
jobs draw on the 30,000-credit free allowance
([CircleCI price list](https://circleci.com/pricing/price-list/),
[pricing](https://circleci.com/pricing/)).
- **GitHub Actions `macos-latest`**: a standard GitHub-hosted runner, and standard runners
are **free for public repositories**
([GitHub Actions billing](https://docs.github.com/en/billing/concepts/product-billing/github-actions)).
(`macos-*-large`/`-xlarge` are *larger* runners and are always billed — do not use them.)

**Recommendation:** add a tag-triggered GitHub Actions release workflow on `macos-latest`
for the darwin artifacts only, and keep CircleCI for everything else — or move the whole
release there. Doing it on CircleCI is functionally fine but costs credits and, on the Free
plan, is not available at all. This only stays free if the repository is public.

### 5.3 What the pipeline steps would look like

Sketch, not tested (no macOS host available here); step order and flags follow Apple's
["Customizing the notarization workflow"](https://developer.apple.com/documentation/security/customizing-the-notarization-workflow):

```yaml
# .github/workflows/release-macos.yml (sketch), on: push: tags: ['v*']
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with: { go-version-file: go.mod }

# 1. Import the Developer ID certificate into a throwaway keychain.
- run: |
echo "$MACOS_CERT_P12" | base64 -d > cert.p12
security create-keychain -p "$KEYCHAIN_PW" build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p "$KEYCHAIN_PW" build.keychain
security import cert.p12 -k build.keychain -P "$MACOS_CERT_PASSWORD" \
-T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PW" build.keychain
rm cert.p12

# 2. Build both darwin architectures.
- run: |
GOOS=darwin GOARCH=arm64 make build-sysml VERSION="$GITHUB_REF_NAME" ...
GOOS=darwin GOARCH=amd64 make build-sysml VERSION="$GITHUB_REF_NAME" ...
# ...and build-lsp for both

# 3. Sign with the hardened runtime (notarization requires --options runtime).
- run: |
codesign --force --timestamp --options runtime \
--sign "Developer ID Application: <NAME> ($APPLE_TEAM_ID)" \
dist/sysml-darwin-arm64 dist/sysml-darwin-amd64 \
dist/sysml-lsp-darwin-arm64 dist/sysml-lsp-darwin-amd64
codesign --verify --strict --verbose=2 dist/sysml-darwin-arm64

# 4. Notarize. notarytool accepts .zip/.pkg/.dmg, not .tar.gz, so zip for submission.
- run: |
echo "$APPLE_API_KEY_P8" | base64 -d > key.p8
ditto -c -k --keepParent dist/sysml-darwin-arm64 notarize.zip
xcrun notarytool submit notarize.zip --wait \
--key key.p8 --key-id "$APPLE_API_KEY_ID" --issuer "$APPLE_API_ISSUER_ID"
rm key.p8

# 5. Tar the *signed* binaries and publish. Do not modify a binary after signing —
# any edit (including strip) invalidates the signature.
```

Two constraints that shape the above:

- **Stapling does not apply to bare executables.** Apple: "Although tickets are created for
standalone binaries, it's not currently possible to staple tickets to them"
(["Customizing the notarization workflow"](https://developer.apple.com/documentation/security/customizing-the-notarization-workflow)).
Gatekeeper then fetches the ticket online at first launch. Getting a *stapled* artifact
would mean shipping a `.pkg` or `.dmg` instead of a tarball — a larger packaging change,
worth doing only if offline first launch matters.
- **Signing must happen after the last modification** of the binary, and the tarball must
be built from the signed files.

### 5.4 Recurring cost

$99/year (Apple Developer Program) plus CI time: on GitHub Actions `macos-latest` for a
public repository, $0; on CircleCI, ~200 credits/minute of macOS VM time per release.

## 6. Windows

Out of scope for this change, and deliberately not fixed here: the Windows artifacts are
unsigned too, so SmartScreen shows "Windows protected your PC" for downloaded
`sysml-windows-amd64.exe`. The remedy (an Authenticode / EV code-signing certificate from a
commercial CA, typically a few hundred USD per year) is a separate decision with a separate
cost, and SmartScreen reputation also builds over download volume.
Loading
Loading