diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..77843c1 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,150 @@ +# A tag becomes a draft release a human publishes (M1 completion grill Q18). +# +# Everything here is exact-commit: the tag must name the workspace version; +# Ubuntu and macOS both run ./scripts/verify on the tagged commit — a green +# scheduled run of another commit is never release evidence (Q11, Q13) — +# then package, then the install-surface smoke, then the production +# lifecycle on the runner's throwaway account. The release job only +# collects what those produced, re-checks the checksums, and creates the +# draft. CI calls the scripts; it never re-implements what they decide +# (AGENTS.md §Verification). +name: Release + +on: + push: + tags: ["v*"] + +# Keep in lockstep with ci.yml so every platform verifies with the same tool. +env: + CARGO_DENY_VERSION: 0.20.2 + +permissions: + contents: read + +jobs: + version: + name: tag names the workspace version + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + + # cargo's answer, the same one scripts/package bakes into the asset + # names and the plist: a tag that disagrees with it would publish an + # archive whose contents say another version. + - name: The tag equals v + run: | + workspace="$(cargo pkgid -p corral)" + workspace="${workspace##*#}" + workspace="${workspace##*@}" + if [ "$GITHUB_REF_NAME" != "v$workspace" ]; then + echo "release: tag $GITHUB_REF_NAME does not name the workspace version $workspace; bump Cargo.toml or retag" >&2 + exit 1 + fi + echo "release: $GITHUB_REF_NAME names workspace version $workspace" + + build: + name: verify, package, smoke (${{ matrix.platform }}) + needs: version + strategy: + fail-fast: false + matrix: + include: + - platform: linux-x86_64 + runner: ubuntu-24.04 + # Pinned, never macos-latest: this image is where the release + # verifies. The oldest macOS the binaries run on is MACOS_MINIMUM + # in scripts/package, the deployment target they compile against + # on this image (Q16 as amended 2026-09-11); the pin can move + # without moving that number. + - platform: macos-arm64 + runner: macos-15 + runs-on: ${{ matrix.runner }} + steps: + - uses: actions/checkout@v4 + + - name: Cache cargo + uses: actions/cache@v4 + with: + path: | + ~/.cargo/bin + ~/.cargo/registry + ~/.cargo/git + target + key: cargo-${{ runner.os }}-${{ hashFiles('Cargo.lock', 'rust-toolchain.toml') }} + restore-keys: cargo-${{ runner.os }}- + + # gpui's Linux backends link against these at build time. Environment + # setup only, the same list ci.yml installs. + - name: Desktop build dependencies + if: runner.os == 'Linux' + run: | + sudo apt-get update -q + sudo apt-get install -y -q --no-install-recommends \ + libxkbcommon-dev libxkbcommon-x11-dev libwayland-dev libx11-xcb-dev libfontconfig1-dev + + - name: Install cargo-deny + run: | + cargo deny --version 2>/dev/null | grep -qF "cargo-deny $CARGO_DENY_VERSION" \ + || cargo install cargo-deny --locked --version "$CARGO_DENY_VERSION" + + - run: ./scripts/verify + + # Ad hoc until the signing secrets exist (Q9): the packaging PR merges + # without credentials, a public macOS release does not ship without + # them. When they are added: import the Developer ID identity into a + # runner keychain and export CORRAL_SIGNING_IDENTITY, and write the App + # Store Connect API key JSON to a file CORRAL_NOTARY_API_KEY names. + - run: ./scripts/package + + - run: ./scripts/package-smoke dist + + # The runner's account is the throwaway the disposable level exists + # for: install into its real home, activate the real daemon under its + # real ~/.corral, list, uninstall, and see the daemon leave. + - run: ./scripts/package-smoke --disposable-account dist + + - uses: actions/upload-artifact@v4 + with: + name: dist-${{ matrix.platform }} + path: | + dist/*.zip + dist/*.tar.gz + dist/SHA256SUMS + if-no-files-found: error + + release: + name: draft release + needs: build + runs-on: ubuntu-24.04 + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + + - uses: actions/download-artifact@v4 + with: + pattern: dist-* + path: assets + + # One manifest over both platforms' archives, which is what install.sh + # downloads beside the archive it picks. Each platform's package run + # wrote a manifest naming only its own archive, so concatenation is the + # whole merge — and every line is re-checked against the bytes that + # actually arrived here before anything is attached. + - name: Assemble dist and re-check SHA256SUMS + run: | + mkdir dist + cp assets/dist-*/*.zip assets/dist-*/*.tar.gz dist/ + cat assets/dist-*/SHA256SUMS > dist/SHA256SUMS + (cd dist && sha256sum -c SHA256SUMS) + ls -l dist + + - name: Create the draft + env: + GH_TOKEN: ${{ github.token }} + run: | + gh release create "$GITHUB_REF_NAME" \ + --draft --verify-tag \ + --title "Corral $GITHUB_REF_NAME" \ + --notes "$(printf 'Built from %s by the release workflow. A human publishes this draft.\n\nInstall (macOS on Apple Silicon, Ubuntu 24.04 on x86_64):\n\n curl -fsSL https://raw.githubusercontent.com/%s/main/install.sh | sh\n' "$GITHUB_SHA" "$GITHUB_REPOSITORY")" \ + dist/*.zip dist/*.tar.gz dist/SHA256SUMS diff --git a/.github/workflows/scheduled.yml b/.github/workflows/scheduled.yml index c2c2146..d1107b2 100644 --- a/.github/workflows/scheduled.yml +++ b/.github/workflows/scheduled.yml @@ -1,8 +1,10 @@ # macOS coverage and scheduled evidence amplification. # -# Per-PR CI runs on Linux; macOS runs after merge and on a schedule, and is -# required by scripts/verify-release. Local development already verifies on -# macOS, and a private repository bills macOS runners at ten times the rate. +# Per-PR CI runs on Linux; macOS runs after merge and on a schedule as +# regression evidence. Release evidence is release.yml, which verifies both +# platforms on the exact tagged commit: a green run here of another commit +# never stands in for that. Local development already verifies on macOS, +# and a private repository bills macOS runners at ten times the rate. # # A failure here never retroactively invalidates merged history. It produces a # finding, may freeze the affected owner's autonomous merge pending triage, and diff --git a/README.md b/README.md index ba29113..8fd3c07 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,33 @@ running on your machine, tells you which ones are blocked on you, and lets you answer them without hunting through terminals. You keep your own terminal, editor, and machines. -**Status: pre-release.** M1 is under construction and nothing is packaged -yet. See `ROADMAP.md` for what the current phase includes. +**Status: pre-release.** M1 is under construction; see `ROADMAP.md` for +what the current phase includes and what it must prove before it ships. + +## Install + +macOS 14 or later on Apple Silicon, and Ubuntu 24.04 on x86_64: + +```sh +curl -fsSL https://raw.githubusercontent.com/Poordeveloper/corral/main/install.sh | sh +``` + +The installer places `Corral.app` in `~/Applications` (macOS) or Corral's +executables under `~/.local/share/corral` (Linux), links `corral` into +`~/.local/bin`, and — after saying which — enables Corral's integration +with the Claude Code and Codex installs it finds. Nothing runs in the +background until you use it. `CORRAL_VERSION=vX.Y.Z` installs that release +instead of the latest; every artifact is on the +[releases page](https://github.com/Poordeveloper/corral/releases). On +Linux the CLI and the terminal session list are what is supported; the +Desktop is built and included but not yet validated there. + +To remove it: `corral uninstall`. It refuses while Corral still manages a +running session, then takes Corral's entries back out of your agents' +configuration, stops `corrald`, and removes what install placed. Your +`~/.corral` stays unless you pass `--purge`. + +Install is not upgrade: to move to a new release, uninstall and install. ## Documentation diff --git a/docs/decisions/2026-09-06-m1-completion-grill.md b/docs/decisions/2026-09-06-m1-completion-grill.md index befd2d9..e5187c5 100644 --- a/docs/decisions/2026-09-06-m1-completion-grill.md +++ b/docs/decisions/2026-09-06-m1-completion-grill.md @@ -77,7 +77,7 @@ Digest of what round 1 froze: | Q4 | **Every unresolved release-relevant noise row needs an explicit ship-time disposition**, and dogfood missed-item evidence participates. For the three miss-shaped rows the disposition is one of: fixed; correctly suppressed or reclassified; capability narrowed so the state is no longer claimed supported; documented non-systematic limitation supported by dogfood evidence. "Probably not systematic" without measurement is not available. Stronger rule: a measured condition that causes a repeatable miss inside a capability Corral still claims is systematic by construction — fix it or narrow the claim; low absolute frequency does not make it non-systematic. Genuinely intermittent misses record opportunities where measurable, confirmed misses, affected provider/version/surface, and the human disposition. No numeric miss-rate threshold is introduced; the bar stays *no systematic missed states inside a claimed supported capability*. | | Q5 | **(a).** The journal stays diagnostic, deletable, non-authoritative, outside epoch migration protection; it is not promoted to durable truth because release evaluation reads it. Retention 30 → 90 days. At the end of each window the canonical report is generated immediately and frozen into `docs/evidence/`; the durable release claim is the reviewed evidence artifact, never the local journal's survival. Any INCOMPLETE day inside the 14-consecutive-day attention window breaks continuity; counting restarts from the next complete day; missing instrumentation is never read as "probably zero bad events", and the failure is itself reliability evidence. This does not silently redefine the cohort's 4-week rule, which owns its own calendar. Invariant: *diagnostic evidence may be bounded and deletable; release evidence may never be silently incomplete.* | | Q6 | **Part 1 withdrawn 2026-09-10 (§Amendments): no migration runner, no fixture gate, no ADR; a schema change after the epoch advance is an approved reset. Parts 2 and 3 stand.** Original ruling: **Accepted in three parts, with a non-vacuous migration gate.** (1) `corral-state` gains a versioned forward migration runner: inspect the stored version; current → open; older supported → apply contiguous forward migrations, all steps and the version update in one transaction, failure → full rollback; newer than this binary → refuse; downgrade never attempted; no destructive fallback to a fresh database. `DOGFOOD_BASELINE_SCHEMA = 5` is frozen: at the epoch's advance schema 5 becomes the first protected baseline; zero real migrations exist and that is fine. A permanent representative schema-5 registry fixture is opened by the current build under `verify-release`; when the current schema exceeds 5 the complete chain must apply and the expected Corral-owned facts must survive, so a schema 6 cannot release without a working 5 → 6. Runner tests: rollback on failure, missing step rejected, newer schema rejected, old-build/new-DB refusal. (2) The local schema-1 database is dev-era state a human removes before the advance — allowed only while the epoch is `dev`; never encoded as startup behaviour; unavailable after dogfood begins. (3) The advance `dev → dogfood` is a human-only, repository-visible change in a dedicated commit/PR; an agent never advances it. Invariant: *dogfood starts only after schema 5 has become a migration-supported baseline future releases are obligated to preserve.* | -| Q7 | **Bundle accepted; identifier frozen: `com.poordeveloper.corral`.** `Corral.app/Contents/{Info.plist, MacOS/{corral-desktop, corrald, corral}}`, `CFBundleExecutable` = `corral-desktop`, the three executables siblings so sibling-only daemon resolution holds for the Desktop and for the CLI symlink after canonicalization. Install to `~/Applications/Corral.app`; `~/.local/bin/corral` → `Corral.app/Contents/MacOS/corral`. Regular Dock app; no LSUIElement conversion. | +| Q7 | **Amended 2026-09-11 (§Amendments): the identifier is `com.carriez.corral`.** Bundle accepted; identifier frozen: `com.poordeveloper.corral`. `Corral.app/Contents/{Info.plist, MacOS/{corral-desktop, corrald, corral}}`, `CFBundleExecutable` = `corral-desktop`, the three executables siblings so sibling-only daemon resolution holds for the Desktop and for the CLI symlink after canonicalization. Install to `~/Applications/Corral.app`; `~/.local/bin/corral` → `Corral.app/Contents/MacOS/corral`. Regular Dock app; no LSUIElement conversion. | | Q8 | **Install accepted; uninstall gains a safety gate.** Installer: identify OS/arch → resolve the exact release artifact → download → fetch and check the SHA-256 manifest → verify before extraction → place → CLI symlink → PATH action if needed → detect supported providers → disclose which integrations it intends to enable → `corral integration enable` per detected provider → per-provider status. An integration conflict never overwrites user-owned configuration, reports Limited awareness and the resolution path, and never rolls back an otherwise valid install. A checksum from the same release authority is integrity, not an independent trust root. **Uninstall preflight**: query runtime truth first; any managed runtime Running or Unknown → default uninstall refuses ("Corral is still managing N running sessions" / "could not verify whether U managed sessions have ended"); no destructive `--force` in M1. Successful uninstall: connect/activate → uninstall Corral-owned integrations → verify cleanup → request daemon shutdown → wait for ownership to end → remove symlink → remove `.app`/binaries; if cleanup cannot complete safely, fail before deleting binaries. Default preserves `~/.corral`; `--purge` removes state and diagnostics only after the preconditions succeed and is explicit destructive intent after `dogfood`. | | Q9 | **Modified: three build classes.** Ad-hoc signing serves local developer dogfood and proves packaging mechanics only. The external cohort requires Developer ID Application signing, Hardened Runtime as the distribution design requires, a secure timestamp, notarization, and a Gatekeeper launch test on a clean user environment — never right-click Open, quarantine removal, or a Security Settings bypass as the onboarding path. Public M1 macOS release: Developer ID + notarization is a release gate, not polish; the packaging PR may merge without credentials, the cohort and public release cannot pass without them. Architecture: universal only if Intel is claimed; if claimed, every shipped executable carries coherent x86_64 + arm64 slices and both are validated; never a universal Desktop beside an arm64-only daemon or CLI. | | Q10 | **Accepted with support-boundary wording.** `~/.local/share/corral/bin/{corral, corrald, corral-desktop}` as siblings; `~/.local/bin/corral` symlink; no `.desktop`, no Linux tray. `corral-desktop` is included and buildable but unvalidated and is neither placed on PATH nor marketed. Linux TUI/CLI supported per its tested matrix. Asset names carry the architecture; no generic `linux` artifact; only architectures actually built and tested may be claimed. | @@ -872,7 +872,7 @@ Digest of what round 2 froze: | Q | Ruling | |---|---| | Q15 | **(a), semantics pinned.** A false-item dispute binds only the current active attention item. No current item → non-zero exit, no journal record, message `No current attention item. If Corral failed to surface an item, use --missed.` `--missed` is a distinct statement of fact — "should have appeared, did not" — not a dispute without an id. A false positive that has already ended is never attributed by guessing the most recent item; the evidence review matches it by session and wall-clock time against `born` / `ended`. Item ids stay out of the normal product UI; a diagnostics surface, if ever needed, is its own task. | -| Q16 | **Accepted with a narrowed Linux claim.** macOS: Apple Silicon only; minimum version = the lowest macOS major M1 CI actually verifies, filled into `LSMinimumSystemVersion` after the runner image is pinned. Linux: **Ubuntu 24.04 x86_64 only** for M1 — `ubuntu-latest` plus the PRoot host prove nothing about Debian, Fedora, Arch, or older Ubuntu; a glibc baseline may replace the distro claim only if the build actually establishes one. aarch64 Linux is explicitly unsupported and unclaimed. | +| Q16 | **Amended 2026-09-11 (§Amendments): the minimum macOS is the deployment target the build compiles against, not the runner's major.** Accepted with a narrowed Linux claim. macOS: Apple Silicon only; minimum version = the lowest macOS major M1 CI actually verifies, filled into `LSMinimumSystemVersion` after the runner image is pinned. Linux: **Ubuntu 24.04 x86_64 only** for M1 — `ubuntu-latest` plus the PRoot host prove nothing about Debian, Fedora, Arch, or older Ubuntu; a glibc baseline may replace the distro claim only if the build actually establishes one. aarch64 Linux is explicitly unsupported and unclaimed. | | Q17 | **Accepted, artifact pin distinguished from installer pin.** GitHub Releases is the canonical distribution; the root `install.sh` is a bootstrap served from `main`; default = latest published, non-draft, non-prerelease; `CORRAL_VERSION=vX.Y.Z` pins the release artifact, **not** the revision of `install.sh`, which is a moving target. No installer release asset in M1 and no claim of a fully reproducible pinned installation; freezing `install.sh` at the tag arrives with a later supply-chain task. | | Q18 | **Accepted with two invariants.** (1) tag `vX.Y.Z` must mechanically equal `workspace.package.version = X.Y.Z`, else the workflow fails before any draft release. (2) draft → publish is the only human release authority: human pushes tag → CI verifies the exact tagged commit → package → smoke → checksums → draft release → release-gate evidence accepted → human publishes. "CI ran" leaves the human evidence document; the machine proves it. `0.0.0 → 0.1.0` for M1. | | Q19 | **(a), count unit pinned.** ≥ 100 trusted activations in aggregate across the declared provider set — not journal transitions, sessions, attention items, or daemon runs. Trusted activation = `to == needs_you && born.is_some() && assurance ∈ {deterministic, attested} && sealed == true`. Coverage: Claude + Codex total ≥ 100 **and** each declared-supported provider ≥ 1 trusted activation; one is not maturity, it prevents claiming a provider the window never exercised. No second per-provider threshold; missing coverage narrows the claim or extends the window (Q4). | @@ -1131,3 +1131,35 @@ database, and the advance is a human-only dedicated PR. Why: the epoch is registry schema; a runner with nothing to run is speculative infrastructure. If a migration is ever wanted, the change that needs one writes the first, as `corral-state::schema` already says. + +### 2026-09-11 — Q16: the minimum macOS is the deployment target, not the runner's major + +Founder, on being told the release runner would be pinned to `macos-15` +because `macos-14` is marked deprecated, and that Q16 would then make the +claim 15.0: 「runner是15,不影响编译支持14」. + +What changes: `MACOS_MINIMUM` in `scripts/package` — `MACOSX_DEPLOYMENT_TARGET` +and `LSMinimumSystemVersion` — is 14.0, the deployment target the release +build compiles against; the release workflow verifies on the pinned +`macos-15` image. The two numbers are no longer one: the runner pin says +where the tests ran, the constant says the oldest macOS the binaries are +built to run on. What stays: nothing runs the suite on macOS 14, so the +14.0 claim rests on the deployment target and on the founder's own +dogfood, not on CI; a macOS-14 failure report is a bug, not a contract +violation. Q16's Linux claim and its Intel exclusion are unchanged. + +### 2026-09-11 — Q7: the bundle identifier is `com.carriez.corral` + +Founder, unprompted, while the bundle was still unreleased: +「请把com.poordevelper.corral改成com.carriez.corral」. + +What changes: `packaging/macos/Info.plist.in` and the key the package smoke +asserts. What it reaches: the notification probe this file specifies under +Q22 uses "the real bundle identity" — that identity is now +`com.carriez.corral`, and `m1-notifications` seals the mechanism against +it. The Q7 body above is the transcript of that day and is left as it was; +this section is the authority. Why now is free: TCC keys authorization on +the bundle identifier, so a change after the notification probe would +discard every grant the probe established, and nothing has been released +under the old identifier. The freeze holds from here — the identifier +becomes load-bearing at the first published release, not before. diff --git a/packaging/macos/Info.plist.in b/packaging/macos/Info.plist.in index ea45ba1..5d1ae4c 100644 --- a/packaging/macos/Info.plist.in +++ b/packaging/macos/Info.plist.in @@ -14,7 +14,7 @@ CFBundleExecutable corral-desktop CFBundleIdentifier - com.poordeveloper.corral + com.carriez.corral CFBundleInfoDictionaryVersion 6.0 CFBundleName diff --git a/scripts/package b/scripts/package index ce417e5..e272e28 100755 --- a/scripts/package +++ b/scripts/package @@ -11,14 +11,34 @@ # D3). Signing class follows the credentials present: none → ad hoc, which # proves the packaging mechanics and nothing about Gatekeeper (grill Q9); # CORRAL_SIGNING_IDENTITY → Developer ID with hardened runtime and a secure -# timestamp; CORRAL_NOTARY_PROFILE on top → notarized and stapled. Missing -# credentials are not a failure; a failed notarization is. +# timestamp; an App Store Connect API key beside it → notarized and stapled. +# Missing credentials are not a failure; a failed notarization is. set -euo pipefail cd "$(dirname "$0")/.." -# One constant: the lowest macOS major the release workflow verifies, as -# LSMinimumSystemVersion and as the deployment target of the build (grill -# Q16). The release workflow pins the runner image that fixes this number. +# The App Store Connect API key notarization authenticates with: JSON +# carrying `issuer_id`, `key_id`, and `private_key` — the .p8 body, with or +# without PEM armor. Defaulted to the maintainer's release-machine location +# so a signed build notarizes without being told; CI points it at a file it +# writes from a secret. Only ever read beside a Developer ID identity: an +# ad-hoc bundle cannot be notarized, so an ad-hoc run ignores it entirely. +NOTARY_API_KEY="${CORRAL_NOTARY_API_KEY:-$HOME/.p12/api-key.json}" + +# The rebuilt private key lives in one 0700 directory for the length of the +# submission and goes away with the script however it ends. It is never +# written into dist/, which is uploaded. +NOTARY_WORKDIR="" +cleanup() { + if [ -n "$NOTARY_WORKDIR" ]; then + rm -rf "$NOTARY_WORKDIR" + fi +} +trap cleanup EXIT + +# One constant: the oldest macOS the binaries are built to run on, as the +# deployment target of the build and as LSMinimumSystemVersion. Not the +# release runner's major — .github/workflows/release.yml verifies on +# macos-15 and compiles against this (grill Q16 as amended 2026-09-11). MACOS_MINIMUM="14.0" # cargo's own answer, so a version edited in Cargo.toml cannot disagree with @@ -53,6 +73,48 @@ done rm -rf "$DIST" mkdir -p "$DIST" +# Submit the bundle and staple the ticket Apple issues. +# +# The key file is the one input: `notarytool` wants the private key as a +# file, so the JSON's `private_key` is rebuilt into one. Stapling is what +# makes the ticket travel with the archive — without it a machine that +# cannot reach Apple's service sees an unnotarized app. +notarize() { + local app="$1" + NOTARY_WORKDIR="$(mktemp -d "${TMPDIR:-/tmp}"/corral-notary.XXXXXX)" + local key="$NOTARY_WORKDIR/AuthKey.p8" + local issuer key_id + + key_id="$(plutil -extract key_id raw -o - "$NOTARY_API_KEY")" + issuer="$(plutil -extract issuer_id raw -o - "$NOTARY_API_KEY")" + [ -n "$key_id" ] && [ -n "$issuer" ] || { + echo "package: $NOTARY_API_KEY does not carry key_id and issuer_id" >&2 + exit 1 + } + + # Tolerant of both shapes the field is found in: armored PEM, or the bare + # base64 body that has to be folded and wrapped before a parser accepts it. + if plutil -extract private_key raw -o - "$NOTARY_API_KEY" | grep -q 'BEGIN PRIVATE KEY'; then + plutil -extract private_key raw -o - "$NOTARY_API_KEY" > "$key" + else + { + echo "-----BEGIN PRIVATE KEY-----" + plutil -extract private_key raw -o - "$NOTARY_API_KEY" | tr -d '\n' | fold -w 64 + echo "" + echo "-----END PRIVATE KEY-----" + } > "$key" + fi + + echo "==> notarize (key $key_id)" + local submission="$NOTARY_WORKDIR/notarize.zip" + ditto -c -k --keepParent "$app" "$submission" + xcrun notarytool submit "$submission" \ + --key "$key" --key-id "$key_id" --issuer "$issuer" --wait + xcrun stapler staple "$app" + # Proof the ticket is attached rather than merely issued. + xcrun stapler validate "$app" +} + package_macos() { local app="$DIST/Corral.app" local archive="Corral-$VERSION-macos-arm64.zip" @@ -73,10 +135,6 @@ package_macos() { codesign --force --sign "$CORRAL_SIGNING_IDENTITY" --options runtime --timestamp "$@" } else - if [ -n "${CORRAL_NOTARY_PROFILE:-}" ]; then - echo "package: CORRAL_NOTARY_PROFILE is set without CORRAL_SIGNING_IDENTITY; an ad-hoc bundle cannot be notarized" >&2 - exit 1 - fi class="ad-hoc" sign() { codesign --force --sign - "$@" @@ -91,14 +149,15 @@ package_macos() { sign "$app" codesign --verify --deep --strict "$app" - if [ -n "${CORRAL_NOTARY_PROFILE:-}" ]; then - echo "==> notarize" - local submission="$DIST/notarize.zip" - ditto -c -k --keepParent "$app" "$submission" - xcrun notarytool submit "$submission" --keychain-profile "$CORRAL_NOTARY_PROFILE" --wait - rm -f "$submission" - xcrun stapler staple "$app" - class="$class, notarized" + # Only a Developer ID bundle is submittable; an ad-hoc one is rejected by + # Apple, so the key is not even read on that path. + if [ "$class" = "Developer ID" ]; then + if [ -f "$NOTARY_API_KEY" ]; then + notarize "$app" + class="$class, notarized" + else + echo "package: $NOTARY_API_KEY not found — signed but not notarized; a download from a browser will still be refused by Gatekeeper" + fi fi echo "==> archive $DIST/$archive" @@ -106,7 +165,7 @@ package_macos() { (cd "$DIST" && shasum -a 256 "$archive" > SHA256SUMS) echo "package: $DIST/$archive ($class)" if [ "$class" = "ad-hoc" ]; then - echo "package: ad-hoc signed — proves packaging, not Gatekeeper; set CORRAL_SIGNING_IDENTITY (and CORRAL_NOTARY_PROFILE) for a distributable build" + echo "package: ad-hoc signed — proves packaging, not Gatekeeper; set CORRAL_SIGNING_IDENTITY for a distributable build, notarized from $NOTARY_API_KEY" fi } diff --git a/scripts/package-smoke b/scripts/package-smoke index 4b336bc..459facf 100755 --- a/scripts/package-smoke +++ b/scripts/package-smoke @@ -71,7 +71,7 @@ case "$PLATFORM" in actual="$(plist_key "$1")" [ "$actual" = "$2" ] || fail "Info.plist $1 is '$actual', expected '$2'" } - expect_key CFBundleIdentifier com.poordeveloper.corral + expect_key CFBundleIdentifier com.carriez.corral expect_key CFBundleExecutable corral-desktop expect_key CFBundleName Corral expect_key CFBundlePackageType APPL diff --git a/scripts/verify-release b/scripts/verify-release index b7a3c99..dded0af 100755 --- a/scripts/verify-release +++ b/scripts/verify-release @@ -30,9 +30,12 @@ cat >&2 <<'MISSING' verify-release: INCOMPLETE — the following gates are not implemented yet: supported provider/version matrix (PR4-PR6) - multi-platform release checks (macOS + Linux) dogfood release-gate evidence (ROADMAP.md §5) +Multi-platform release checks cannot run here: one machine is one OS. They +are .github/workflows/release.yml, which runs this tree's verify, package +and smoke on Ubuntu 24.04 and macOS 15 on the exact tagged commit. + Implement the gate with the work it covers; do not make this script pass by removing the check. MISSING