diff --git a/.github/ISSUE_TEMPLATE/install-failure.yml b/.github/ISSUE_TEMPLATE/install-failure.yml index 811a585..8b87205 100644 --- a/.github/ISSUE_TEMPLATE/install-failure.yml +++ b/.github/ISSUE_TEMPLATE/install-failure.yml @@ -9,7 +9,7 @@ body: Simulator Broker is **Alpha**, **macOS-only**, and needs **Xcode** to create or run iOS Simulators. Install the CLI with `brew install fiveonecode/simulator-broker/simbroker` or - `npm install -g` of the `simbroker-0.1.0-alpha.3.tgz` from GitHub + `npm install -g` of the `simbroker-0.1.0-alpha.4.tgz` from GitHub Releases. Install the operator app with `brew install --cask fiveonecode/simulator-broker/simulator-broker`. @@ -23,7 +23,7 @@ body: label: What install path did you use? options: - Homebrew (`brew install fiveonecode/simulator-broker/simbroker`) - - npm (`npm install -g` of `simbroker-0.1.0-alpha.3.tgz` from GitHub Releases) + - npm (`npm install -g` of `simbroker-0.1.0-alpha.4.tgz` from GitHub Releases) - Homebrew cask (`brew install --cask fiveonecode/simulator-broker/simulator-broker`) - CLI-only (`bash scripts/install_local.sh --cli-only`) - Contributor app + CLI (`npm run install:local`) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dab6d49..0603715 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,6 +1,6 @@ -# Publish the Alpha CLI tarball and the packable npm CLI when a version -# tag is pushed. After merge, create the matching tag (example: -# v0.1.0-alpha.1). This workflow does not build or attach the macOS app. +# Publish the Alpha CLI tarball, its SHA-256 checksum, and the packable npm +# CLI when a version tag is pushed. The operator attaches the signed, +# notarized macOS app zip, completing the four-custom-asset Release contract. name: Release CLI on: @@ -61,7 +61,7 @@ jobs: fi gh release create "${GITHUB_REF_NAME}" \ --title "Simulator Broker ${GITHUB_REF_NAME}" \ - --notes "Alpha CLI and npm package. Install the CLI with \`brew install fiveonecode/simulator-broker/simbroker\` or \`npm install -g\` the attached \`simbroker-${version}.tgz\`. Install the operator app with \`brew install --cask fiveonecode/simulator-broker/simulator-broker\`. Node.js 20 or newer is required. macOS and Xcode are still required to create and run iOS Simulators. This workflow attaches the CLI tarball and npm package; the signed, notarized operator app zip is attached to the same GitHub Release by the operator. See CHANGELOG.md." \ + --notes "Alpha release artifacts. Install the CLI with \`brew install fiveonecode/simulator-broker/simbroker\` or \`npm install -g\` the attached \`simbroker-${version}.tgz\`. Install the operator app with \`brew install --cask fiveonecode/simulator-broker/simulator-broker\`. Node.js 20 or newer is required. macOS and Xcode are still required to create and run iOS Simulators. This workflow attaches three custom assets: the CLI tarball, its SHA-256 checksum, and the npm package. The operator attaches the signed, notarized app zip to complete exactly four custom assets; GitHub source archives are generated separately. See CHANGELOG.md." \ "${prerelease_args[@]}" \ "$asset" \ "$checksum" \ diff --git a/CHANGELOG.md b/CHANGELOG.md index dfca6b5..8156901 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.1.0-alpha.4] - 2026-08-31 + +This release hardens the public install, upgrade, and dashboard paths used for +the first broader Simulator Broker announcement. It also makes the complete +GitHub Release inventory explicit: CLI archive, CLI checksum, npm tarball, and +notarized app zip. + ### Added - `scripts/package_cask_zip.sh` (`npm run package:cask-zip`) writes the @@ -18,6 +25,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 `codesign --verify --deep --strict` and an unstapled app with `xcrun stapler validate`. It does not build, sign, notarize, tag, or publish. +- Correlated installed-app smoke evidence now proves that the launched + `/Applications` app decoded the freshly prepared broker snapshot and + completed a manual refresh under one exact process, executable, state root, + and snapshot generation. + +### Changed + +- Public onboarding now keeps Homebrew CLI, Homebrew cask, guided setup, + reinstall, and direct tagged-asset commands aligned and CI-gated. Bash PATH + persistence follows the login files Bash actually reads, while reinstall + keeps host configuration and broker state unless reset is explicit. +- GitHub workflows use maintained Node 24 action runtimes with explicit cache + behavior, and the agent/reliability contract now examines interacting + conditions, degraded operation, recovery, and residual risk without adding + unnecessary operational coupling. +- The app's timestamp wording and Automatic shutdown guidance now distinguish + snapshot age from refresh time and explain intentional warm Simulator reuse. +- Completion fixtures and durable task evidence remain public-safe even when a + checkout lives beneath a private machine path. + +### Fixed + +- `simbroker setup --help` follows the normal help path instead of entering + setup dispatch, and installed daemon health is checked against the current + runtime across upgrades before commands are exposed. +- The Simulators pane remains usable at the minimum supported window size. +- Dashboard refresh failures now fail closed: stale service authority cannot + enable mutations, queued or cancelled setup/refresh work cannot publish + after lifecycle loss, and verified recovery restores authority only after a + current exact-status success. ## [0.1.0-alpha.3] - 2026-08-29 diff --git a/Casks/simulator-broker.rb b/Casks/simulator-broker.rb index 45cfcaa..70a4709 100644 --- a/Casks/simulator-broker.rb +++ b/Casks/simulator-broker.rb @@ -1,6 +1,6 @@ cask "simulator-broker" do - version "0.1.0-alpha.3" - sha256 "42d5d441a40d551201afa23df303738475b559d9b7d1c98f0403aa1ff1822c8b" + version "0.1.0-alpha.4" + sha256 "d44c4ba8318338c5e009ed2e71aa6fea03e6167698a4a60df7ce5b865f5e3963" url "https://github.com/fiveonecode/simulator-broker/releases/download/v#{version}/Simulator-Broker-#{version}.zip" name "Simulator Broker" diff --git a/Formula/simbroker.rb b/Formula/simbroker.rb index b13831e..d721813 100644 --- a/Formula/simbroker.rb +++ b/Formula/simbroker.rb @@ -1,8 +1,8 @@ class Simbroker < Formula desc "Local iOS Simulator control plane CLI" homepage "https://github.com/fiveonecode/simulator-broker" - url "https://github.com/fiveonecode/simulator-broker/releases/download/v0.1.0-alpha.3/simulator-broker-0.1.0-alpha.3-cli.tar.gz" - sha256 "2607b6756b2785d433c00f14ccbf9d56a24d8a607668994cc2bdd611c85c2e86" + url "https://github.com/fiveonecode/simulator-broker/releases/download/v0.1.0-alpha.4/simulator-broker-0.1.0-alpha.4-cli.tar.gz" + sha256 "1e04e4e9f7c0b372722b80e057b63dda87e12d7d5cbf7043d084826f0ea57503" license "MIT" depends_on macos: :sonoma diff --git a/README.md b/README.md index 0fc4e82..1288c0a 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ simbroker idle enable --grace-seconds <60-86400> --actor-type human --actor-id < Other CLI install options: ```bash -npm install -g https://github.com/fiveonecode/simulator-broker/releases/download/v0.1.0-alpha.3/simbroker-0.1.0-alpha.3.tgz +npm install -g https://github.com/fiveonecode/simulator-broker/releases/download/v0.1.0-alpha.4/simbroker-0.1.0-alpha.4.tgz simbroker --help ``` @@ -102,8 +102,8 @@ are also attached to those releases. The archive contains a versioned top-level directory: ```bash -tar -xzf simulator-broker-0.1.0-alpha.3-cli.tar.gz -./simulator-broker-0.1.0-alpha.3-cli/bin/simbroker --help +tar -xzf simulator-broker-0.1.0-alpha.4-cli.tar.gz +./simulator-broker-0.1.0-alpha.4-cli/bin/simbroker --help ``` `simbroker` help and `simbroker doctor` print human-readable text by default. diff --git a/SECURITY.md b/SECURITY.md index 0f4fc0c..19c43f6 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -3,11 +3,16 @@ ## Supported Versions Security fixes are considered for the current `main` branch and for the latest -tagged Alpha (`0.1.0-alpha.3`). Older Alpha tags are not supported. The -published CLI artifacts are the GitHub Release tarball, the Homebrew -formula that installs that tarball, and the packable `simbroker` npm -package, and the signed, notarized operator app zip -`Simulator-Broker-0.1.0-alpha.3.zip`. +tagged Alpha (`0.1.0-alpha.4`). Older Alpha tags are not supported. The +published Alpha has exactly four custom GitHub Release assets: + +1. `simulator-broker-0.1.0-alpha.4-cli.tar.gz` +2. `simulator-broker-0.1.0-alpha.4-cli.tar.gz.sha256` +3. `simbroker-0.1.0-alpha.4.tgz` +4. `Simulator-Broker-0.1.0-alpha.4.zip` + +The Homebrew formula and cask install the matching CLI and signed, notarized +app archives. GitHub's generated source archives appear separately. ## Reporting A Vulnerability diff --git a/docs/getting-started.md b/docs/getting-started.md index 1adcb81..7c44ecf 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -51,7 +51,7 @@ does not require XcodeGen. The app still needs the CLI installed separately. ## Install the CLI with npm ```bash -npm install -g https://github.com/fiveonecode/simulator-broker/releases/download/v0.1.0-alpha.3/simbroker-0.1.0-alpha.3.tgz +npm install -g https://github.com/fiveonecode/simulator-broker/releases/download/v0.1.0-alpha.4/simbroker-0.1.0-alpha.4.tgz command -v simbroker simbroker --help ``` @@ -76,13 +76,23 @@ To install from a tagged Alpha without cloning, download then run: ```bash -tar -xzf simulator-broker-0.1.0-alpha.3-cli.tar.gz -./simulator-broker-0.1.0-alpha.3-cli/bin/simbroker --help +tar -xzf simulator-broker-0.1.0-alpha.4-cli.tar.gz +./simulator-broker-0.1.0-alpha.4-cli/bin/simbroker --help ``` The archive is the Node CLI only and contains that versioned top-level directory. +A complete tagged Alpha has exactly four custom GitHub Release assets: + +1. `simulator-broker--cli.tar.gz` +2. `simulator-broker--cli.tar.gz.sha256` +3. `simbroker-.tgz` +4. `Simulator-Broker-.zip` + +GitHub's generated source archives appear separately and are not part of that +four-asset install contract. + - If Homebrew is present and `$(brew --prefix)/bin` is writable, the wrapper is installed there so a new login shell already has it on `PATH`. - Otherwise the wrapper is installed to `~/.local/bin` and the installer diff --git a/docs/status.md b/docs/status.md index 8bb1c90..5bef1cb 100644 --- a/docs/status.md +++ b/docs/status.md @@ -35,7 +35,9 @@ This Alpha already includes: `simulators repair --alias ` - CLI-only install through `bash scripts/install_local.sh --cli-only`, plus the contributor app+CLI path `npm run install:local` -- tagged Alpha CLI tarball through `npm run package:cli` and GitHub Releases +- tagged Alpha GitHub Releases with exactly four custom assets: the CLI + tarball, its `.sha256` checksum, the packable npm tarball, and the signed, + notarized app zip. GitHub's generated source archives appear separately - public Node test CI: GitHub-hosted Ubuntu for broker-core and harness-adoption plus the public docs contract, GitHub-hosted macOS for client tests. Tagged releases run the public docs contract before packaging. diff --git a/docs/test/front-door.test.mjs b/docs/test/front-door.test.mjs index df3c4a8..d1fe75f 100644 --- a/docs/test/front-door.test.mjs +++ b/docs/test/front-door.test.mjs @@ -15,6 +15,58 @@ function readRepoFile(relativePath) { const packageJson = JSON.parse(readRepoFile("package.json")); const version = packageJson.version; const cliArchiveDirectory = `simulator-broker-${version}-cli`; +const customReleaseAssetTemplates = [ + "simulator-broker--cli.tar.gz", + "simulator-broker--cli.tar.gz.sha256", + "simbroker-.tgz", + "Simulator-Broker-.zip", +]; + +function assertExactCustomReleaseAssetInventory(markdown) { + const marker = "A complete tagged Alpha has exactly four custom GitHub Release assets:"; + const markerIndex = markdown.indexOf(marker); + assert.notEqual(markerIndex, -1, "release asset inventory marker must be present"); + + const lines = markdown.slice(markerIndex + marker.length).trimStart().split(/\r?\n/); + const assets = []; + for (const line of lines) { + const match = line.match(/^\d+\. `([^`]+)`$/); + if (match) { + assets.push(match[1]); + continue; + } + if (assets.length > 0) { + break; + } + } + assert.deepEqual(assets, customReleaseAssetTemplates); +} + +function assertExactWorkflowAssetArguments(workflow) { + const lines = workflow.split(/\r?\n/); + const createIndex = lines.findIndex((line) => line.includes('gh release create "${GITHUB_REF_NAME}"')); + assert.notEqual(createIndex, -1, "gh release create command must be present"); + + const commandLines = []; + for (let index = createIndex; index < lines.length; index += 1) { + const line = lines[index].trim(); + commandLines.push(line); + if (!line.endsWith("\\")) { + break; + } + } + const normalizedLines = commandLines.map((line) => line.replace(/\s+\\$/, "")); + assert.equal(normalizedLines.length, 7, "release command must keep its exact flag and asset shape"); + assert.equal(normalizedLines[0], 'gh release create "${GITHUB_REF_NAME}"'); + assert.equal(normalizedLines[1], '--title "Simulator Broker ${GITHUB_REF_NAME}"'); + assert.match(normalizedLines[2], /^--notes ".*"$/); + assert.equal(normalizedLines[3], '"${prerelease_args[@]}"'); + assert.deepEqual( + normalizedLines.slice(4), + ['"$asset"', '"$checksum"', '"$npm_asset"'], + "the tag workflow must attach exactly the three operator-replaceable CLI/npm assets", + ); +} function firstScreen(markdown) { const lines = markdown.split(/\r?\n/); @@ -434,9 +486,14 @@ test("release workflow packages the CLI tarball on version tags", () => { assertWorkflowJobRunsExactCommand(release, "release", "npm run test:docs"); assert.ok(release.includes("npm run package:cli")); assert.ok(release.includes("npm run package:npm")); + assert.ok(release.includes('asset="artifacts/cli/simulator-broker-${version}-cli.tar.gz"')); + assert.ok(release.includes('checksum="${asset}.sha256"')); assert.ok(release.includes("artifacts/npm/simbroker-${version}.tgz")); assert.ok(release.includes("gh release create")); assert.ok(release.includes("--prerelease")); + assertExactWorkflowAssetArguments(release); + assert.match(release, /CLI tarball, its SHA-256 checksum, and the npm package/); + assert.match(release, /complete exactly four custom assets/); assert.equal(release.includes("test:app"), false); assert.equal( release.replace(/\s+/g, " ").includes("This release is not a Homebrew formula, notarized app, or npm package."), @@ -449,6 +506,53 @@ test("release workflow packages the CLI tarball on version tags", () => { "tag release notes must not claim the notarized app zip is absent", ); assert.ok(release.includes("brew install --cask fiveonecode/simulator-broker/simulator-broker")); + + const withUnexpectedAsset = release.replace( + ' "$npm_asset"', + ' "$npm_asset" \\\n "unexpected.zip"', + ); + assert.notEqual(withUnexpectedAsset, release, "negative fixture must add a fourth attachment"); + assert.throws( + () => assertExactWorkflowAssetArguments(withUnexpectedAsset), + "a fourth workflow attachment must fail the exact asset assertion", + ); + const withInterspersedAsset = release.replace( + ' "${prerelease_args[@]}"', + ' "unexpected.zip" \\\n "${prerelease_args[@]}"', + ); + assert.notEqual(withInterspersedAsset, release, "negative fixture must intersperse an attachment"); + assert.throws( + () => assertExactWorkflowAssetArguments(withInterspersedAsset), + "a positional attachment interspersed with flags must fail the exact asset assertion", + ); +}); + +test("public release contract inventories exactly four custom assets", () => { + const spec = readRepoFile("spec/build-and-test.md"); + const gettingStarted = readRepoFile("docs/getting-started.md"); + const status = readRepoFile("docs/status.md"); + const security = readRepoFile("SECURITY.md"); + + assertExactCustomReleaseAssetInventory(spec); + assertExactCustomReleaseAssetInventory(gettingStarted); + assert.match(status, /exactly four custom assets/); + for (const template of customReleaseAssetTemplates) { + assert.ok( + security.includes(template.replace("", version)), + `SECURITY must name ${template}`, + ); + } + assert.match(spec, /GitHub's generated source archives[\s\S]*not part of this custom-asset count/); + + const withoutChecksum = spec.replace( + "2. `simulator-broker--cli.tar.gz.sha256`\n", + "", + ); + assert.notEqual(withoutChecksum, spec, "negative fixture must remove the checksum asset"); + assert.throws( + () => assertExactCustomReleaseAssetInventory(withoutChecksum), + "omitting the CLI checksum must fail the release-contract assertion", + ); }); test("on-demand OCR review uses the maintained hosted action runtime contract", () => { diff --git a/package-lock.json b/package-lock.json index 31c34b5..f96907a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "simulator-broker-app", - "version": "0.1.0-alpha.3", + "version": "0.1.0-alpha.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "simulator-broker-app", - "version": "0.1.0-alpha.3", + "version": "0.1.0-alpha.4", "license": "MIT", "engines": { "node": ">=20" diff --git a/package.json b/package.json index 62cdcd5..277044c 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "test:package-smoke": "bash ./scripts/package_smoke.sh", "verify:public-surface": "node client/public-surface.mjs" }, - "version": "0.1.0-alpha.3", + "version": "0.1.0-alpha.4", "description": "Local simulator broker and macOS operator app for coordinated iOS Simulator workflows", "license": "MIT", "repository": { diff --git a/packages/simbroker/README.md b/packages/simbroker/README.md index 6710e38..cf80699 100644 --- a/packages/simbroker/README.md +++ b/packages/simbroker/README.md @@ -4,7 +4,7 @@ Alpha CLI for Simulator Broker. Node.js 20 or newer is required. Creating and running iOS Simulators still requires macOS and Xcode. ```bash -npm install -g https://github.com/fiveonecode/simulator-broker/releases/download/v0.1.0-alpha.3/simbroker-0.1.0-alpha.3.tgz +npm install -g https://github.com/fiveonecode/simulator-broker/releases/download/v0.1.0-alpha.4/simbroker-0.1.0-alpha.4.tgz simbroker --help ``` diff --git a/packages/simbroker/package.json b/packages/simbroker/package.json index a4f2c05..3424330 100644 --- a/packages/simbroker/package.json +++ b/packages/simbroker/package.json @@ -1,6 +1,6 @@ { "name": "simbroker", - "version": "0.1.0-alpha.3", + "version": "0.1.0-alpha.4", "private": false, "description": "CLI for the local iOS Simulator control plane", "license": "MIT", diff --git a/spec/README.md b/spec/README.md index 0bba125..1144605 100644 --- a/spec/README.md +++ b/spec/README.md @@ -56,7 +56,7 @@ This repo exists to develop a reusable local simulator broker: - local install, local-debug portable packaging, Release distribution packaging, and onboarding flows now exist through `install_local.sh`, `install_local.sh --cli-only`, `package_local.sh`, `package_distribution.sh`, `test:install-smoke`, `test:package-smoke`, `host init --bootstrap-config`, and `project init` - the published onboarding docs now distinguish CLI-only install, repo-local contributor app+CLI install, local-debug portable bundling, and signed distribution packaging; a new login shell should resolve `simbroker` after install without sourcing `env.sh` - `CONTRIBUTING.md` publishes a public-patch track (Node.js 20 and the Node test suites, no harness session) and a labeled maintainer/agent harness track; `agent:complete` enforcement is unchanged -- tagged Alpha `0.1.0-alpha.3` publishes a CLI tarball from `scripts/package_cli.sh`, the packable npm CLI, and a notarized operator app zip that include guided `simbroker setup`, and runs broker-core plus harness-adoption on GitHub-hosted Ubuntu and client tests on GitHub-hosted macOS; the macOS app suite and home-path public-surface scan are not on those jobs. Reproduce the cask zip with `scripts/package_cask_zip.sh` after Developer ID signing and notarization. +- tagged Alpha `0.1.0-alpha.4` publishes exactly four custom GitHub Release assets: a CLI tarball from `scripts/package_cli.sh`, its SHA-256 checksum, the packable npm CLI, and a notarized operator app zip. The tag workflow runs the public docs, public-surface, broker-core, client, and harness-adoption checks on GitHub-hosted Ubuntu before attaching the first three assets; the operator attaches the app zip after Developer ID signing and notarization. GitHub's generated source archives are not custom assets. Public pull-request CI remains split between Ubuntu and macOS, while the macOS app suite and home-path public-surface scan stay local. - public pull-request CI and the tagged-release workflow run `npm run test:docs` so archive paths, install guidance, and workflow gates cannot drift without a failing check diff --git a/spec/build-and-test.md b/spec/build-and-test.md index 790102f..f68b5df 100644 --- a/spec/build-and-test.md +++ b/spec/build-and-test.md @@ -126,12 +126,14 @@ A first extracted implementation slice now exists: fixtures inject `processController.currentPid` so hardcoded fixture PIDs cannot match the GitHub Actions test-runner pid. Containment still skips the live `process.pid` when `currentPid` is omitted. -- tagged versions such as `v0.1.0-alpha.3` attach the CLI tarball, the - packable `simbroker-.tgz`, and the notarized - `Simulator-Broker-.zip` to a GitHub Release. The Homebrew - formula and cask pin the operator-packed checksums of those tagged - assets. `.github/workflows/release.yml` may rebuild the CLI and npm - tarballs on the tag. Its Ubuntu `test:client` run must finish within the +- tagged versions such as `v0.1.0-alpha.4` attach exactly four custom assets + to a GitHub Release: the CLI tarball, its `.sha256` checksum, the packable + `simbroker-.tgz`, and the notarized + `Simulator-Broker-.zip`. GitHub-generated source archives are not + custom assets. The Homebrew formula and cask pin the operator-packed + checksums of the CLI tarball and app zip. `.github/workflows/release.yml` + may rebuild the CLI, checksum, and npm tarball on the tag. Its Ubuntu + `test:client` run must finish within the 30-minute job budget; issue `#9`'s nested full-repo scan was removed, so a timeout is a regression rather than an expected release condition. After the workflow succeeds, attach or `--clobber` operator-packed assets as in @@ -147,6 +149,19 @@ A first extracted implementation slice now exists: The current deterministic verification contract includes implementation tests plus spec integrity. +### Release asset contract + +A complete tagged Alpha has exactly four custom GitHub Release assets: + +1. `simulator-broker--cli.tar.gz` +2. `simulator-broker--cli.tar.gz.sha256` +3. `simbroker-.tgz` +4. `Simulator-Broker-.zip` + +The tag workflow attaches the first three. The operator attaches the signed, +notarized, stapled app zip as the fourth. GitHub's generated source archives +appear separately and are not part of this custom-asset count. + ## Tagged Alpha ship This is the operator sequence for a tagged Alpha whose Homebrew formula @@ -174,10 +189,12 @@ not reinstall a live machine. tree. 8. Open a pull request. After merge, create tag `v` and wait for `.github/workflows/release.yml` to finish or fail. If that job - did not create the GitHub Release, create it with the operator-packed - CLI tarball, npm tgz, and app zip. If the job created the release, - `gh release upload` the app zip and `--clobber` CLI/npm assets whose - hashes differ from the formula pins. Operator-packed checksums remain + did not create the GitHub Release, create it with exactly the four + operator-packed paths: the CLI tarball, CLI `.sha256`, npm tgz, and app + zip listed in Release asset contract. If the job created the release, + `gh release upload` the app zip and `--clobber` all three workflow-built + assets (CLI tarball, CLI checksum, and npm tgz) whose bytes differ from + the operator-packed copies. The operator-packed CLI and app hashes remain the Homebrew source of truth. 9. Run `scripts/sync_homebrew_tap.sh` against a `fiveonecode/homebrew-simulator-broker` checkout.