docs: add the assurance case #422
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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' |