Skip to content

chore(deps): bump the go-deps group across 1 directory with 19 updates #151

chore(deps): bump the go-deps group across 1 directory with 19 updates

chore(deps): bump the go-deps group across 1 directory with 19 updates #151

Workflow file for this run

name: CI
# CI runs automatically on pushes to main and on PRs targeting main.
# The fast gates (build, lint, unit tests, compose/deploy config validation,
# docs) run on every PR. The heavy/real-infra deploy smokes (helm-kind,
# fly-staging, prod-compose) are OPT-IN: they only run on manual dispatch or on
# a PR carrying the `deploy-smoke` label, so ordinary PRs never spin up clusters,
# deploy to Fly, or require the full prod secret set.
on:
workflow_dispatch:
inputs:
run_helm_kind_smoke:
description: "Run the full Helm kind deploy smoke test"
required: false
default: "true"
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
pull-requests: read
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
changes:
name: Detect changes
runs-on: ubuntu-latest
outputs:
go: ${{ steps.filter.outputs.go }}
python: ${{ steps.filter.outputs.python }}
typescript: ${{ steps.filter.outputs.typescript }}
docs: ${{ steps.filter.outputs.docs }}
docs_site: ${{ steps.filter.outputs.docs_site }}
compose: ${{ steps.filter.outputs.compose }}
deploy: ${{ steps.filter.outputs.deploy }}
images: ${{ steps.filter.outputs.images }}
steps:
- uses: actions/checkout@v7
- uses: dorny/paths-filter@v4
id: filter
with:
filters: |
go:
- '**/*.go'
- 'go.mod'
- 'go.sum'
- '.golangci.yml'
python:
- '**/*.py'
- 'pyproject.toml'
- 'packages/sdk-py/**'
- 'apps/backend/**'
typescript:
- '**/*.ts'
- '**/*.tsx'
- 'package.json'
- 'pnpm-workspace.yaml'
- 'packages/sdk-ts/**'
- 'apps/dashboard/**'
- 'scripts/generate-brand.mjs'
- 'brand.yaml'
docs:
- '**/*.md'
- 'docs/**'
docs_site:
- 'docs-site/**'
- 'docs/**'
compose:
- 'docker-compose*.yml'
- 'scripts/postgres-init.sh'
deploy:
- '.github/workflows/ci.yml'
- 'deploy/**'
- 'docker-compose*.yml'
- 'scripts/validate-deploy-targets.py'
- 'scripts/test-helm-kind.sh'
- 'scripts/test-fly-staging.sh'
- 'scripts/test-prod-compose-smoke.sh'
images:
- 'apps/dashboard/Dockerfile'
- 'apps/customer-app/Dockerfile'
- 'scripts/generate-brand.mjs'
- 'package.json'
- 'pnpm-lock.yaml'
- 'pnpm-workspace.yaml'
lint-go:
name: Lint (Go)
needs: changes
if: needs.changes.outputs.go == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
# only-new-issues needs the base ref to diff against.
fetch-depth: 0
- uses: actions/setup-go@v5
with:
# Match the toolchain go.mod requires (go 1.25.x). Pinning an older
# version forced a GOTOOLCHAIN auto-download whose tool set was
# missing `covdata`, breaking `go test -cover` on no-test packages.
go-version-file: go.mod
cache: true
- name: go vet
run: go vet ./...
- name: go build
run: go build ./...
# golangci-lint v2 (S7): the config was migrated from the v1 schema so it
# analyzes the go 1.25 toolchain cleanly again. It is restored as a
# blocking gate in only-new-issues mode — the ~106-issue pre-existing
# backlog is not gated, but any NEW issue a PR introduces fails the build,
# so the debt burns down as files are touched. Run ENFORCE via
# `golangci-lint run` locally to see the full backlog.
- name: golangci-lint
uses: golangci/golangci-lint-action@v7
with:
# v2.5.0's release binary is built with go1.25.1; older v2 binaries
# (e.g. v2.1.6, built with go1.24) refuse a config targeting go 1.25.
version: v2.5.0
# Compute NEW issues from git, not the GitHub diff API. The action's
# only-new-issues path fetches the PR patch from the API, which has a
# hard 20k-line ceiling ("diff too large") — a large PR then trips it
# and the action falls back to reporting the entire ~106-issue
# backlog. `--new-from-merge-base` does the identical filtering via
# git (fetch-depth: 0 above provides origin/main), with no ceiling:
# only issues on lines this PR changed relative to the merge base
# fail the build. Same gate, robust to PR size.
only-new-issues: false
args: --new-from-merge-base=origin/main
test-go:
name: Test (Go)
needs: changes
if: needs.changes.outputs.go == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v5
with:
# Match the toolchain go.mod requires (go 1.25.x). Pinning an older
# version forced a GOTOOLCHAIN auto-download whose tool set was
# missing `covdata`, breaking `go test -cover` on no-test packages.
go-version-file: go.mod
cache: true
- run: go test ./... -race -cover
lint-python:
name: Lint (Python)
needs: changes
if: needs.changes.outputs.python == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@v7
- run: uv python install 3.11
# Pin ruff: its formatter output drifts across releases, so an unpinned
# `ruff` would fail `format --check` whenever a new version ships. The
# baseline is formatted with this exact version.
- run: uv tool install ruff==0.15.20
- run: ruff check .
- run: ruff format --check .
test-python:
name: Test (Python)
needs: changes
if: needs.changes.outputs.python == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@v7
- run: uv python install 3.11
- run: uv sync --all-packages
continue-on-error: true # OK while packages are empty
- run: |
if find packages/sdk-py/tests apps/backend/tests -name 'test_*.py' 2>/dev/null | grep -q .; then
uv run pytest -q
else
echo "No Python tests yet"
fi
lint-typescript:
name: Lint (TypeScript)
needs: changes
if: needs.changes.outputs.typescript == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: "pnpm"
- run: pnpm install --frozen-lockfile
- name: Brand codegen (js-yaml ESM import)
run: node scripts/generate-brand.mjs
- run: pnpm -r lint || echo "no TS lint scripts yet"
- run: pnpm exec prettier --check "**/*.{ts,tsx,js,jsx,json,md,yaml,yml}" || true
test-typescript:
name: Test (TypeScript)
needs: changes
if: needs.changes.outputs.typescript == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: "pnpm"
- run: pnpm install --frozen-lockfile
- run: pnpm -r test || echo "no TS tests yet"
dco:
name: DCO
# PR-only: GitHub merge commits on main are not DCO-signed. The
# required check is the PR range against origin/main.
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Verify Signed-off-by on PR commits
env:
BASE_REF: origin/${{ github.base_ref }}
run: scripts/check-dco.sh "$BASE_REF"
validate-compose:
name: Validate docker-compose
needs: changes
if: needs.changes.outputs.compose == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Validate compose syntax
run: |
docker compose config --quiet
- name: Validate prod compose
run: |
if [ -f docker-compose.prod.yml ]; then
docker compose -f docker-compose.yml -f docker-compose.prod.yml config --quiet
fi
validate-deploy-targets:
name: Validate deploy targets
needs: changes
if: needs.changes.outputs.deploy == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: azure/setup-helm@v4
- name: Validate Helm, Fly, Railway, Render, and prod compose
run: scripts/validate-deploy-targets.py
build-app-images:
name: Build app images
needs: changes
# Catch release-image breakage (brand codegen, lockfile, Dockerfile)
# before workflow_run/Release tries to push to GHCR.
if: needs.changes.outputs.images == 'true'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- name: dashboard
dockerfile: apps/dashboard/Dockerfile
- name: customer-app
dockerfile: apps/customer-app/Dockerfile
steps:
- uses: actions/checkout@v7
- uses: docker/setup-buildx-action@v3
- name: Build ${{ matrix.name }} (no push)
uses: docker/build-push-action@v6
with:
context: .
file: ${{ matrix.dockerfile }}
push: false
tags: af-stack-${{ matrix.name }}:ci
cache-from: type=gha,scope=${{ matrix.name }}
cache-to: type=gha,mode=max,scope=${{ matrix.name }}
provenance: false
helm-kind-smoke:
name: Helm kind smoke
needs: changes
# Opt-in only: manual dispatch (with the input set) or a `deploy-smoke`-labeled PR.
if: needs.changes.outputs.deploy == 'true' && ((github.event_name == 'workflow_dispatch' && github.event.inputs.run_helm_kind_smoke == 'true') || contains(github.event.pull_request.labels.*.name, 'deploy-smoke'))
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: azure/setup-helm@v4
- uses: helm/kind-action@v1
with:
install_only: true
- name: Install kubectl
uses: azure/setup-kubectl@v5
- name: Deploy chart to kind and probe health endpoints
run: scripts/test-helm-kind.sh
fly-staging-smoke:
name: Fly staging smoke
needs: changes
# Opt-in only: deploys to REAL Fly infra (needs FLY_API_TOKEN), so never on
# an ordinary PR. Manual dispatch or a `deploy-smoke`-labeled PR.
if: needs.changes.outputs.deploy == 'true' && (github.event_name == 'workflow_dispatch' || contains(github.event.pull_request.labels.*.name, 'deploy-smoke'))
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: superfly/flyctl-actions/setup-flyctl@master
- name: Deploy Fly staging apps and probe health endpoints
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
AF_STACK_FLY_STAGING_RUNTIME_APP: ${{ vars.AF_STACK_FLY_STAGING_RUNTIME_APP }}
AF_STACK_FLY_STAGING_DASHBOARD_APP: ${{ vars.AF_STACK_FLY_STAGING_DASHBOARD_APP }}
AF_STACK_FLY_STAGING_REGION: ${{ vars.AF_STACK_FLY_STAGING_REGION }}
AF_STACK_FLY_STAGING_RUNTIME_URL: ${{ vars.AF_STACK_FLY_STAGING_RUNTIME_URL }}
AF_STACK_FLY_STAGING_DASHBOARD_URL: ${{ vars.AF_STACK_FLY_STAGING_DASHBOARD_URL }}
run: scripts/test-fly-staging.sh
prod-compose-smoke:
name: Production compose smoke
needs: changes
# Opt-in only: needs the full prod secret set; absent secrets => guaranteed
# red on ordinary PRs. Manual dispatch or a `deploy-smoke`-labeled PR.
if: needs.changes.outputs.deploy == 'true' && (github.event_name == 'workflow_dispatch' || contains(github.event.pull_request.labels.*.name, 'deploy-smoke'))
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Bring up production compose and probe services
env:
AF_STACK_PROD_COMPOSE_SMOKE: ${{ vars.AF_STACK_PROD_COMPOSE_SMOKE }}
AF_STACK_DOMAIN: ${{ vars.AF_STACK_PROD_COMPOSE_DOMAIN }}
ACME_EMAIL: ${{ vars.AF_STACK_PROD_COMPOSE_ACME_EMAIL }}
AF_STACK_DATABASE_URL: ${{ secrets.AF_STACK_PROD_COMPOSE_DATABASE_URL }}
AGENTFIELD_STORAGE_POSTGRES_URL: ${{ secrets.AGENTFIELD_STORAGE_POSTGRES_URL }}
AF_STACK_KMS_KEY: ${{ secrets.AF_STACK_PROD_COMPOSE_KMS_KEY }}
AF_STACK_AUTH_SECRET: ${{ secrets.AF_STACK_PROD_COMPOSE_AUTH_SECRET }}
AF_STACK_S3_ENDPOINT: ${{ vars.AF_STACK_PROD_COMPOSE_S3_ENDPOINT }}
AF_STACK_S3_BUCKET: ${{ vars.AF_STACK_PROD_COMPOSE_S3_BUCKET }}
AF_STACK_S3_ACCESS_KEY: ${{ secrets.AF_STACK_PROD_COMPOSE_S3_ACCESS_KEY }}
AF_STACK_S3_SECRET_KEY: ${{ secrets.AF_STACK_PROD_COMPOSE_S3_SECRET_KEY }}
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
E2B_API_KEY: ${{ secrets.E2B_API_KEY }}
run: scripts/test-prod-compose-smoke.sh
docs-lint:
name: Lint docs
needs: changes
if: needs.changes.outputs.docs == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Check for broken internal links
run: |
# Simple check for obviously broken markdown links
! grep -rn '](\.\./\.\./\.\.' --include='*.md' . || (echo "Suspicious path traversal in docs" && exit 1)
build-docs-site:
name: Build docs-site (Astro Starlight)
needs: changes
if: needs.changes.outputs.docs_site == 'true'
runs-on: ubuntu-latest
defaults:
run:
working-directory: docs-site
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v4
with:
# Astro 5 requires Node >=22.12.0; the docs-site build fails on 20.
node-version: "22"
cache: "npm"
cache-dependency-path: docs-site/package-lock.json
- name: Install
run: npm install
- name: Build
run: npm run build
- name: Verify build output
run: |
# Build must produce a non-trivial static site. Threshold is loose
# so adding/removing one or two pages doesn't fail CI, but a
# broken collection that drops most pages does.
pages=$(find dist -name '*.html' -type f | wc -l)
echo "Built $pages HTML pages"
if [ "$pages" -lt 15 ]; then
echo "Expected at least 15 pages, got $pages"
exit 1
fi
ci-success:
name: CI Success
needs:
[
lint-go,
test-go,
lint-python,
test-python,
lint-typescript,
test-typescript,
validate-compose,
validate-deploy-targets,
helm-kind-smoke,
fly-staging-smoke,
prod-compose-smoke,
docs-lint,
build-docs-site,
dco,
build-app-images,
]
if: always()
runs-on: ubuntu-latest
steps:
- name: Check all jobs
run: |
# This job aggregates results so branch protection can require one check.
# If any required job fails, this job fails.
results='${{ toJSON(needs) }}'
echo "Job results: $results"
# Allow skipped (due to path filters) but not failed
echo "$results" | jq -e 'all(.[]; .result == "success" or .result == "skipped")'