Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
ed1f5a2
Describe tables from PostgreSQL's catalog and list how two descriptio…
anth-volk Nov 3, 2026
b5fc3d4
Compare sim's table models with the Public API's tables at a pinned c…
anth-volk Nov 3, 2026
ccacc35
Compare the models' tables by name now that the models name their schema
anth-volk Nov 3, 2026
b64b136
Mark the schema rule's proof so the rule's own CI jobs run it
anth-volk Nov 3, 2026
42db4d2
Keep no token in the schema jobs, and close the review's coverage and…
anth-volk Nov 3, 2026
97cadb8
Keep every test the schema rule names in the schema jobs, and move th…
anth-volk Nov 3, 2026
bfa2908
Check every persistence rule's tests against the markers its jobs select
anth-volk Nov 3, 2026
6de9087
Pin the Public API commit that moves report runs' untabled results to…
anth-volk Nov 3, 2026
771bf66
Require every persistence rule's job to select its tests by one marker
anth-volk Nov 3, 2026
bd0bd23
Pin the Public API pull request's head after its move onto master
anth-volk Nov 3, 2026
7d070c5
Merge the simulation service's renamed setting, yes-or-no readiness a…
anth-volk Nov 5, 2026
111e67b
Open an issue, or comment on the open one, when the weekly schema com…
anth-volk Nov 5, 2026
a092043
Test the drift report with gh's own filter, and name a failed setup s…
anth-volk Nov 5, 2026
63649d5
Merge the readiness logging fixes from the pull request below
anth-volk Nov 5, 2026
0fc8655
Merge the database connection's main merge and the Public API's new t…
anth-volk Nov 6, 2026
99065d5
Build the Public API's referenced tables before comparing, and requir…
anth-volk Nov 6, 2026
138c9c0
Pin 772ca6b14, the Public API pull request's head with the decided ta…
anth-volk Nov 6, 2026
5d0eab1
Merge the review round's tests from the database connection
anth-volk Nov 6, 2026
35f6feb
Compare the Public API's keys by name, reading each key's table as Po…
anth-volk Nov 6, 2026
e100348
Merge the shared decoy-schema setup from the database connection
anth-volk Nov 6, 2026
bbf2add
Compare each Public API key's table too, and test quoted names
anth-volk Nov 6, 2026
fe54bad
Merge the keys expected by table and name from the database connection
anth-volk Nov 6, 2026
d32eca6
Find the keys to the Public API's tables on every table, by table and…
anth-volk Nov 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .github/scripts/public-api-commit-on-default-branch.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/usr/bin/env bash
# Require the checked-out Public API commit to be on the Public API's default
# branch, so sim's table models only ever describe a migration chain the
# Public API has merged (rule sim.persistence.models-match-public-api).
#
# Usage: public-api-commit-on-default-branch.sh <Public API checkout> <branch>
set -euo pipefail

checkout="${1:?usage: public-api-commit-on-default-branch.sh <checkout> <branch>}"
branch="${2:?usage: public-api-commit-on-default-branch.sh <checkout> <branch>}"
commit="$(git -C "${checkout}" rev-parse HEAD)"
git -C "${checkout}" fetch --quiet origin "${branch}"
if ! git -C "${checkout}" merge-base --is-ancestor "${commit}" FETCH_HEAD; then
printf '%s is not on the Public API'"'"'s %s branch yet; pin a merged commit\n' \
"${commit}" "${branch}" >&2
exit 1
fi
printf '%s is on the Public API'"'"'s %s branch\n' "${commit}" "${branch}"
21 changes: 21 additions & 0 deletions .github/scripts/public-api-schema-commit.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/usr/bin/env bash
# Read the pinned Public API commit whose v2 migration chain sim's table
# models describe (rule sim.persistence.models-match-public-api).
#
# Usage: public-api-schema-commit.sh <pin file>
#
# The pin file holds one full commit hash. The script prints it and, on
# GitHub Actions, passes it to the later steps as the step output `commit`.
set -euo pipefail

pin_file="${1:?usage: public-api-schema-commit.sh <pin file>}"
commit="$(tr -d '[:space:]' < "${pin_file}")"
if ! [[ "${commit}" =~ ^[0-9a-f]{40}$ ]]; then
printf '%s must hold one Public API commit as 40 lowercase hexadecimal characters\n' \
"${pin_file}" >&2
exit 1
fi
printf 'Pinned Public API commit: %s\n' "${commit}"
if [ -n "${GITHUB_OUTPUT:-}" ]; then
printf 'commit=%s\n' "${commit}" >> "${GITHUB_OUTPUT}"
fi
49 changes: 49 additions & 0 deletions .github/scripts/public-api-schema-drift-issue.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/usr/bin/env bash
# Report a failed weekly comparison of sim's table models with the Public
# API's default branch: comment on the open issue that carries the drift
# label, or open one. Runs with the workflow's own token (GH_TOKEN, with
# issues: write) and GitHub's GITHUB_SERVER_URL, GITHUB_REPOSITORY and
# GITHUB_RUN_ID, which name the failed run.
set -euo pipefail

