Skip to content

Serve each table's schema in the summary tier, and set search_path fo… #867

Serve each table's schema in the summary tier, and set search_path fo…

Serve each table's schema in the summary tier, and set search_path fo… #867

Workflow file for this run

name: CI
# The unbypassable quality + secret gate on every PR (local hooks can be skipped;
# CI can't). Config lives in pyproject.toml so nothing is duplicated here.
# Branch protection is what makes these checks *required* to merge.
on:
pull_request:
push:
branches: [main]
permissions:
contents: read
jobs:
lint-and-test:
name: lint + test (py${{ matrix.python-version }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
with:
python-version: ${{ matrix.python-version }}
# Lint + import sorting — blocking. Rules come from pyproject.toml.
# packages/ holds the agami-core library.
- name: ruff check
run: uvx ruff@0.15.19 check plugins packages tests dev.py dev
# Format check is informational for now: the tree has a large unformatted
# backlog (~74 files). Flip `continue-on-error` off after a dedicated
# `ruff format` pass makes the tree clean.
- name: ruff format --check (informational)
run: uvx ruff@0.15.19 format --check plugins packages tests dev.py dev
continue-on-error: true
# Coverage is ENFORCED, not just reported. Measured 2026-07-29 at 85.66% on every leg of this
# matrix — 3.10, 3.11 and 3.12 alike, 1584 tests — so the floor sits just below it, and because
# no leg measured differently, enforcing it on one leg (below) loses nothing. Before this the job printed a coverage report and
# failed on nothing: OCR-024 claimed a >=80% floor that was never in the workflow, so coverage
# could regress to zero and CI stayed green.
# Ratchet upward as the suite grows; never lower it to make a red build pass.
#
# The suite imports the agami-core library, so install it editable with the
# [model] extra (pydantic/pyyaml/sqlglot — sqlglot backs the binding-validation and
# unit-resolution paths; without it ~287 tests skip). DB drivers are intentionally
# omitted: those tests skip cleanly without a database.
#
# Why two steps and `-n auto` (#296): this step took ~600s on EVERY leg, one test at a time,
# under coverage tracing. `--durations` showed no handful of slow tests to fix — the slowest is
# ~4s and the 40 slowest are a quarter of the run — so the time is the long tail, and the lever
# is running it in parallel, not trimming it. The floor is enforced on 3.12 only, with the
# `sysmon` tracer that 3.12 added, because tracing is what costs most on the older versions and
# the three legs measured the same coverage when the floor was set. 3.10 and 3.11 still run the
# whole suite; they just don't trace it. Nothing is deselected on any leg.
- name: pytest
if: matrix.python-version != '3.12'
run: >-
uvx --python ${{ matrix.python-version }}
--with pytest --with pytest-xdist
--with-editable "packages/agami-core[model,server]"
pytest tests/ -q -n auto
- name: pytest --cov
if: matrix.python-version == '3.12'
env:
COVERAGE_CORE: sysmon
run: >-
uvx --python ${{ matrix.python-version }}
--with pytest --with pytest-cov --with pytest-xdist
--with-editable "packages/agami-core[model,server]"
pytest tests/ -q -n auto --cov=plugins --cov=packages/agami-core/src --cov-report=term-missing
--cov-fail-under=85
# The safety corpus gets its own required checks rather than riding inside `lint-and-test`.
# Two reasons, and the second is why the spec exists:
#
# * `lint-and-test` runs the corpus's FILE-path half already, but a failure there reads as "the
# suite is red" — a named job makes a safety regression attributable at a glance, and branch
# protection can require it by name.
# * the DB-served half cannot run there at all: `lint-and-test` installs no database driver and
# has no Postgres, so those tests skip. A suite that skips is a suite that exits 0, which is
# precisely how this evidence went missing before. The job below sets `AGAMI_IT_PG_REQUIRED`,
# which turns a missing dependency into a failure (`tests/e2e/itdeps.py`) and arms the
# collection sentinel that ends the session if the run collected fewer vectors than the corpus
# holds (`tests/e2e/conftest.py`).
#
# Both jobs name their work by PATH, never by `-k`: the job these replace selected with
# `pytest -k "db_path or role"`, a substring match on the node id, and a rename dropped 102 of 108
# vectors while the job still exited 0.
safety-corpus-file-path:
name: safety corpus (file path, no database)
runs-on: ubuntu-latest
env:
# This job's own declaration that it must RUN its half, and it needs a name of its own: the
# DB sentinel below would demand a Postgres this job deliberately does not have. It turns a
# missing model dependency into a failure (`tests/e2e/itdeps.py`) and forbids a run-time skip
# anywhere in the directory (`tests/e2e/conftest.py`).
#
# Measured before it existed: with `sqlglot` unimportable, `pytest tests/e2e` reported
# `4 passed, 6 skipped` and exited 0 — every corpus module opened with `pytest.importorskip`,
# so the required check passed having collected almost none of the corpus.
AGAMI_E2E_REQUIRED: "1"
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
with:
python-version: "3.12"
# No database and no driver, deliberately: this is the half that must run on a machine with
# neither, and the DB-backed modules skip cleanly here because `AGAMI_IT_PG_REQUIRED` is NOT
# set. Coverage is not measured — `lint-and-test` owns the floor, and duplicating it here
# would report a number for a fraction of the suite.
- name: pytest tests/e2e (file path)
run: >-
uvx --python 3.12
--with pytest
--with-editable "packages/agami-core[model,server]"
pytest tests/e2e -q
safety-corpus-db-path:
name: safety corpus (DB path, Postgres in Docker)
runs-on: ubuntu-latest
env:
# The same values `tests/integration/docker-compose.yml` uses, so one invocation works in both
# places. The password belongs to a throwaway container that exists for the length of this job
# and is reachable from nowhere else; it is a fixture, not a credential.
AGAMI_IT_PG_HOST: 127.0.0.1
AGAMI_IT_PG_PORT: "55432"
AGAMI_IT_PG_USER: agami_test
AGAMI_IT_PG_PASSWORD: agami_test_pw
# The whole point of this job. It makes a missing driver a FAILURE rather than a skip, and it
# arms both halves of the sentinel — the collection count and the session count — so a run that
# cannot execute the DB-backed evidence ends red instead of reporting green for work it never
# selected.
#
# These two lines and the password above are the one place all of that can be switched off
# from, which is why `tests/e2e/test_suite_integrity.py` reads this file and asserts they are
# here. That test runs in `lint + test`, not in this job, so the edit that disarms this job
# cannot also disarm the check on it.
AGAMI_IT_PG_REQUIRED: "1"
# This job runs the file-path half too, so it holds itself to the same no-skips rule.
AGAMI_E2E_REQUIRED: "1"
services:
postgres:
image: postgres:16
env:
POSTGRES_DB: shop
POSTGRES_USER: agami_test
POSTGRES_PASSWORD: agami_test_pw
ports:
- 55432:5432
options: >-
--health-cmd "pg_isready -U agami_test -d shop"
--health-interval 2s
--health-timeout 3s
--health-retries 30
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
with:
python-version: "3.12"
# The role is created by executing the SAME file the compose fixture mounts into
# `docker-entrypoint-initdb.d`, so the recipe has exactly one copy and a change to it reaches
# both paths. Only the DELIVERY differs, and it has to: a service container starts BEFORE
# `actions/checkout` runs, so there is no repository on disk to mount from — and the image's
# init directory is read once, on an empty data directory, which by then has passed.
#
# Run through the container's own `psql` rather than the runner's, so the client is present and
# version-matched by construction. `ON_ERROR_STOP` is what makes a broken grants script fail
# this step: without it psql reports the error and exits 0, and every DB test below would then
# fail one layer down with a confusing permissions message instead.
- name: create the read-only role and the corpus database
run: |
docker exec -i "${{ job.services.postgres.id }}" \
psql -v ON_ERROR_STOP=1 -U agami_test -d shop \
< tests/integration/fixtures/postgres-readonly-grants.sql
# `psycopg2-binary` on top of the standard extras: without it the DB modules would raise
# through `itdeps.importorfail`, which is the correct outcome for a broken job and a waste of a
# run for a working one.
- name: pytest tests/e2e (DB path)
run: >-
uvx --python 3.12
--with pytest --with psycopg2-binary
--with-editable "packages/agami-core[model,server]"
pytest tests/e2e -q
gitleaks:
name: gitleaks (secret scan)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0 # full history, so a secret in any commit is caught
# Run the gitleaks binary directly: the gitleaks-action requires a paid
# license for organization repos; the CLI does not. Pinned to a release.
- name: gitleaks detect
run: |
V=8.30.1
base="https://github.com/gitleaks/gitleaks/releases/download/v${V}"
curl -sSfL -O "${base}/gitleaks_${V}_linux_x64.tar.gz"
curl -sSfL -O "${base}/gitleaks_${V}_checksums.txt"
# Verify the download: pull the exact checksum line for our artifact and check it,
# failing if that line is absent (so a renamed/missing entry can't skip verification).
grep "gitleaks_${V}_linux_x64.tar.gz$" "gitleaks_${V}_checksums.txt" | sha256sum -c -
tar -xzf "gitleaks_${V}_linux_x64.tar.gz" gitleaks
./gitleaks detect --source . --redact --no-banner