Files
hermes-agent/tests/install/macos-bundle-smoke.sh
ethernet b37acb8389 feat(release): dynamic R2-owned channels and preview retirement (rounds 1-2)
Checkpoint before round-3 reduction (two-tier retirement derived from
product identity). Includes:

- R2 channel protocol (release_channels.py, channel-protocol.ts): records,
  builds, manifests, retired channels with pinned destinationHead and
  receiverProtocol; fail-closed readers in both languages
- One shared native manifest/feed writer (scripts/bundles/channel_artifacts.py)
- Scoped/disposable R2 publication, fork isolation before credential
  access, canary bootstrap verification of its own promoted outputs
- Channel source CLI: typed SourceTarget, source-channel resolution,
  retirement downgrade refusal
- Desktop channel resolver/strategy, install-stamp/build-stamp receiver
  ownership (stable-owned S/T candidates), single-flight updater operation
- Cross-package retirement machinery (receiver/host/preservation/
  compatibility/connections/dialog/discovery, backup_migration strict
  snapshots with retained-link inventory, empty-dir preservation,
  connection-collision resolution, URL-credential rejection)
- Native install harness (tests/install/channel-retirement-*) and
  install-e2e retirement jobs
- checkout-source.test.ts transport shim now covers build_opener().open
  (was silently hitting the real network in CI)

Removed secondary certification protocol (channel_qualification.py) per
approved round-2 plan. All focused suites green at checkpoint; native
cross-package journeys unverified (to be deleted in round 3).
2026-09-14 10:26:36 -04:00

80 lines
3.8 KiB
Bash

#!/usr/bin/env bash
# Consumes receipt-verified bytes; never builds, repairs, or relaxes Gatekeeper.
set -euo pipefail
fail() { printf '%s\n' "bundle smoke: $*" >&2; exit 1; }
[[ ${GITHUB_ACTIONS:-} == true && ${RUNNER_ENVIRONMENT:-} == github-hosted && $(uname -s) == Darwin ]] ||
fail 'Disposable native GitHub-hosted macOS runner required'
artifact= arch= commit= tag= channel_request= work= out=
while (($#)); do
(($# >= 2)) || fail "Missing value for $1"
case "$1" in
--artifact) artifact=$2;; --arch) arch=$2;; --commit) commit=$2;;
--channel-request) channel_request=$2;; --tag) tag=$2;; --work) work=$2;; --out) out=$2;;
*) fail "Unknown argument: $1";;
esac
shift 2
done
[[ -f $artifact && $artifact == /* && -n $work && -n $out ]] || fail 'Absolute artifact, work and out are required'
[[ $arch == arm64 || $arch == x64 ]] || fail 'Expected arm64 or x64'
[[ $commit =~ ^[a-f0-9]{40}$ ]] || fail 'Expected exact lowercase full commit SHA'
# uname can report x86_64 under Rosetta; inspect the physical host as well.
host=x64
if [[ $(/usr/sbin/sysctl -in hw.optional.arm64) == 1 ]]; then host=arm64; fi
[[ $host == "$arch" ]] || fail "Requested $arch on native $host host"
[[ $(uname -m) == "${arch/x64/x86_64}" ]] || fail 'Adapter must not run under Rosetta'
assets=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/e2e-assets" && pwd -P)
node=$(command -v node)
[[ $("$node" -p 'process.arch') == "$arch" ]] || fail 'Driver Node must also use the native architecture'
metadata=$assets/bundle-smoke-metadata.mjs
identity_args=(--commit "$commit")
if [[ -n $tag ]]; then identity_args+=(--tag "$tag"); fi
if [[ -n $channel_request ]]; then identity_args+=(--channel-request "$channel_request"); fi
"$node" "$metadata" identity "${identity_args[@]}" >/dev/null
"$node" "$metadata" prepare --work "$work" --out "$out"
exec > >(tee "$out/native-install.log") 2>&1
mountpoint=
cleanup() {
status=$?
trap - EXIT
if [[ -n $mountpoint ]]; then
if ! /usr/bin/hdiutil detach "$mountpoint"; then status=1; fi
fi
printf '{"exitCode":%s}\n' "$status" > "$out/native-install-exit.json"
exit "$status"
}
trap cleanup EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
/usr/bin/shasum -a 256 "$artifact" > "$out/artifact-before.sha256"
mkdir "$work/install" "$work/home" "$work/user-data"
shopt -s nullglob
case "$artifact" in
*.dmg)
mountpoint=$work/mount
mkdir "$mountpoint"
/usr/bin/hdiutil attach -readonly -nobrowse -noautoopen -mountpoint "$mountpoint" "$artifact"
apps=("$mountpoint"/*.app)
((${#apps[@]} == 1)) || fail 'DMG must contain exactly one top-level .app'
[[ -d ${apps[0]} && ! -L ${apps[0]} ]] || fail 'Application must be a directory, not a link'
/usr/bin/ditto --rsrc --extattr --acl "${apps[0]}" "$work/install/$(basename "${apps[0]}")"
/usr/bin/hdiutil detach "$mountpoint"
mountpoint=
;;
*.zip) /usr/bin/ditto -x -k --rsrc --extattr --acl "$artifact" "$work/install";;
*) fail 'Expected a receipt-selected DMG or ZIP';;
esac
apps=("$work/install"/*.app)
((${#apps[@]} == 1)) || fail 'Installed artifact must contain exactly one top-level .app'
[[ -d ${apps[0]} && ! -L ${apps[0]} ]] || fail 'Installed application must not be a link'
"$node" "$metadata" verify-mac --app "${apps[0]}" --arch "$arch" "${identity_args[@]}" --out "$out/installed-identity.json"
exe=$("$node" -e 'console.log(require(process.argv[1]).exe)' "$out/installed-identity.json")
root=$("$node" -e 'console.log(require(process.argv[1]).root)' "$out/installed-identity.json")
/usr/bin/shasum -a 256 "$artifact" > "$out/artifact-after.sha256"
cmp "$out/artifact-before.sha256" "$out/artifact-after.sha256"
cd "$work"
"$node" "$assets/desktop-smoke.ts" --exe "$exe" --root "$root" --origin bundled \
--home "$work/home" --user-data "$work/user-data" --out "$out" --phase installed --expect-commit "$commit"