Skip to content

docs: add the assurance case #422

docs: add the assurance case

docs: add the assurance case #422

Workflow file for this run

# SPDX-FileCopyrightText: Sudo Apt Holdings LLC
# SPDX-License-Identifier: Apache-2.0
name: gate
on:
push:
pull_request:
jobs:
gate:
runs-on: ubuntu-latest
env:
MIX_ENV: test
TRINITY_DB: sqlite
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
with:
fetch-depth: 0 # the DCO check below reads the whole history
# On a pull request the default checkout is a merge commit GitHub makes on the fly,
# authored by nobody and signed off by nobody, which the DCO check below rightly
# refuses. The branch head is what was written and signed, so that is what the gate
# reads. The repository ruleset requires a branch to be current with main before it
# can merge, so the head is also what main will contain.
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- uses: erlef/setup-beam@54075bcc5e249e4758d363f27d099f55d843f124 # v1.24.1
with:
version-file: .tool-versions
version-type: strict
- uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: |
deps
_build
key: ${{ runner.os }}-mix-${{ hashFiles('mix.lock') }}
restore-keys: ${{ runner.os }}-mix-
- run: mix deps.get
# Developer Certificate of Origin: every commit carries a sign-off (ADR-0012).
- name: DCO sign-off present on every commit
run: |
git log --format=%B ${{ github.event.pull_request.base.sha || 'HEAD~1' }}..HEAD 2>/dev/null \
| grep -q '^Signed-off-by: ' || {
echo "::error::a commit in this range has no Signed-off-by line"; exit 1; }
- run: mix gate
# Slice 010 AC1 and AC2: the same migrations and the same suite on Postgres. A second job
# rather than a matrix entry so the required check keeps its context name, `gate`. This job
# is not required by the ruleset until it has run green on main once; then it is added.
# Tests tagged :sqlite read SQLite pragmas and are excluded here by tag, never by skip.
postgres:
runs-on: ubuntu-latest
services:
postgres:
# Slice 032: pgvector's image of Postgres 17, so the vector extension exists for the
# semantic memory's Postgres store.
image: pgvector/pgvector:pg17
env:
POSTGRES_USER: trinity
POSTGRES_PASSWORD: trinity
POSTGRES_DB: trinity_test
ports: ['5432:5432']
options: >-
--health-cmd "pg_isready -U trinity"
--health-interval 5s
--health-timeout 5s
--health-retries 10
env:
MIX_ENV: test
TRINITY_DB: postgres
DATABASE_URL: postgres://trinity:trinity@localhost:5432/trinity_test
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
- uses: erlef/setup-beam@54075bcc5e249e4758d363f27d099f55d843f124 # v1.24.1
with:
version-file: .tool-versions
version-type: strict
- uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: |
deps
_build
key: ${{ runner.os }}-mix-postgres-${{ hashFiles('mix.lock') }}
restore-keys: ${{ runner.os }}-mix-postgres-
- run: mix deps.get
# The adapter is chosen at compile time (config/config.exs); this proves the build
# under TRINITY_DB=postgres links the Postgres adapter and nothing SQLite. `--no-start`
# since slice 050: the check needs the compiled module, not a booted application, and
# Oban refuses to start before its table is migrated (run 35711666503, this step booted
# the application before `ecto.reset`).
- name: The compiled adapter is Postgres
run: mix run --no-start -e 'Ecto.Adapters.Postgres = Trinity.Repo.__adapter__()'
- run: mix ecto.reset
- run: mix test --exclude sqlite
# Slice 003: the same gate on an OTP built from source with --enable-fips, in FIPS mode,
# against the FIPS provider of a UBI9 container (docs/fips-leg.md). A second job rather than
# a matrix entry, as `postgres` is, so `gate` keeps its context name. The image is built by
# fips-image.yml and pulled here by the tag scripts/fips_image_tag.sh derives from the
# files that define it; this job never builds it.
fips-tag:
runs-on: ubuntu-latest
outputs:
ref: ${{ steps.tag.outputs.ref }}
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
- id: tag
run: echo "ref=$(scripts/fips_image_tag.sh --ref)" >> "$GITHUB_OUTPUT"
# The image tag is a hash of the files that define the image, so any change to them -
# including a comment - names an image that has to be built before it can be pulled. The
# `fips` job below takes that image as its container, which GitHub pulls before the job's
# first step runs, so the job cannot retry the pull itself: it fails with `manifest
# unknown` and no output. The fips-image workflow builds and pushes on the same event, so
# the two race, and the loser is always this one.
#
# Waiting here turns that race into a wait. The image is a function of its inputs, so the
# one being waited for is exactly the one this commit needs, and a build from source is
# slow enough that the ceiling is generous. A timeout fails with the reason rather than
# with a pull error, because "the image is still building" and "the image will never
# exist" need different responses from whoever reads it.
- name: Wait for the FIPS image to be published
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
ref='${{ steps.tag.outputs.ref }}'
echo "$GH_TOKEN" | docker login ghcr.io -u '${{ github.actor }}' --password-stdin
for attempt in $(seq 1 60); do
if docker manifest inspect "$ref" >/dev/null 2>&1; then
echo "$ref is published (attempt $attempt)"
exit 0
fi
echo "attempt $attempt: $ref is not published yet; waiting 30s"
sleep 30
done
echo "::error::$ref was not published within 30 minutes. It is built by the" \
"fips-image workflow, which runs when ci/fips/Containerfile, .tool-versions or" \
"scripts/fips_image_tag.sh changes. Check that workflow's run for this commit."
exit 1
fips:
needs: fips-tag
runs-on: ubuntu-latest
container:
image: ${{ needs.fips-tag.outputs.ref }}
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
env:
MIX_ENV: test
TRINITY_DB: sqlite
# test/test_helper.exs includes the :fips tests under this, and test/fips/mode_test.exs
# asserts the runtime agrees, so a leg that failed to enter the mode is red, not quiet.
TRINITY_FIPS_LEG: "1"
# FIPS mode is entered by OTP's own configuration, read by the crypto NIF when it loads,
# which needs the crypto application loaded first; ERL_AFLAGS is prepended to every
# erl command line, so every `mix` and `elixir` here runs in the mode. Measured at G1:
# ERL_FLAGS alone leaves the mode off (the NIF loads before the application does).
ERL_AFLAGS: "-crypto fips_mode true -eval application:load(crypto)"
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha || github.sha }}
# The checkout action marks the workspace safe under a temporary HOME it removes when
# it is done; every later git call in the container (the names, secrets and
# reuse censuses, all of which read `git ls-files`) then hits git's dubious-ownership
# refusal and reads an empty tree, which makes every census pass over nothing.
- run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: |
deps
_build
key: fips-mix-${{ needs.fips-tag.outputs.ref }}-${{ hashFiles('mix.lock') }}
restore-keys: fips-mix-${{ needs.fips-tag.outputs.ref }}-
# The one step outside the mode. The fetch, because Hex's client offers TLS 1.0 and 1.1
# beside 1.2 and ssl in the mode refuses the set (insufficient_crypto_support); the
# dependency compile, because rustler_precompiled downloads mdex's NIF at compile time
# and OTP's TLS 1.3 client in the mode fails that host's HelloRetryRequest
# (erlang/otp#8470). docs/fips-leg.md findings 1 and 2; neither is what the leg
# measures. The gate's own hex.audit step clears ERL_AFLAGS for the same reason (mix.exs).
- name: Fetch and compile dependencies (FIPS mode off)
env:
ERL_AFLAGS: "-eval application:load(crypto)"
run: |
mix deps.get
mix deps.compile
# AC1, before anything else runs: the runtime is in the mode and names the provider.
- name: The runtime is in FIPS mode
run: |
erl -noshell -eval 'enabled = crypto:info_fips(), io:format("~p~n", [crypto:info()]), halt().'
# AC3, the default half: the listing with the mode off equals the committed file; the
# mode-on half is test/fips/mode_test.exs, inside the suite.
- name: crypto:supports/0 with the mode off matches docs/fips-leg/supports-default.txt
env:
ERL_AFLAGS: "-eval application:load(crypto)"
run: elixir scripts/crypto_supports.exs | diff - docs/fips-leg/supports-default.txt
- name: DCO sign-off present on every commit
run: |
git log --format=%B ${{ github.event.pull_request.base.sha || 'HEAD~1' }}..HEAD 2>/dev/null \
| grep -q '^Signed-off-by: ' || {
echo "::error::a commit in this range has no Signed-off-by line"; exit 1; }
- run: mix gate
# AC2 and AC3 by test name with this run's id in the log, and AC5's coverage line for
# this leg. The gate above already ran the suite; this repeats the four FIPS tests with
# their names printed, then the whole suite once more under the coverage tool.
- name: The FIPS tests by name, and this leg's coverage
run: |
mix test --trace test/fips
mix test --cover | grep -E 'Result:|\| *Total'