From 20b94d395af958be45ba686d7aa016731fcfefe2 Mon Sep 17 00:00:00 2001 From: Justin Helmer Date: Mon, 24 Aug 2026 12:49:41 -0700 Subject: [PATCH] ci(release): real install smoke through polylane.com right after publishing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The GitHub release's asset list is a contract with the installer: shipping checksums.txt in v0.2.20 broke every curl install for 15 minutes while all stubbed suites stayed green (RCA: coreplanelabs/polylanedotcom#176). Add a reusable install-smoke workflow that installs the just-published release through the PRODUCTION installer in a throwaway HOME — API-digest and API-blocked (checksums.txt) legs both — and alerts Slack on failure. release.yml calls it right after the release job, so a release that breaks the installer pages within minutes instead of at polylanedotcom's daily install-bytes cron (which gains the same real-install legs as the backstop). Trigger is workflow_call, not `on: release` — releases created with the default GITHUB_TOKEN never trigger other workflows. workflow_dispatch smokes any version on demand and validates the alert path (test_alert). Co-Authored-By: Claude Fable 5 --- .github/workflows/install-smoke.yml | 153 ++++++++++++++++++++++++++++ .github/workflows/release.yml | 16 +++ 2 files changed, 169 insertions(+) create mode 100644 .github/workflows/install-smoke.yml diff --git a/.github/workflows/install-smoke.yml b/.github/workflows/install-smoke.yml new file mode 100644 index 0000000..9ee77b2 --- /dev/null +++ b/.github/workflows/install-smoke.yml @@ -0,0 +1,153 @@ +# Real install smoke: installs the CLI through the PRODUCTION installer at +# polylane.com/install, pinned to a given release, in a throwaway HOME. +# +# Why this exists: the GitHub release's asset list is a contract with the +# installer. Shipping checksums.txt in v0.2.20 broke every curl install for +# 15 minutes while all stubbed test suites stayed green, because only a real +# download exercises that contract (RCA: +# https://github.com/coreplanelabs/polylanedotcom/pull/176). release.yml calls +# this right after publishing, so a release that breaks the installer alerts +# within minutes instead of at polylanedotcom's daily install-bytes cron (the +# backstop, which runs the same real-install legs). +# +# Note the trigger is workflow_call from release.yml, NOT `on: release` — the +# release is created with the default GITHUB_TOKEN, and events caused by that +# token never trigger other workflows. workflow_dispatch exists to smoke any +# version on demand and to validate the Slack alert path (test_alert). +name: install-smoke + +on: + workflow_call: + inputs: + version: + description: "Version to install (with or without the v prefix); empty = latest" + required: false + type: string + default: "" + workflow_dispatch: + inputs: + version: + description: "Version to install (with or without the v prefix); empty = latest" + required: false + type: string + default: "" + test_alert: + description: Post the failure alert even on success, to validate the alert path + type: boolean + default: false + +env: + # Nominal's prod 1Password environment; supplies SLACK_REPORTER_TOKEN for + # the failure alert — the same environment polylanedotcom's install-bytes + # workflow posts through. Requires this repo's OP service account to have + # access to that environment; validate with a test_alert dispatch. + SECRETS_ENVIRONMENT_ID: 4r6j7zk3wmoppaotpeds6xg63m + ONEPASSWORD_CLI_VERSION: 2.38.0-beta.01 + # sha256 of the pinned op zip above; bump both together. + ONEPASSWORD_CLI_SHA256: ab5617d23cf99f5df5fef983c0d1c1f9b3421c28d0c5764863350365d4315857 + +jobs: + smoke: + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read + steps: + # The installer needs Node 20+ to take the release-download path (its + # no-Node branch falls back to Homebrew, which is not what this smokes). + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20.x + + - name: Fetch the production installer + id: fetch + run: curl -fsSL --retry 3 --connect-timeout 10 -m 60 https://polylane.com/install -o /tmp/install.sh + + # Fresh HOME, no PATH or shell-config mutation, no telemetry, agent + # wiring skipped. A piped run is EXPECTED to exit non-zero at the + # sign-in stop — reaching "(step: signin)" proves the whole pre-auth + # flow (download, verify, install, version check) ran against the real + # release assets. + - name: Real install (GitHub API digest verification path) + id: install + env: + VERSION: ${{ inputs.version }} + run: | + set -euo pipefail + V="${VERSION#v}" + TH="$(mktemp -d)" + set +e + OUT="$(HOME="$TH" POLYLANE_NO_PATH=1 DO_NOT_TRACK=1 POLYLANE_VERSION="$V" sh /tmp/install.sh --no-setup &1)" + set -e + printf '%s\n' "$OUT" + printf '%s\n' "$OUT" | grep -qF "sha256 verified" || { echo "::error::real install: no 'sha256 verified' in output"; exit 1; } + printf '%s\n' "$OUT" | grep -qF "installed $TH/.polylane/bin/polylane" || { echo "::error::real install: binary was not installed"; exit 1; } + printf '%s\n' "$OUT" | grep -qF "(step: signin)" || { echo "::error::real install: flow did not reach the sign-in stop"; exit 1; } + + # Same install with api.github.com unreachable: verification must come + # from this release's checksums.txt asset. Guards both the installer's + # fallback path and this release actually shipping the asset. + - name: Real install with the GitHub API blocked (checksums.txt fallback) + id: fallback + env: + VERSION: ${{ inputs.version }} + run: | + set -euo pipefail + V="${VERSION#v}" + SHIM="$(mktemp -d)" + printf '#!/bin/sh\ncase "$*" in *api.github.com*) exit 22 ;; esac\nexec /usr/bin/curl "$@"\n' > "$SHIM/curl" + chmod +x "$SHIM/curl" + TH="$(mktemp -d)" + set +e + OUT="$(HOME="$TH" PATH="$SHIM:$PATH" POLYLANE_NO_PATH=1 DO_NOT_TRACK=1 POLYLANE_VERSION="$V" sh /tmp/install.sh --no-setup &1)" + set -e + printf '%s\n' "$OUT" + printf '%s\n' "$OUT" | grep -qF "sha256 verified" || { echo "::error::fallback install: no 'sha256 verified' (checksums.txt missing from the release or fallback path broken)"; exit 1; } + printf '%s\n' "$OUT" | grep -qF "installed $TH/.polylane/bin/polylane" || { echo "::error::fallback install: binary was not installed"; exit 1; } + + - name: Install 1Password CLI (beta — Environments support) + if: failure() || inputs.test_alert + run: | + set -euo pipefail + curl -sSfLo /tmp/op.zip --retry 3 --connect-timeout 10 \ + "https://cache.agilebits.com/dist/1P/op2/pkg/v${ONEPASSWORD_CLI_VERSION}/op_linux_amd64_v${ONEPASSWORD_CLI_VERSION}.zip" + # Pinned checksum: this binary receives a service-account token, so + # it is never installed unverified. Version bumps update both envs. + echo "${ONEPASSWORD_CLI_SHA256} /tmp/op.zip" | sha256sum -c - + unzip -oq /tmp/op.zip -d /tmp/op-cli + sudo install -m 0755 /tmp/op-cli/op /usr/local/bin/op + + - name: Alert the internal channel + if: failure() || inputs.test_alert + env: + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + TEST_ALERT: ${{ inputs.test_alert }} + VERSION_LABEL: ${{ inputs.version || 'latest' }} + FETCH_OUTCOME: ${{ steps.fetch.outcome }} + INSTALL_OUTCOME: ${{ steps.install.outcome }} + FALLBACK_OUTCOME: ${{ steps.fallback.outcome }} + run: | + set -euo pipefail + # Real failures before the test flag; the test all-clear requires + # the final smoke step's success so a setup failure (outcomes + # "skipped", not "failure") can't post it. + if [ "$FETCH_OUTCOME" = "failure" ]; then + text=":rotating_light: cli install-smoke (${VERSION_LABEL}): fetching polylane.com/install failed — the installer endpoint itself is unreachable. Check the polylane.com deployment. Run: ${RUN_URL}" + elif [ "$INSTALL_OUTCOME" = "failure" ]; then + text=":rotating_light: cli install-smoke: release ${VERSION_LABEL} FAILED a real install through polylane.com/install. The release likely broke the release<->installer contract (asset or digest shape; last time: https://github.com/coreplanelabs/polylanedotcom/pull/176). Fastest mitigations: fix/re-upload the offending asset on the release, or cut a corrected release. The run log carries the full installer output. Run: ${RUN_URL}" + elif [ "$FALLBACK_OUTCOME" = "failure" ]; then + text=":rotating_light: cli install-smoke: release ${VERSION_LABEL} installs via the GitHub API digest but FAILED the API-blocked leg — checksums.txt is missing from the release or doesn't match polylane.mjs, so rate-limited users can't install. Check: curl -fsSL https://github.com/coreplanelabs/cli/releases/download/v${VERSION_LABEL#v}/checksums.txt. Run: ${RUN_URL}" + elif [ "$TEST_ALERT" = "true" ] && [ "$FALLBACK_OUTCOME" = "success" ]; then + text=":white_check_mark: cli install-smoke alert-path test: deliberate post to verify Slack delivery. Real install of ${VERSION_LABEL} through polylane.com/install passed on both the API-digest and checksums.txt paths; no action needed. Run: ${RUN_URL}" + else + text=":rotating_light: cli install-smoke (${VERSION_LABEL}): a setup step failed before the install checks ran, so the release was not smoke-tested. See the run log: ${RUN_URL}" + fi + SMOKE_ALERT_PAYLOAD=$(jq -cn --arg text "$text" '{channel: "polylane-notifications", text: $text}') + export SMOKE_ALERT_PAYLOAD + # --no-masking: see polylanedotcom's install-bytes.yml — op run + # otherwise conceals response substrings that coincide with env + # values, breaking the ok-check while the post itself succeeds. + response=$(op run --no-masking --environment "$SECRETS_ENVIRONMENT_ID" -- sh -c 'curl -sS -X POST https://slack.com/api/chat.postMessage -H "Authorization: Bearer $SLACK_REPORTER_TOKEN" -H "content-type: application/json" -d "$SMOKE_ALERT_PAYLOAD"') + echo "$response" | grep -q '"ok":true' || { echo "slack post failed: $response"; exit 1; } diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6d7deab..2e00291 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -110,6 +110,22 @@ jobs: release-artifacts/polylane.mjs release-artifacts/checksums.txt + # Real install of the just-published release through the production + # installer (polylane.com/install), API-digest and checksums.txt legs both. + # Catches a release that breaks the installer contract within minutes of + # publishing instead of at polylanedotcom's daily install-bytes cron — the + # class of failure that took installs down for 15 minutes on v0.2.20 (RCA: + # https://github.com/coreplanelabs/polylanedotcom/pull/176). Alerts Slack + # on failure; the release itself is already published by then, so the + # mitigation lives in the alert text, not in blocking this workflow. + install-smoke: + name: Real install smoke via polylane.com + needs: release + uses: ./.github/workflows/install-smoke.yml + with: + version: ${{ needs.release.outputs.version }} + secrets: inherit + update-homebrew: name: Bump Homebrew formula needs: release