Qt/QML now carries 11 local views while the Agent owns observation and intent. Tauri, WebKit, and the second React desktop build leave together. Linux ships first; Windows waits for a native Qt runner.
318 lines
12 KiB
YAML
318 lines
12 KiB
YAML
name: ci
|
|
on:
|
|
push:
|
|
branches: [main, public]
|
|
pull_request:
|
|
branches: [main, public]
|
|
workflow_dispatch:
|
|
|
|
jobs:
|
|
go-vet:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
|
|
with:
|
|
go-version-file: agent/go.mod
|
|
cache: true
|
|
- name: go vet (server)
|
|
run: cd server && go vet ./...
|
|
- name: go vet (agent)
|
|
run: cd agent && go vet ./...
|
|
|
|
go-test:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
|
|
with:
|
|
go-version-file: agent/go.mod
|
|
cache: true
|
|
- name: go test -race (server)
|
|
run: cd server && go test -race -count=1 ./...
|
|
- name: go test -race (agent)
|
|
run: cd agent && go test -race -count=1 ./...
|
|
|
|
rust-test:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
- uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable
|
|
with:
|
|
components: clippy
|
|
- name: cargo test
|
|
run: cd helper && cargo test
|
|
- name: cargo clippy
|
|
run: cd helper && cargo clippy -- -D warnings
|
|
|
|
# Cross-compile check: does it build for every target platform?
|
|
# Tests run only on native linux-amd64 above; this catches portability
|
|
# regressions (cfg(target_os), FFI, path assumptions) without needing
|
|
# a runner per OS.
|
|
cross-compile:
|
|
runs-on: ubuntu-latest
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- goos: linux
|
|
goarch: arm64
|
|
rust_target: aarch64-unknown-linux-gnu
|
|
linker: gcc-aarch64-linux-gnu
|
|
use_zigbuild: false
|
|
skip_helper: false
|
|
- goos: windows
|
|
goarch: amd64
|
|
rust_target: ""
|
|
linker: gcc-mingw-w64-x86-64
|
|
use_zigbuild: false
|
|
skip_helper: true
|
|
- goos: darwin
|
|
goarch: arm64
|
|
rust_target: aarch64-apple-darwin
|
|
linker: ""
|
|
use_zigbuild: true
|
|
skip_helper: false
|
|
steps:
|
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
|
|
with:
|
|
go-version-file: agent/go.mod
|
|
cache: true
|
|
- uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable
|
|
with:
|
|
targets: ${{ matrix.rust_target }}
|
|
|
|
- name: Install cross-linker
|
|
if: matrix.linker != ''
|
|
run: sudo apt-get update && sudo apt-get install -y ${{ matrix.linker }}
|
|
|
|
- name: Install cargo-zigbuild
|
|
if: matrix.use_zigbuild
|
|
run: pip3 install --break-system-packages cargo-zigbuild
|
|
|
|
- name: Cross-compile Go (server)
|
|
env:
|
|
GOOS: ${{ matrix.goos }}
|
|
GOARCH: ${{ matrix.goarch }}
|
|
CGO_ENABLED: "0"
|
|
run: cd server && go build -o /dev/null ./cmd/server/
|
|
|
|
- name: Cross-compile Go (agent)
|
|
env:
|
|
GOOS: ${{ matrix.goos }}
|
|
GOARCH: ${{ matrix.goarch }}
|
|
CGO_ENABLED: "0"
|
|
run: cd agent && go build -o /dev/null ./cmd/agent/
|
|
|
|
- name: Cross-compile Rust (helper)
|
|
if: "!matrix.skip_helper"
|
|
run: |
|
|
cd helper
|
|
if [ "${{ matrix.use_zigbuild }}" = "true" ]; then
|
|
cargo zigbuild --release --target ${{ matrix.rust_target }}
|
|
else
|
|
cargo build --release --target ${{ matrix.rust_target }}
|
|
fi
|
|
|
|
web-build:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
|
with:
|
|
node-version: 20
|
|
cache: npm
|
|
cache-dependency-path: web/package-lock.json
|
|
# npm run build = tsc && vite build — type errors and bundling failures
|
|
# both surface here, not at release time.
|
|
- name: Build web UI
|
|
run: cd web && npm ci && npm run build
|
|
|
|
desktop-check:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
- uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable
|
|
- name: Install Qt 6 build dependencies
|
|
run: |
|
|
sudo apt-get update -qq
|
|
sudo apt-get install -y -qq qt6-base-dev qt6-declarative-dev qt6-declarative-dev-tools libgl1-mesa-dev
|
|
- name: Check native RedFlag Desktop
|
|
run: cd desktop && cargo check --locked
|
|
|
|
installer-integrity:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
|
|
with:
|
|
go-version-file: agent/go.mod
|
|
cache: true
|
|
- name: Install template integrity
|
|
run: cd server && go test -run 'TestInstallTemplateRenders|TestFreshInstallConfigKeys|TestInstallTemplateScriptletSyntax' -v -count=1 ./internal/services/
|
|
|
|
# Dependency vulnerability scanning — RedFlag held to the supply-chain standard
|
|
# it enforces on the fleet. Tools installed directly (no third-party actions) so
|
|
# the socket-mounted runner's surface stays small. Go is reachability-gated via a
|
|
# documented allowlist (.govulncheck-allow, mirrored in SECURITY.md); npm gates the
|
|
# production tree and treats dev-only advisories as warnings; cargo gates outright.
|
|
dep-scan:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
|
|
with:
|
|
go-version-file: agent/go.mod
|
|
cache: true
|
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
|
|
with:
|
|
node-version: 20
|
|
cache: npm
|
|
cache-dependency-path: web/package-lock.json
|
|
- uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable
|
|
|
|
# Provenance first: record the substrate even if a later scan fails the job.
|
|
# "Are we using hacked programs to build it?" — this is how we SEE the answer.
|
|
# Floor enforcement (fail on an out-of-date engine/toolchain) is the next layer.
|
|
- name: Record build substrate
|
|
run: |
|
|
{
|
|
echo "## Build substrate"
|
|
echo '```'
|
|
echo "go: $(go version)"
|
|
echo "rustc: $(rustc --version)"
|
|
echo "cargo: $(cargo --version)"
|
|
echo "node: $(node --version)"
|
|
echo "npm: $(npm --version)"
|
|
echo "docker: $(docker version --format '{{.Server.Version}}' 2>&1 || echo 'no engine reachable')"
|
|
echo "runner: ${RUNNER_NAME:-unknown} / $(uname -srm)"
|
|
echo '```'
|
|
} | tee -a "${GITHUB_STEP_SUMMARY:-/dev/stdout}"
|
|
|
|
# Scanners run latest on purpose — an old scanner misses new advisories.
|
|
- name: Install scanners
|
|
run: |
|
|
go install golang.org/x/vuln/cmd/govulncheck@latest
|
|
cargo install cargo-audit --locked
|
|
|
|
# One audited script gates Go (reachability + allowlist), web (prod tree),
|
|
# and the Rust helper. release.yml runs the same script with --posture-out
|
|
# to emit the attested posture — CI and release can't drift on the verdict.
|
|
- name: Dependency gate
|
|
run: scripts/dep-scan.sh
|
|
|
|
no-ai-attribution:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
with:
|
|
fetch-depth: 0
|
|
- name: Check commit messages for AI attribution
|
|
run: |
|
|
# Check all commits in the push/PR range.
|
|
# On push: compare against the base branch.
|
|
# On PR: compare against the PR base.
|
|
if [ "${{ github.event_name }}" = "pull_request" ]; then
|
|
RANGE="${{ github.event.pull_request.base.sha }}..${{ github.sha }}"
|
|
else
|
|
RANGE="${{ github.event.before }}..${{ github.sha }}"
|
|
# First push — no before sha. Check last 10 commits.
|
|
if [ "${{ github.event.before }}" = "0000000000000000000000000000000000000000" ]; then
|
|
RANGE="HEAD~10..HEAD"
|
|
fi
|
|
fi
|
|
|
|
PATTERNS="Co-Authored-By:.*[Cc]laude|Co-Authored-By:.*OpenAI|Co-Authored-By:.*ChatGPT|Co-Authored-By:.*Copilot|Co-Authored-By:.*Letta|Co-Authored-By:.*Cursor|Generated by|Generated with|AI-assisted|Auto-generated by"
|
|
FAIL=0
|
|
while IFS= read -r msg; do
|
|
if echo "$msg" | grep -qiE "$PATTERNS"; then
|
|
echo "::error::AI attribution found in commit: $msg"
|
|
FAIL=1
|
|
fi
|
|
done < <(git log --format='%s%n%b' $RANGE 2>/dev/null)
|
|
|
|
if [ "$FAIL" -eq 1 ]; then
|
|
echo "::error::Commits contain AI attribution lines. Remove them before merging."
|
|
exit 1
|
|
fi
|
|
echo "No AI attribution found in commits."
|
|
|
|
action-pins:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
- name: Check for floating action refs
|
|
run: |
|
|
if grep -rE 'uses:.*@(v[0-9]+|stable|main|master)(\s|$)' .gitea/workflows/; then
|
|
echo "::error::Floating action refs found — run scripts/update-action-pins.sh"
|
|
exit 1
|
|
fi
|
|
echo "All action refs are SHA-pinned."
|
|
|
|
public-history:
|
|
runs-on: ubuntu-latest
|
|
if: github.ref == 'refs/heads/public' || github.base_ref == 'public'
|
|
steps:
|
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
with:
|
|
fetch-depth: 0
|
|
- name: Fetch pinned gitleaks
|
|
run: |
|
|
command -v curl || (apt-get update -qq && apt-get install -y -qq curl)
|
|
curl -fsSL -o /tmp/gitleaks.tar.gz \
|
|
https://github.com/gitleaks/gitleaks/releases/download/v8.30.1/gitleaks_8.30.1_linux_x64.tar.gz
|
|
echo '551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb /tmp/gitleaks.tar.gz' |
|
|
sha256sum -c -
|
|
tar -xzf /tmp/gitleaks.tar.gz -C /tmp gitleaks
|
|
- name: Scan complete public history
|
|
run: |
|
|
/tmp/gitleaks git . --redact --no-banner
|
|
scripts/check-public-history.sh HEAD
|
|
|
|
# The internal forge is the only writer. This job projects the exact tested
|
|
# public SHA to Forgejo, without force, then reads it back anonymously.
|
|
sync-public:
|
|
runs-on: ubuntu-latest
|
|
needs: [go-vet, go-test, rust-test, cross-compile, web-build, desktop-check, installer-integrity, dep-scan, no-ai-attribution, action-pins, public-history]
|
|
if: github.ref == 'refs/heads/public'
|
|
env:
|
|
PUBLIC_FORGE_TOKEN: ${{ secrets.PUBLIC_FORGE_TOKEN }}
|
|
steps:
|
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
with:
|
|
fetch-depth: 0
|
|
- name: Publish and verify exact SHA
|
|
run: |
|
|
set -euo pipefail
|
|
: "${PUBLIC_FORGE_TOKEN:?PUBLIC_FORGE_TOKEN is required}"
|
|
|
|
expected="${GITHUB_SHA}"
|
|
forge_url='https://forge.caseytunturi.com/Fimeg/RedFlag.git'
|
|
|
|
check_fast_forward() {
|
|
url="$1"
|
|
remote_sha="$(git ls-remote "$url" refs/heads/public | awk '{print $1}')"
|
|
if [ -z "$remote_sha" ]; then
|
|
return 0
|
|
fi
|
|
if ! git cat-file -e "${remote_sha}^{commit}" 2>/dev/null; then
|
|
echo "[publish] remote public SHA is not in tested history: $url" >&2
|
|
exit 1
|
|
fi
|
|
if ! git merge-base --is-ancestor "$remote_sha" "$expected"; then
|
|
echo "[publish] refusing non-fast-forward public history: $url" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
check_fast_forward "$forge_url"
|
|
|
|
forge_auth="$(printf 'publisher-redflag:%s' "$PUBLIC_FORGE_TOKEN" | base64 -w0)"
|
|
|
|
git -c "http.https://forge.caseytunturi.com/.extraheader=Authorization: Basic $forge_auth" \
|
|
push "$forge_url" public:public
|
|
|
|
forge_sha="$(git ls-remote "$forge_url" refs/heads/public | awk '{print $1}')"
|
|
test "$forge_sha" = "$expected"
|
|
echo "[publish] exact SHA is anonymous on Forgejo: $expected"
|