mirror of
https://github.com/luxfi/aml.git
synced 2026-09-24 00:25:20 +00:00
hanzo-build-linux-amd64, lux-build-linux-amd64, zoo-build-linux-amd64 and every other org-prefixed spelling all meant the same machine. Which org a job belongs to does not change which host can build it, so the org bought one pool name per org per arch and each could go stale on its own -- the evo host alone answered to fourteen names for one label. One vocabulary now: linux-amd64, linux-arm64, macos-arm64, win-amd64. The generic labels every plain workflow uses (ubuntu-latest, self-hosted) are untouched, so naive CI keeps working. Co-authored-by: Hanzo Dev <[email protected]>
164 lines
6.8 KiB
YAML
164 lines
6.8 KiB
YAML
name: CI
|
|
# Runs on the native runners at git.hanzo.ai (git-runner, label
|
|
# linux-amd64). There are no GitHub-hosted runners in this
|
|
# organisation and none is a permitted fallback: a workflow that names one
|
|
# queues forever, which reads from the outside as a test suite that never fails.
|
|
#
|
|
# Every branch, not only main. A compliance engine's tests are the evidence that
|
|
# its controls work, so they run before the merge that would rely on them.
|
|
|
|
on:
|
|
push:
|
|
branches: ["*"]
|
|
tags-ignore: ["v*"]
|
|
pull_request:
|
|
branches: ["*"]
|
|
workflow_dispatch:
|
|
|
|
concurrency:
|
|
group: ci-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
test:
|
|
runs-on: [linux-amd64]
|
|
timeout-minutes: 30
|
|
# A runner may carry a machine-level go env that routes some module paths
|
|
# around the proxy and the checksum database. GOENV=off ignores it, so every
|
|
# module resolves through proxy.golang.org and is verified against
|
|
# sum.golang.org — a tag that moves later cannot change what was tested.
|
|
env:
|
|
GOENV: off
|
|
GOWORK: off
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- uses: actions/setup-go@v5
|
|
with:
|
|
go-version-file: go.mod
|
|
|
|
- uses: actions/cache@v4
|
|
with:
|
|
path: |
|
|
~/.cache/go-build
|
|
~/go/pkg/mod
|
|
key: ${{ runner.os }}-go-${{ hashFiles('go.sum') }}
|
|
restore-keys: ${{ runner.os }}-go-
|
|
|
|
- name: Modules verify against the checksum database
|
|
# An AML engine's dependency graph is part of its audit surface. This
|
|
# fails on a moved tag rather than building whatever the cache holds.
|
|
run: |
|
|
go mod download
|
|
go mod verify
|
|
|
|
- name: Formatting
|
|
run: |
|
|
unformatted=$(gofmt -l pkg cmd)
|
|
if [ -n "$unformatted" ]; then
|
|
echo "::error::not gofmt-clean:"; echo "$unformatted"; exit 1
|
|
fi
|
|
|
|
- name: Vet
|
|
run: go vet ./...
|
|
|
|
- name: Test with the race detector
|
|
# Ingest is concurrent by construction — the aggregate windows and the
|
|
# behavioural model are written on the request path — so -race is the
|
|
# test run, not an extra one.
|
|
run: go test -race -count=1 ./...
|
|
|
|
- name: Build the daemon
|
|
# The daemon is the API and nothing else — the console is a separate
|
|
# artifact with its own job below, so this needs no Node toolchain.
|
|
run: |
|
|
CGO_ENABLED=0 go build -trimpath -o /tmp/amld ./cmd/amld
|
|
test -x /tmp/amld
|
|
|
|
- name: It serves, and it refuses what it should
|
|
# Two facts worth a check on every commit, because both have regressed
|
|
# before: the process comes up and answers, and an unauthenticated
|
|
# request is refused rather than served somebody's tenant data.
|
|
#
|
|
# AML_CLIENT_ID is required — the daemon refuses to start without it,
|
|
# because it is the audience every token is pinned to and a plane that
|
|
# cannot check the audience authenticates any first-party token. A real
|
|
# bearer is a brand-issued IAM token CI cannot mint, so the smoke checks
|
|
# what needs no token: it serves, and an unauthenticated read is refused.
|
|
#
|
|
# The key is hex from od/tr, and its length is asserted before use. It was
|
|
# xxd, which is not in the runner image; the substitution failed to an
|
|
# EMPTY key and nothing caught it, because an env prefix on a backgrounded
|
|
# command reports the status of the async list, not of the substitution.
|
|
# The smoke ran green for weeks against a daemon holding no key.
|
|
run: |
|
|
set -euo pipefail
|
|
dir=$(mktemp -d)
|
|
key=$(head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n')
|
|
[ ${#key} -eq 64 ] || { echo "::error::token key is ${#key} hex chars, want 64"; exit 1; }
|
|
AML_TENANT_HEADER=X-Org-Id \
|
|
AML_CLIENT_ID=aml-ci-smoke \
|
|
AML_TOKEN_KEY="$key" \
|
|
/tmp/amld serve --dir "$dir" --http 127.0.0.1:8791 --dev=false &
|
|
pid=$!
|
|
for i in $(seq 1 60); do
|
|
curl -sf -o /dev/null http://127.0.0.1:8791/v1/aml/health && break
|
|
sleep 1
|
|
done
|
|
curl -sf -o /dev/null http://127.0.0.1:8791/v1/aml/health \
|
|
|| { echo "::error::amld never answered /v1/aml/health"; kill $pid; exit 1; }
|
|
|
|
code=$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8791/v1/aml/cases)
|
|
kill $pid
|
|
if [ "$code" != "401" ]; then
|
|
echo "::error::an unauthenticated read of /v1/aml/cases returned $code, want 401"
|
|
exit 1
|
|
fi
|
|
echo "serves, and refuses an unauthenticated read"
|
|
|
|
console:
|
|
runs-on: [linux-amd64]
|
|
timeout-minutes: 15
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 22
|
|
cache: npm
|
|
cache-dependency-path: ui/package-lock.json
|
|
- name: Install from the lockfile
|
|
# `npm ci` fails on drift rather than resolving fresh, so what CI builds
|
|
# is what the image builds.
|
|
run: npm --prefix ui ci
|
|
- name: Typecheck
|
|
# The console's own script, the same way every other step here reaches it.
|
|
# `npm --prefix ui exec tsc -- --noEmit` ran the compiler from the REPO
|
|
# root, where there is no tsconfig, so tsc printed its help and exited 1 —
|
|
# this job has never once passed, and the browser suite below it therefore
|
|
# never ran in CI at all.
|
|
run: npm --prefix ui run typecheck
|
|
- name: Build the bundle
|
|
run: |
|
|
npm --prefix ui run build
|
|
test -f ui/dist/index.html
|
|
|
|
- name: The console under the CSP it is served with, signed in
|
|
# The policy in ui/csp.txt is the one charts/app/values/hanzo/aml-ui.yaml
|
|
# sets on the static server, and e2e/serve.mjs sends it verbatim. The kit
|
|
# styles by inserting rules at runtime, which is the thing a strict
|
|
# style-src refuses, so whether the arrangement in ui/src/gui.ts holds is
|
|
# a question only a browser can answer — and the signed-in tree is where
|
|
# the six screens that do the styling live.
|
|
#
|
|
# The browser comes from the console's own script. It was `npm --prefix ui
|
|
# exec playwright install --with-deps chromium`, and npm's parser ate
|
|
# `--with-deps` before playwright saw it: the browser downloaded, its
|
|
# shared libraries did not, and every test that opened a page died on
|
|
# libnspr4.so — 7 of 12, the whole signed-in suite, on the first run this
|
|
# job ever reached. Same shape as the tsc call above. `npm run` hands a
|
|
# script's arguments to the script, so the package states the command once
|
|
# and CI has no second copy to get wrong.
|
|
run: |
|
|
npm --prefix ui run e2e:browser
|
|
npm --prefix ui run e2e
|