label="public-api-schema-drift"
run_url="${GITHUB_SERVER_URL:?}/${GITHUB_REPOSITORY:?}/actions/runs/${GITHUB_RUN_ID:?}"

# Creates the label, or leaves it as it is apart from its color and text.
gh label create "${label}" --force --color B60205 \
--description "The weekly comparison with the Public API's tables failed"

open_issue="$(
gh issue list --label "${label}" --state open --limit 1 \
--json number --jq '.[0].number // empty'
)"

if [ -n "${open_issue}" ]; then
printf 'The comparison failed again: %s\n' "${run_url}" |
gh issue comment "${open_issue}" --body-file -
exit 0
fi

gh issue create \
--title "The weekly comparison with the Public API's tables failed" \
--label "${label}" \
--body-file - <<BODY
The \`Public API schema drift\` workflow compares sim's table models with the
tables the Public API's v2 migration chain builds at the head of its
\`master\` branch, and it failed: ${run_url}

Its log says which of these happened:

- The Public API merged a change to a table the simulation service writes,
or to the service role's grants, that the models in
\`libs/policyengine-simulation-persistence\` haven't followed. Update the
models, and move the pinned commit in
\`libs/policyengine-simulation-persistence/public-api-commit\`, in one pull
request.
- The Public API's tables couldn't be built at that head.
- A setup step failed, such as the database service, a checkout, or installing
Python, uv or the dependencies. Then re-run the workflow.

While this issue is open, later failures are added to it as comments. Close
it once the comparison passes again.
BODY
33 changes: 33 additions & 0 deletions .github/scripts/public-api-schema-reference.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env bash
# Build the Public API's tables with its own v2 migration chain, and grant the
# table privileges its manifest declares, for the comparison with sim's table
# models (rule sim.persistence.models-match-public-api).
#
# Usage: public-api-schema-reference.sh <Public API checkout>
#
# PUBLIC_API_SCHEMA_DATABASE_URL names the empty database to build, as
# postgresql://user:password@host:port/database. The Public API's disposable
# migration mode accepts only the local policyengine_v2_alembic_test database,
# with explicit credentials, and creates the roles its manifest manages
# without a login. The script prints the Public API commit it built and, on
# GitHub Actions, passes it to the later steps as PUBLIC_API_SCHEMA_COMMIT, so
# a failed comparison names it.
set -euo pipefail

checkout="${1:?usage: public-api-schema-reference.sh <Public API checkout>}"
database_url="${PUBLIC_API_SCHEMA_DATABASE_URL:?PUBLIC_API_SCHEMA_DATABASE_URL must name the database to build}"

cd "${checkout}"
commit="$(git rev-parse HEAD)"
printf 'Public API commit: %s\n' "${commit}"
if [ -n "${GITHUB_ENV:-}" ]; then
printf 'PUBLIC_API_SCHEMA_COMMIT=%s\n' "${commit}" >> "${GITHUB_ENV}"
fi
# The Public API installs its lock on Python 3.12, as its own CI does.
export UV_PYTHON=3.12
export V2_MIGRATION_DATABASE_URL="postgresql+psycopg://${database_url#*://}"
export V2_ALEMBIC_DISPOSABLE_TEST=1
export DEPLOYMENT_ENVIRONMENT=ci
uv sync --frozen --extra dev
uv run --frozen --extra dev alembic -c alembic-v2.ini upgrade head
uv run --frozen --extra dev python scripts/reconcile_v2_privileges.py apply --create-missing-roles
95 changes: 91 additions & 4 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
# workflows on every pull request. See docs/engineering/skills/github-prs.md.
name: CI complete
if: ${{ always() }}
needs: [test, simulation-database, lint, pyright, quality-guards, docker-build, test-integration]
needs: [test, simulation-database, public-api-schema, lint, pyright, quality-guards, docker-build, test-integration]
runs-on: ubuntu-latest
permissions:
contents: read
Expand Down Expand Up @@ -81,9 +81,10 @@ jobs:

simulation-database:
name: Simulation database
# Runs the persistence tests that need PostgreSQL, which the Test job
# skips, on a fresh server. Full CI runs only on the lowest unmerged pull
# request in a stack.
# Runs the persistence tests marked postgres, which the Test job skips,
# on a fresh server. Tests that need the Public API's tables are marked
# public_api_schema instead and run in the Public API schema job. Full CI
# runs only on the lowest unmerged pull request in a stack.
if: ${{ !github.event.pull_request.stack || github.event.pull_request.stack.base.ref == github.event.pull_request.base.ref }}
runs-on: ubuntu-latest
permissions:
Expand Down Expand Up @@ -129,6 +130,92 @@ jobs:
working-directory: libs/policyengine-simulation-persistence
run: uv run --frozen pytest tests/ -v -m postgres

public-api-schema:
name: Public API schema
# Builds the Public API's tables with its own v2 migration chain, at the
# commit pinned in libs/policyengine-simulation-persistence/public-api-commit,
# grants the privileges its manifest declares, and runs the persistence
# tests marked public_api_schema against them: sim's table models must
# match those tables, and the simulation service's role must write them
# (rule sim.persistence.models-match-public-api). The pinned commit must
# also be on the Public API's default branch. The Public API's repository
# is public, so the job reads no secret. Full CI runs only on the lowest
# unmerged pull request in a stack.
if: ${{ !github.event.pull_request.stack || github.event.pull_request.stack.base.ref == github.event.pull_request.base.ref }}
runs-on: ubuntu-latest
permissions:
contents: read
services:
postgres:
image: postgres:17
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: simulation_ci
# The Public API's disposable migration mode accepts only this
# database; the server's postgres database holds the models' tables.
POSTGRES_DB: policyengine_v2_alembic_test
ports:
- 5432:5432
options: >-
--health-cmd="pg_isready -U postgres"
--health-interval=5s
--health-timeout=5s
--health-retries=20
env:
SIMULATION_TEST_DATABASE_URL: postgresql://postgres:simulation_ci@127.0.0.1:5432/postgres
PUBLIC_API_SCHEMA_DATABASE_URL: postgresql://postgres:simulation_ci@127.0.0.1:5432/policyengine_v2_alembic_test
# A missing server or Public API database fails the tests instead of
# skipping them.
SIMULATION_REQUIRE_DATABASE_TESTS: "1"
SIMULATION_REQUIRE_PUBLIC_API_SCHEMA: "1"

steps:
# Neither checkout keeps the job's token, since the job runs the Public
# API's code from a pinned commit.
- uses: actions/checkout@v6
with:
persist-credentials: false

- name: Read the pinned Public API commit
id: public-api
run: .github/scripts/public-api-schema-commit.sh libs/policyengine-simulation-persistence/public-api-commit

- name: Check out the pinned Public API commit
uses: actions/checkout@v6
with:
repository: PolicyEngine/policyengine-api
ref: ${{ steps.public-api.outputs.commit }}
path: public-api
persist-credentials: false

- name: Set up Python
uses: actions/setup-python@v6
with:
# 3.12 for the Public API's lock, as its own CI uses; 3.13 for sim's.
python-version: |
3.12
3.13

- name: Install uv
uses: astral-sh/setup-uv@v8.1.0
with:
version: "0.13.6"
enable-cache: true

- name: Build the Public API's tables at the pinned commit
run: .github/scripts/public-api-schema-reference.sh public-api

- name: Install dependencies
working-directory: libs/policyengine-simulation-persistence
run: uv sync --extra test --frozen

- name: Compare sim's table models with the Public API's tables
working-directory: libs/policyengine-simulation-persistence
run: uv run --frozen pytest tests/ -v -m public_api_schema

- name: Require the pinned commit on the Public API's default branch
run: .github/scripts/public-api-commit-on-default-branch.sh public-api master

lint:
name: Lint
# Full CI runs only on the lowest unmerged pull request in a stack.
Expand Down
111 changes: 111 additions & 0 deletions .github/workflows/public-api-schema-drift.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
name: Public API schema drift

# Compares sim's table models with the tables the Public API's v2 migration
# chain builds at the head of its default branch, every Monday and on demand.
# Pull requests compare them at the pinned commit instead (the PR workflow's
# public-api-schema job). A failure here means the Public API has merged a
# change to a table the simulation service writes, or to its role's grants,
# that the models and the pin need to follow, or that its tables can't be
# built at that head; the log says which (rule
# sim.persistence.models-match-public-api). The Public API's repository is
# public, so the workflow reads no secret. When the comparison fails, a second
# job opens an issue labelled public-api-schema-drift, or comments on the open
# one, with the workflow's own token; only that job may write issues.

on:
schedule:
- cron: "17 6 * * 1"
workflow_dispatch:

permissions:
contents: read

jobs:
public-api-schema-drift:
name: Compare the models with the Public API's default branch
runs-on: ubuntu-latest
services:
postgres:
image: postgres:17
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: simulation_ci
# The Public API's disposable migration mode accepts only this
# database; the server's postgres database holds the models' tables.
POSTGRES_DB: policyengine_v2_alembic_test
ports:
- 5432:5432
options: >-
--health-cmd="pg_isready -U postgres"
--health-interval=5s
--health-timeout=5s
--health-retries=20
env:
SIMULATION_TEST_DATABASE_URL: postgresql://postgres:simulation_ci@127.0.0.1:5432/postgres
PUBLIC_API_SCHEMA_DATABASE_URL: postgresql://postgres:simulation_ci@127.0.0.1:5432/policyengine_v2_alembic_test
# A missing server or Public API database fails the tests instead of
# skipping them.
SIMULATION_REQUIRE_DATABASE_TESTS: "1"
SIMULATION_REQUIRE_PUBLIC_API_SCHEMA: "1"

steps:
# Neither checkout keeps the job's token, since the job runs the Public
# API's code.
- uses: actions/checkout@v6
with:
persist-credentials: false

- name: Check out the Public API's default branch
uses: actions/checkout@v6
with:
repository: PolicyEngine/policyengine-api
ref: master
path: public-api
persist-credentials: false

- name: Set up Python
uses: actions/setup-python@v6
with:
# 3.12 for the Public API's lock, as its own CI uses; 3.13 for sim's.
python-version: |
3.12
3.13

- name: Install uv
uses: astral-sh/setup-uv@v8.1.0
with:
version: "0.13.6"
enable-cache: true

- name: Build the Public API's tables at its default branch
run: .github/scripts/public-api-schema-reference.sh public-api

- name: Install dependencies
working-directory: libs/policyengine-simulation-persistence
run: uv sync --extra test --frozen

- name: Compare sim's table models with the Public API's tables
working-directory: libs/policyengine-simulation-persistence
run: uv run --frozen pytest tests/ -v -m public_api_schema

report-public-api-schema-drift:
name: Open or update the drift issue
needs: public-api-schema-drift
# Only a failed comparison: a passing or cancelled run reports nothing.
if: ${{ failure() }}
runs-on: ubuntu-latest
# It checks out sim alone, to run its script; it never checks out or runs
# the Public API's code.
permissions:
contents: read
issues: write
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false

- name: Open or comment on the drift issue
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
run: .github/scripts/public-api-schema-drift-issue.sh
17 changes: 9 additions & 8 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,14 @@ same-repository draft PR.
- The simulation service lives in `projects/policyengine-simulation-executor`.
- API integration tests live in `projects/policyengine-apis-integ`.
- PR CI runs simulation unit tests, the persistence tests that need
PostgreSQL, Ruff format and lint checks, Pyright for every package, quality
guards (including the rules registry), Docker build, and local integration
tests. Two jobs run separately, each from its own workflow, on every PR,
stacked ones included: `PR description` checks that every open PR's
description starts with `Fixes #ISSUE_NUMBER` for an open issue in this
repository, and `Changelog fragment` checks that the PR adds a changelog
fragment relative to its own base branch.
PostgreSQL, the comparison of the persistence models with the Public API's
tables at a pinned commit, Ruff format and lint checks, Pyright for every
package, quality guards (including the rules registry), Docker build, and
local integration tests. Two jobs run separately, each from its own
workflow, on every PR, stacked ones included: `PR description` checks that
every open PR's description starts with `Fixes #ISSUE_NUMBER` for an open
issue in this repository, and `Changelog fragment` checks that the PR adds a
changelog fragment relative to its own base branch.
- Every PR adds a changelog fragment, `changelog.d/<name>.<type>.md`, and never
edits `CHANGELOG.md` to describe a change (rule
`sim.process.changelog-fragment`). A merge to `main` doesn't deploy itself:
Expand Down Expand Up @@ -135,7 +136,7 @@ To add or change a rule, read `docs/engineering/skills/rules-registry.md`.

### Simulation service database

- Checked: `sim.persistence.missing-privileges-reported`
- Checked: `sim.persistence.missing-privileges-reported`, `sim.persistence.models-match-public-api`

### Deployment pipeline

Expand Down
1 change: 1 addition & 0 deletions changelog.d/b2-public-api-schema-check.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CI checks sim's models of the tables the simulation service writes against the tables the Public API's migration chain builds at a pinned Public API commit, and that the service's role can write them; a weekly run compares them with the Public API's default branch, and opens an issue, or comments on the open one, when that comparison fails.
Loading
Loading