2026-10-25 15:28:19 +00:00
|
|
|
name: validate
|
|
|
|
|
on:
|
|
|
|
|
push:
|
|
|
|
|
branches: [main]
|
|
|
|
|
pull_request:
|
|
|
|
|
branches: [main]
|
|
|
|
|
|
2026-10-25 15:36:59 +00:00
|
|
|
# The repository uses the sha256 git object format. Git < 2.42 cannot speak
|
2026-10-25 15:40:01 +00:00
|
|
|
# the sha256 wire protocol, and actions/checkout@v4 does `git init` without
|
|
|
|
|
# --object-format=sha256, which produces `fatal: mismatched algorithms:
|
|
|
|
|
# client sha1; server sha256`. We therefore skip actions/checkout entirely
|
|
|
|
|
# and clone manually from a trixie-based container (git 2.47+).
|
2026-10-25 15:28:19 +00:00
|
|
|
jobs:
|
|
|
|
|
json-schema:
|
|
|
|
|
name: JSON Schema (ajv)
|
|
|
|
|
runs-on: ubuntu-latest
|
2026-10-25 15:36:59 +00:00
|
|
|
container:
|
|
|
|
|
image: node:24-trixie-slim
|
2026-10-25 15:28:19 +00:00
|
|
|
steps:
|
2026-10-25 15:36:59 +00:00
|
|
|
- name: Install git + curl in container
|
2026-10-25 15:30:57 +00:00
|
|
|
run: |
|
|
|
|
|
set -euo pipefail
|
2026-10-25 15:36:59 +00:00
|
|
|
apt-get update
|
|
|
|
|
apt-get install -y --no-install-recommends git curl ca-certificates
|
|
|
|
|
git --version
|
2026-10-25 16:19:37 +00:00
|
|
|
node --version
|
2026-10-25 15:30:57 +00:00
|
|
|
|
2026-10-25 15:40:01 +00:00
|
|
|
- name: Clone repository (sha256-aware)
|
|
|
|
|
run: |
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
git clone --depth=1 --branch="${GITHUB_REF_NAME}" \
|
|
|
|
|
"https://x-access-token:${GITHUB_TOKEN}@forgejo.basicstack.de/${GITHUB_REPOSITORY}.git" "${GITHUB_WORKSPACE}"
|
|
|
|
|
cd "${GITHUB_WORKSPACE}"
|
|
|
|
|
git log -1 --oneline
|
2026-10-25 15:28:19 +00:00
|
|
|
|
2026-10-25 15:30:57 +00:00
|
|
|
- name: Install ajv-cli + ajv-formats
|
2026-10-25 15:40:01 +00:00
|
|
|
working-directory: ${{ github.workspace }}
|
2026-10-25 15:30:57 +00:00
|
|
|
run: |
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
npm install --prefix /tmp/ajv ajv-cli@5 ajv-formats@3
|
2026-10-25 15:28:19 +00:00
|
|
|
|
|
|
|
|
- name: Validate every JSON Schema parses as JSON
|
2026-10-25 15:40:01 +00:00
|
|
|
working-directory: ${{ github.workspace }}
|
2026-10-25 15:28:19 +00:00
|
|
|
run: |
|
2026-10-25 16:19:37 +00:00
|
|
|
set -eu
|
|
|
|
|
find schemas -type f -name 'v*.json' -print | while IFS= read -r f; do
|
2026-10-25 15:28:19 +00:00
|
|
|
echo "--- parse: $f"
|
|
|
|
|
node -e "require('./$f')"
|
|
|
|
|
done
|
|
|
|
|
|
2026-10-25 15:30:57 +00:00
|
|
|
- name: Meta-validate each schema against JSON Schema 2020-12
|
2026-10-25 15:40:01 +00:00
|
|
|
working-directory: ${{ github.workspace }}
|
2026-10-25 15:28:19 +00:00
|
|
|
run: |
|
2026-10-25 16:19:37 +00:00
|
|
|
set -eu
|
2026-10-25 15:30:57 +00:00
|
|
|
export NODE_PATH=/tmp/ajv/node_modules
|
2026-10-25 16:19:37 +00:00
|
|
|
find schemas -type f -name 'v*.json' -print | while IFS= read -r f; do
|
2026-10-25 15:28:19 +00:00
|
|
|
echo "--- meta-validate: $f"
|
2026-10-25 15:30:57 +00:00
|
|
|
node /tmp/ajv/node_modules/.bin/ajv compile --spec=draft2020 -c=ajv-formats -s "$f"
|
2026-10-25 15:28:19 +00:00
|
|
|
done
|
|
|
|
|
|
2026-10-25 16:19:37 +00:00
|
|
|
- name: Validate per-version example payloads against their schemas
|
2026-10-25 15:40:01 +00:00
|
|
|
working-directory: ${{ github.workspace }}
|
2026-10-25 15:28:19 +00:00
|
|
|
run: |
|
2026-10-25 15:41:18 +00:00
|
|
|
set -eu
|
2026-10-25 15:30:57 +00:00
|
|
|
export NODE_PATH=/tmp/ajv/node_modules
|
2026-10-25 16:19:37 +00:00
|
|
|
# Each schema file is <path>/vX.Y.Z.json; example payloads for that version
|
|
|
|
|
# live under <path>/examples/vX.Y.Z/*.json. This keeps v1.0.0 examples from
|
|
|
|
|
# being validated against v1.1.0 and vice-versa.
|
2026-10-25 15:41:18 +00:00
|
|
|
find schemas -type f -name 'v*.json' -print | while IFS= read -r schema; do
|
2026-10-25 16:19:37 +00:00
|
|
|
version="$(basename "$schema" .json)"
|
|
|
|
|
dir="$(dirname "$schema")/examples/$version"
|
2026-10-25 15:28:19 +00:00
|
|
|
[ -d "$dir" ] || continue
|
2026-10-25 15:41:18 +00:00
|
|
|
find "$dir" -type f -name '*.json' -print | while IFS= read -r example; do
|
2026-10-25 15:28:19 +00:00
|
|
|
echo "--- validate example $example against $schema"
|
2026-10-25 15:30:57 +00:00
|
|
|
node /tmp/ajv/node_modules/.bin/ajv validate --spec=draft2020 -c=ajv-formats -s "$schema" -d "$example"
|
2026-10-25 15:28:19 +00:00
|
|
|
done
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
rego:
|
|
|
|
|
name: Rego (opa fmt + opa test)
|
|
|
|
|
runs-on: ubuntu-latest
|
2026-10-25 15:36:59 +00:00
|
|
|
container:
|
|
|
|
|
image: debian:trixie-slim
|
2026-10-25 15:28:19 +00:00
|
|
|
steps:
|
2026-10-25 15:36:59 +00:00
|
|
|
- name: Install git + curl in container
|
2026-10-25 15:30:57 +00:00
|
|
|
run: |
|
|
|
|
|
set -euo pipefail
|
2026-10-25 15:36:59 +00:00
|
|
|
apt-get update
|
|
|
|
|
apt-get install -y --no-install-recommends git curl ca-certificates
|
|
|
|
|
git --version
|
2026-10-25 15:30:57 +00:00
|
|
|
|
2026-10-25 15:40:01 +00:00
|
|
|
- name: Clone repository (sha256-aware)
|
|
|
|
|
run: |
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
git clone --depth=1 --branch="${GITHUB_REF_NAME}" \
|
|
|
|
|
"https://x-access-token:${GITHUB_TOKEN}@forgejo.basicstack.de/${GITHUB_REPOSITORY}.git" "${GITHUB_WORKSPACE}"
|
|
|
|
|
cd "${GITHUB_WORKSPACE}"
|
|
|
|
|
git log -1 --oneline
|
2026-10-25 15:28:19 +00:00
|
|
|
|
|
|
|
|
- name: Install OPA
|
|
|
|
|
run: |
|
|
|
|
|
set -euo pipefail
|
2026-10-25 15:30:57 +00:00
|
|
|
mkdir -p "$HOME/.local/bin"
|
|
|
|
|
curl -sSL -o "$HOME/.local/bin/opa" https://openpolicyagent.org/downloads/v1.9.0/opa_linux_amd64_static
|
|
|
|
|
chmod +x "$HOME/.local/bin/opa"
|
|
|
|
|
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
|
|
|
|
"$HOME/.local/bin/opa" version
|
2026-10-25 15:28:19 +00:00
|
|
|
|
|
|
|
|
- name: opa fmt (fail on any diff)
|
2026-10-25 15:40:01 +00:00
|
|
|
working-directory: ${{ github.workspace }}
|
2026-10-25 15:28:19 +00:00
|
|
|
run: opa fmt --list --diff policies/
|
|
|
|
|
|
|
|
|
|
- name: opa test
|
2026-10-25 15:40:01 +00:00
|
|
|
working-directory: ${{ github.workspace }}
|
2026-10-25 15:28:19 +00:00
|
|
|
run: opa test policies/ -v
|
2026-10-25 17:28:32 +00:00
|
|
|
|
|
|
|
|
go:
|
|
|
|
|
name: Go (vet + test)
|
|
|
|
|
runs-on: ubuntu-latest
|
|
|
|
|
container:
|
2026-10-25 17:30:18 +00:00
|
|
|
# Alpine image: git 2.45+, speaks sha256 wire protocol; small footprint.
|
2026-10-25 19:59:05 +00:00
|
|
|
# Pinned to 1.26 because signing-service depends on
|
|
|
|
|
# github.com/open-policy-agent/opa v1.21.4 which requires Go >= 1.26.0.
|
|
|
|
|
image: golang:1.26-alpine3.23
|
2026-10-25 17:28:32 +00:00
|
|
|
steps:
|
2026-10-25 17:30:18 +00:00
|
|
|
- name: Install git + bash in container
|
2026-10-25 17:28:32 +00:00
|
|
|
run: |
|
2026-10-25 17:30:18 +00:00
|
|
|
set -eu
|
|
|
|
|
apk add --no-cache git bash ca-certificates
|
2026-10-25 17:28:32 +00:00
|
|
|
git --version
|
|
|
|
|
go version
|
|
|
|
|
|
|
|
|
|
- name: Clone repository (sha256-aware)
|
|
|
|
|
run: |
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
git clone --depth=1 --branch="${GITHUB_REF_NAME}" \
|
|
|
|
|
"https://x-access-token:${GITHUB_TOKEN}@forgejo.basicstack.de/${GITHUB_REPOSITORY}.git" "${GITHUB_WORKSPACE}"
|
|
|
|
|
cd "${GITHUB_WORKSPACE}"
|
|
|
|
|
git log -1 --oneline
|
|
|
|
|
|
|
|
|
|
- name: Enumerate Go modules
|
|
|
|
|
id: mods
|
|
|
|
|
working-directory: ${{ github.workspace }}
|
|
|
|
|
run: |
|
|
|
|
|
set -eu
|
|
|
|
|
# List every directory that contains a go.mod and run vet+test there.
|
|
|
|
|
# Shell-quoted paths handle spaces.
|
|
|
|
|
find services -type f -name 'go.mod' -print | sed 's|/go.mod$||' | sort -u | tee /tmp/go_modules.txt
|
|
|
|
|
|
|
|
|
|
- name: go vet + test for every module
|
|
|
|
|
working-directory: ${{ github.workspace }}
|
|
|
|
|
run: |
|
|
|
|
|
set -eu
|
|
|
|
|
while IFS= read -r m; do
|
|
|
|
|
[ -n "$m" ] || continue
|
|
|
|
|
echo "=== $m ==="
|
|
|
|
|
(cd "$m" && go vet ./... && go test ./...)
|
|
|
|
|
done < /tmp/go_modules.txt
|
2026-10-25 19:23:20 +00:00
|
|
|
|
|
|
|
|
db:
|
|
|
|
|
name: DB (migrate up/down dry-run)
|
|
|
|
|
runs-on: ubuntu-latest
|
|
|
|
|
container:
|
|
|
|
|
image: debian:trixie-slim
|
|
|
|
|
options: --user 0:0
|
|
|
|
|
env:
|
|
|
|
|
# Host-networked job container → localhost = host; use a non-default
|
|
|
|
|
# port so parallel jobs do not collide on 5432 if any happen to share
|
|
|
|
|
# a runner host.
|
|
|
|
|
PGHOST: 127.0.0.1
|
|
|
|
|
PGPORT: "55432"
|
|
|
|
|
PGUSER: postgres
|
|
|
|
|
PGPASSWORD: postgres
|
|
|
|
|
PGDATABASE: trust
|
|
|
|
|
DB_URL: postgres://postgres:postgres@127.0.0.1:55432/trust?sslmode=disable
|
|
|
|
|
PG_IMAGE: postgres:16-alpine
|
|
|
|
|
PG_CONTAINER: pm-ci-pg-${{ github.run_id }}-${{ github.run_attempt }}
|
|
|
|
|
steps:
|
|
|
|
|
- name: Install git + curl + docker CLI + psql
|
|
|
|
|
run: |
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
apt-get update
|
2026-10-25 19:27:24 +00:00
|
|
|
# Debian trixie: `docker.io` only brings the daemon; the CLI
|
|
|
|
|
# (`/usr/bin/docker`) ships in `docker-cli` as a Recommends that
|
|
|
|
|
# `--no-install-recommends` skips. Pull it in explicitly.
|
2026-10-25 19:23:20 +00:00
|
|
|
apt-get install -y --no-install-recommends \
|
2026-10-25 19:27:24 +00:00
|
|
|
git curl ca-certificates docker.io docker-cli postgresql-client
|
2026-10-25 19:23:20 +00:00
|
|
|
git --version
|
|
|
|
|
docker --version
|
|
|
|
|
psql --version
|
|
|
|
|
|
|
|
|
|
- name: Clone repository (sha256-aware)
|
|
|
|
|
run: |
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
git clone --depth=1 --branch="${GITHUB_REF_NAME}" \
|
|
|
|
|
"https://x-access-token:${GITHUB_TOKEN}@forgejo.basicstack.de/${GITHUB_REPOSITORY}.git" \
|
|
|
|
|
"${GITHUB_WORKSPACE}"
|
|
|
|
|
cd "${GITHUB_WORKSPACE}"
|
|
|
|
|
git log -1 --oneline
|
|
|
|
|
|
|
|
|
|
- name: Install golang-migrate CLI
|
|
|
|
|
run: |
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
curl -sSL -o /tmp/migrate.tgz \
|
|
|
|
|
https://github.com/golang-migrate/migrate/releases/download/v4.17.1/migrate.linux-amd64.tar.gz
|
|
|
|
|
tar -xzf /tmp/migrate.tgz -C /usr/local/bin migrate
|
|
|
|
|
migrate -version
|
|
|
|
|
|
|
|
|
|
- name: Start Postgres (via docker)
|
|
|
|
|
run: |
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
docker run -d --name "${PG_CONTAINER}" \
|
|
|
|
|
-e POSTGRES_PASSWORD="${PGPASSWORD}" \
|
|
|
|
|
-e POSTGRES_USER="${PGUSER}" \
|
|
|
|
|
-e POSTGRES_DB="${PGDATABASE}" \
|
|
|
|
|
-p "${PGPORT}:5432" \
|
|
|
|
|
"${PG_IMAGE}"
|
|
|
|
|
# Wait up to 60s for pg to be ready.
|
|
|
|
|
for i in $(seq 1 60); do
|
|
|
|
|
if pg_isready -h "${PGHOST}" -p "${PGPORT}" -U "${PGUSER}" -d "${PGDATABASE}" >/dev/null 2>&1; then
|
|
|
|
|
echo "postgres ready after ${i}s"
|
|
|
|
|
exit 0
|
|
|
|
|
fi
|
|
|
|
|
sleep 1
|
|
|
|
|
done
|
|
|
|
|
echo "postgres never became ready" >&2
|
|
|
|
|
docker logs "${PG_CONTAINER}" || true
|
|
|
|
|
exit 1
|
|
|
|
|
|
|
|
|
|
- name: migrate up (full) + verify tables
|
|
|
|
|
working-directory: ${{ github.workspace }}
|
|
|
|
|
run: |
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
migrate -path db/trust/migrations -database "${DB_URL}" up
|
|
|
|
|
psql "${DB_URL}" -c '\dt' | tee /tmp/dt-after-up.txt
|
|
|
|
|
grep -q ' per_diem_rates ' /tmp/dt-after-up.txt
|
|
|
|
|
grep -q ' agents ' /tmp/dt-after-up.txt
|
|
|
|
|
grep -q ' agent_keys ' /tmp/dt-after-up.txt
|
|
|
|
|
|
|
|
|
|
- name: migrate down 2 + verify only agent tables gone
|
|
|
|
|
working-directory: ${{ github.workspace }}
|
|
|
|
|
run: |
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
migrate -path db/trust/migrations -database "${DB_URL}" down 2
|
|
|
|
|
psql "${DB_URL}" -c '\dt' | tee /tmp/dt-after-down.txt
|
|
|
|
|
grep -q ' per_diem_rates ' /tmp/dt-after-down.txt
|
|
|
|
|
if grep -q ' agents ' /tmp/dt-after-down.txt; then
|
|
|
|
|
echo "FAIL: agents still present after migrate down 2" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
if grep -q ' agent_keys ' /tmp/dt-after-down.txt; then
|
|
|
|
|
echo "FAIL: agent_keys still present after migrate down 2" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
- name: migrate up (re-apply) + apply seed + verify three agents
|
|
|
|
|
working-directory: ${{ github.workspace }}
|
|
|
|
|
run: |
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
migrate -path db/trust/migrations -database "${DB_URL}" up
|
|
|
|
|
psql "${DB_URL}" -v ON_ERROR_STOP=1 -f db/trust/seeds/0001_phase1_agents.sql
|
|
|
|
|
# Idempotency: re-run must not error and not duplicate rows.
|
|
|
|
|
psql "${DB_URL}" -v ON_ERROR_STOP=1 -f db/trust/seeds/0001_phase1_agents.sql
|
|
|
|
|
count=$(psql "${DB_URL}" -At -c \
|
|
|
|
|
"SELECT count(*) FROM agents WHERE agent_id IN ('agent-approval-service','agent-testagent-transform','agent-dummy-sink')")
|
|
|
|
|
echo "seeded agents = ${count}"
|
|
|
|
|
[ "${count}" = "3" ]
|
|
|
|
|
|
|
|
|
|
- name: Postgres logs (always)
|
|
|
|
|
if: always()
|
|
|
|
|
run: docker logs "${PG_CONTAINER}" || true
|
|
|
|
|
|
|
|
|
|
- name: Stop Postgres (always)
|
|
|
|
|
if: always()
|
|
|
|
|
run: docker rm -f "${PG_CONTAINER}" || true
|