Files
hermes-agent/.github/workflows/bootstrap-installer.yml
ethernet 13ebc163a1 ci: fold the PowerShell installer job into the tests-os Windows lanes
installer-tests.yml predates nothing it still owned. Its pytest step
(test_source_launcher_stages.py) is platforms("windows") and already runs in
both tests-os Windows lanes, so every installer PR ran it twice. The
`installer` lane never gated anything on its own either: every path that set
it also sets `python`, which gates tests-os.

The two standalone scripts/tests/*.ps1 suites become one platforms("windows")
pytest file parametrized over Windows PowerShell 5.1 and pwsh 7, so
list_os_marked_tests picks them up with everything else. The `installer` lane
goes away from the classifier, detect-changes, ci.yaml and the
all-checks-pass gate; the classifier contract now pins that install.ps1 and
its suites turn `python` on.
2026-09-23 17:19:29 -04:00

157 lines
6.9 KiB
YAML

name: Bootstrap installer
# Exercises the bootstrap-installer path on PRs that can affect it: the
# POSIX shell installer (scripts/install.sh), its generated pin fragments,
# and the version stamp the `complete` stage ships. The PowerShell installer's
# native tests run in the tests-os Windows lanes; this lane runs the
# protocol/stamp cross-checks plus a whole current installer against a tiny
# application graph. The latter runs real PM/uv and generated launchers, not
# every production extra. Tool download/hash tests remain separate.
on:
workflow_call:
permissions:
contents: read
concurrency:
group: bootstrap-installer-${{ github.ref_type == 'tag' && github.run_id || github.ref }}
cancel-in-progress: ${{ github.ref_type != 'tag' }}
jobs:
posix:
name: install.sh protocol + small fresh-install E2E
runs-on: ubuntu-24.04
timeout-minutes: 15
# NOTE: `runner.temp` is only available in step-level env, not job-level
# env — a job-env `${{ runner.temp }}` makes GitHub reject the workflow
# graph at dispatch (0 jobs). Each step that needs these exports them.
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
# The mirror step pushes HEAD to a fresh bare repo — a shallow
# checkout can't (git: "shallow update not allowed").
fetch-depth: 0
- name: Stage manifest is well-formed protocol v1 (piped, as `curl | bash` runs it)
shell: bash
run: |
cat scripts/install.sh | bash -s -- --manifest | python3 -c '
import json, sys
m = json.load(sys.stdin)
assert m["protocol_version"] == 1, m
names = [s["name"] for s in m["stages"]]
expected = ["prerequisites", "repository", "venv", "python-deps",
"config", "products", "setup", "gateway", "complete"]
assert names == expected, names
assert m["stages"][-1]["name"] == "complete"
print("stage manifest ok:", " -> ".join(names))
'
- name: Generated bootstrap pins match pm/lock.json
shell: bash
run: python3 scripts/gen-bootstrap-pins.py --check
- name: Publish the PR checkout as the install source
id: source
shell: bash
run: |
# The installer clones --branch <branch>; a PR checkout is a
# detached HEAD, so mirror it onto a named branch first.
mirror="$RUNNER_TEMP/source-mirror.git"
git init --bare "$mirror"
sha="$(git rev-parse HEAD)"
git push "$mirror" "HEAD:refs/heads/ci-under-test"
echo "mirror=$mirror" >> "$GITHUB_OUTPUT"
echo "sha=$sha" >> "$GITHUB_OUTPUT"
- name: Check repository/config/completion protocol (not a working install)
shell: bash
env:
# install.sh reads HERMES_INSTALL_DIR, not INSTALL_DIR (its default
# is $HOME/.hermes/hermes-agent); the marker/verification steps
# below use the same env var so they agree on the install root.
HERMES_INSTALL_DIR: ${{ runner.temp }}/bootstrap-install
HERMES_HOME: ${{ runner.temp }}/hermes-home
HERMES_RUNTIME_DIR: ${{ runner.temp }}/hermes-tools
HERMES_REPO_URL: ${{ steps.source.outputs.mirror }}
run: |
set -euo pipefail
sha="${{ steps.source.outputs.sha }}"
run_stage() {
echo "::group::stage $1"
bash scripts/install.sh --branch ci-under-test --commit "$sha" \
--non-interactive --stage "$1" --json
echo "::endgroup::"
}
run_stage prerequisites
run_stage repository
run_stage config
run_stage complete
test -f "$HERMES_INSTALL_DIR/.hermes-bootstrap-complete"
- name: Verify the shipped version stamp
shell: bash
env:
HERMES_INSTALL_DIR: ${{ runner.temp }}/bootstrap-install
run: |
python3 scripts/verify-bootstrap-version-stamp.py \
--stamp "$HERMES_INSTALL_DIR/.hermes-bootstrap-complete" \
--repo "$HERMES_INSTALL_DIR" \
--expect-commit "${{ steps.source.outputs.sha }}" \
--expect-branch ci-under-test \
--no-source-stamp # the protocol check above skips the products stage
- name: The pinned commit is on the branch the installer cloned
shell: bash
env:
HERMES_INSTALL_DIR: ${{ runner.temp }}/bootstrap-install
run: |
# The stamp must describe the bytes actually checked out: the
# installed repo's HEAD is exactly the commit the installer pinned.
head="$(git -C "$HERMES_INSTALL_DIR" rev-parse HEAD)"
test "$head" = "${{ steps.source.outputs.sha }}" \
|| { echo "::error::installed HEAD $head != pinned ${{ steps.source.outputs.sha }}"; exit 1; }
- name: Prepare tools for the real small-graph installer test
uses: ./.github/actions/setup-pm
- name: Prepare isolated acceptance-test dependencies
shell: bash
run: python -m scripts.ci.python_packages pytest==9.1.1 pytest-asyncio==1.3.0 ruamel.yaml==0.18.17 packaging==26.0
- name: Current installer to PM worker to published command
shell: bash
env:
HERMES_TEST_FILE_RETRIES: '0'
run: bash scripts/run_tests.sh tests/scripts/test_fresh_source_install.py -q -j 1
windows:
name: install.ps1 protocol surface
runs-on: windows-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
# Windows PowerShell 5.1 is what ships with Windows and what `irm | iex`
# lands in for most users — the protocol surface must parse there.
- name: Protocol version + stage manifest (Windows PowerShell 5.1)
shell: powershell
run: |
$pv = powershell -NoProfile -ExecutionPolicy Bypass -File scripts/install.ps1 -ProtocolVersion
if ("$pv" -ne "1") { Write-Error "protocol version '$pv' != 1"; exit 1 }
$json = powershell -NoProfile -ExecutionPolicy Bypass -File scripts/install.ps1 -Manifest | ConvertFrom-Json
if ($json.protocol_version -ne 1) { Write-Error "manifest protocol_version != 1"; exit 1 }
$names = @($json.stages | ForEach-Object { $_.name })
if ($names -notcontains "complete") { Write-Error "manifest missing complete stage"; exit 1 }
Write-Host "stage manifest ok: $($names -join ' -> ')"
- name: Protocol version + stage manifest (pwsh 7)
shell: pwsh
run: |
$pv = pwsh -NoProfile -File scripts/install.ps1 -ProtocolVersion
if ("$pv" -ne "1") { Write-Error "protocol version '$pv' != 1"; exit 1 }
$json = pwsh -NoProfile -File scripts/install.ps1 -Manifest | ConvertFrom-Json
if ($json.protocol_version -ne 1) { Write-Error "manifest protocol_version != 1"; exit 1 }
Write-Host "stage manifest ok (pwsh 7)"