I'm siGit, running on siGit Code & Deploy platform, browsing siGit Code coding agent repo.
Build the Code Assurance runner image here, with sigit baked in
ghcr.io/getsigit/sigit-code-assurance-runner used to be built in sigit-app. It now builds from packaging/assurance-runner/Dockerfile, dispatched by release-github after each release, so every image carries that release's sigit at /usr/local/bin/sigit and the assurance agent no longer downloads it per run. The image drops the warm pnpm store (jobs install their own dependencies) and adds bubblewrap for the agent's sandbox. Pull requests that touch it build it without pushing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
foodandtravel committed
Oct 29, 2026 at 21:04 UTC
01e8c22dd07e11c57635149f1426d7159b7ef3db
4 files changed
+165
-1
.agents/AGENTS.md
+11
@@ -560,6 +560,17 @@ GitHub release, so `release-github` builds the binaries, attaches the assets, an
560
is logged as a warning rather than failing the others, so an unconfigured channel does not block a
561
release. Their inputs live in `packaging/`.
562
563
+The same fan-out dispatches `release-assurance-image`, which builds
564
+`ghcr.io/getsigit/sigit-code-assurance-runner` from `packaging/assurance-runner/Dockerfile`. That
565
+is the guest image of the Fireactions pool `sigit-code-assurance`, where siGit Code Assurance runs
566
+a release candidate's tests and its QA agent in a fresh microVM. Each sigit release rebuilds it
567
+with that release's `sigit` at `/usr/local/bin/sigit`, so the agent uses it instead of downloading
568
+one, and pushes `:latest`, `:<tag>` and `:<sha>`. The image carries no app dependencies and no
569
+secrets. Its `PLAYWRIGHT_VERSION` must match the consuming repo's `@playwright/test`. A pull
570
+request touching the image builds it without pushing. One-time setup on the package: it is
571
+public (the Fireactions hosts pull it without credentials), and under "Manage Actions access"
572
+getsigit/sigit has write access.
573
+
574
The Windows binaries link the MSVC C runtime statically (`+crt-static` in `.cargo/config.toml`).
575
winget, Scoop, npm, PyPI and NuGet all ship the bare exe with no installer, so a dynamic build
576
that imports `VCRUNTIME140.dll` fails to load on a machine without the VC++ Redistributable
.github/workflows/release-assurance-image.yml
new
+78
@@ -0,0 +1,78 @@
1
+name: Assurance runner image
2
+
3
+# Builds ghcr.io/getsigit/sigit-code-assurance-runner, the guest image of the
4
+# siGit Code Assurance Fireactions pool, with the released `sigit` baked in.
5
+# release-github dispatches this once the release assets exist. A pull request
6
+# that touches the image builds it without pushing, so a broken Dockerfile
7
+# shows up before merge.
8
+on:
9
+ workflow_dispatch:
10
+ inputs:
11
+ tag:
12
+ description: "Release tag (e.g. v0.1.1)"
13
+ required: true
14
+ pull_request:
15
+ paths:
16
+ - packaging/assurance-runner/**
17
+ - .github/workflows/release-assurance-image.yml
18
+
19
+concurrency:
20
+ group: ${{ github.workflow }}-${{ github.event.inputs.tag || github.ref }}
21
+ cancel-in-progress: false
22
+
23
+permissions:
24
+ contents: read
25
+ packages: write
26
+
27
+env:
28
+ IMAGE: ghcr.io/getsigit/sigit-code-assurance-runner
29
+
30
+jobs:
31
+ build:
32
+ name: Build image
33
+ runs-on: ubuntu-latest
34
+ timeout-minutes: 60
35
+
36
+ steps:
37
+ - name: Checkout
38
+ # A release builds from the tagged tree, so the Dockerfile matches the binary it bakes in.
39
+ uses: actions/checkout@v6
40
+ with:
41
+ ref: ${{ github.event.inputs.tag || github.ref }}
42
+
43
+ - name: Resolve tags
44
+ id: meta
45
+ shell: bash
46
+ run: |
47
+ if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
48
+ tag="${{ github.event.inputs.tag }}"
49
+ echo "sigit_version=${tag}" >> "$GITHUB_OUTPUT"
50
+ {
51
+ echo "tags<<EOF"
52
+ echo "${IMAGE}:latest"
53
+ echo "${IMAGE}:${tag}"
54
+ echo "${IMAGE}:$(git rev-parse HEAD)"
55
+ echo "EOF"
56
+ } >> "$GITHUB_OUTPUT"
57
+ else
58
+ echo "sigit_version=latest" >> "$GITHUB_OUTPUT"
59
+ echo "tags=${IMAGE}:pr-${{ github.event.pull_request.number }}" >> "$GITHUB_OUTPUT"
60
+ fi
61
+
62
+ - uses: docker/setup-buildx-action@v4
63
+
64
+ - uses: docker/login-action@v4
65
+ if: github.event_name == 'workflow_dispatch'
66
+ with:
67
+ registry: ghcr.io
68
+ username: ${{ github.actor }}
69
+ password: ${{ secrets.GITHUB_TOKEN }}
70
+
71
+ - uses: docker/build-push-action@v7
72
+ with:
73
+ context: packaging/assurance-runner
74
+ build-args: SIGIT_VERSION=${{ steps.meta.outputs.sigit_version }}
75
+ push: ${{ github.event_name == 'workflow_dispatch' }}
76
+ tags: ${{ steps.meta.outputs.tags }}
77
+ cache-from: type=gha
78
+ cache-to: type=gha,mode=max
.github/workflows/release-github.yml
+2
-1
@@ -302,7 +302,7 @@ jobs:
302
files: release/*
303
304
# These channels all publish somewhere outside this repo (a tap, a Scoop
305
- # bucket, microsoft/winget-pkgs, the AUR) and every one of them reads
305
+ # bucket, microsoft/winget-pkgs, the AUR, ghcr.io) and every one of them reads
306
# checksums off the release created above, so they hang off this job
307
# rather than firing on the tag directly.
308
- name: Trigger OS package manager releases
@@ -314,6 +314,7 @@ jobs:
314
'release-homebrew.yml',
315
'release-scoop.yml',
316
'release-winget.yml',
317
+ 'release-assurance-image.yml',
318
// New AUR account registration is temporarily closed
319
//'release-aur.yml',
320
]
packaging/assurance-runner/Dockerfile
new
+74
@@ -0,0 +1,74 @@
1
+# ghcr.io/getsigit/sigit-code-assurance-runner: the guest image of the `sigit-code-assurance`
2
+# Fireactions pool, where siGit Code Assurance tests a release candidate in a fresh microVM.
3
+# The package is public and holds no secrets: everything a run needs arrives with the job.
4
+#
5
+# It carries no app dependencies, so the same image serves any repository the gate runs on;
6
+# jobs run their own `pnpm install`. What it does carry is this release's `sigit`, so the
7
+# assurance agent doesn't download it on every run. release-assurance-image.yml rebuilds it
8
+# for each sigit release.
9
+#
10
+# Fireactions needs an image that boots systemd and runs `fireactions agent`, so this starts from
11
+# the official fireactions-images base (runner, docker, gh, jq, agent) and adds what the gate
12
+# needs on top. It does not start from mcr.microsoft.com/playwright: that image has no init.
13
+#
14
+# docker build --build-arg SIGIT_VERSION=v1.7.6 -t sigit-code-assurance-runner packaging/assurance-runner
15
+
16
+ARG BASE=ghcr.io/hostinger/fireactions-images/ubuntu24.04:latest
17
+
18
+# tauri-driver is cargo-only; build it here and copy the binary so the guest has no Rust.
19
+FROM rust:1-bookworm AS tauri-driver
20
+RUN cargo install tauri-driver --locked --root /out
21
+
22
+FROM ${BASE}
23
+
24
+LABEL org.opencontainers.image.source=https://github.com/getsigit/sigit \
25
+ org.opencontainers.image.description="Guest image for siGit Code Assurance runs"
26
+
27
+ARG NODE_VERSION=24.12.0
28
+ARG PNPM_VERSION=10
29
+# Must match the consumer's @playwright/test. A job's `playwright install` is a no-op when it
30
+# does and a browser download when it doesn't, and the visual baselines are only stable against
31
+# the browsers they were recorded with.
32
+ARG PLAYWRIGHT_VERSION=1.64.1
33
+# A sigit release tag (v1.7.6), or `latest` for a pull-request build.
34
+ARG SIGIT_VERSION=latest
35
+
36
+# Build-time only inside the microVM: Fireactions boots this as a rootfs, so jobs must set
37
+# PLAYWRIGHT_BROWSERS_PATH themselves (see sigit-app fireactions-smoke.yml).
38
+ENV DEBIAN_FRONTEND=noninteractive \
39
+ PLAYWRIGHT_BROWSERS_PATH=/ms-playwright
40
+
41
+# Tauri runtime on Linux (AppImage under tauri-driver), a virtual display, and bubblewrap for
42
+# the assurance agent's sandbox.
43
+RUN apt-get update \
44
+ && apt-get install -y --no-install-recommends \
45
+ xvfb libwebkit2gtk-4.1-0 webkit2gtk-driver bubblewrap \
46
+ && rm -rf /var/lib/apt/lists/*
47
+
48
+# Node from nodejs.org, then pnpm.
49
+RUN curl -fsSL "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz" \
50
+ | tar -xJ -C /usr/local --strip-components=1 \
51
+ && npm install -g "pnpm@${PNPM_VERSION}"
52
+
53
+# Browsers and their system libraries and fonts, at the pinned Playwright version.
54
+RUN npx -y "playwright@${PLAYWRIGHT_VERSION}" install --with-deps chromium webkit \
55
+ && chmod -R a+rX /ms-playwright \
56
+ && rm -rf /var/lib/apt/lists/*
57
+
58
+COPY --from=tauri-driver /out/bin/tauri-driver /usr/local/bin/tauri-driver
59
+
60
+# The released sigit. The .sha256 sidecar is a bare digest, so it is compared rather than fed
61
+# to `sha256sum -c`. It comes from the same origin as the binary: it catches a truncated or
62
+# corrupted download, not a tampered release.
63
+RUN set -eu; \
64
+ if [ "${SIGIT_VERSION}" = latest ]; then \
65
+ url=https://github.com/getsigit/sigit/releases/latest/download/sigit-linux-amd64; \
66
+ else \
67
+ url="https://github.com/getsigit/sigit/releases/download/${SIGIT_VERSION}/sigit-linux-amd64"; \
68
+ fi; \
69
+ expected="$(curl -fsSL --retry 2 "${url}.sha256" | tr -d '[:space:]')"; \
70
+ curl -fsSL --retry 2 "${url}" -o /usr/local/bin/sigit; \
71
+ actual="$(sha256sum /usr/local/bin/sigit | cut -d' ' -f1)"; \
72
+ [ "${actual}" = "${expected}" ] || { echo "sigit checksum mismatch: got ${actual}, expected ${expected}" >&2; exit 1; }; \
73
+ chmod 755 /usr/local/bin/sigit; \
74
+ /usr/local/bin/sigit --version