Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
19 changes: 19 additions & 0 deletions .github/scripts/build-simulation-api-client.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/usr/bin/env bash
# Version and build the generated simulation API Python client.
# Usage: build-simulation-api-client.sh <client-directory>
# Required env vars: RUN_NUMBER (the workflow run number).
# The client is versioned 0.<YYYYMMDD>.<RUN_NUMBER> and built into
# <client-directory>/dist/.

set -euo pipefail

CLIENT_DIR="${1:?Client directory required}"
: "${RUN_NUMBER:?RUN_NUMBER is required}"

cd "$CLIENT_DIR"
# Update version
DATE=$(date +%Y%m%d)
NEW_VERSION="0.${DATE}.${RUN_NUMBER}"
sed -i.bak "s/^version = .*/version = \"${NEW_VERSION}\"/" pyproject.toml
rm pyproject.toml.bak
uv build
2 changes: 1 addition & 1 deletion .github/scripts/modal-precompute.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ fi
# waste the whole run.
if [ -z "${GITHUB_OUTPUT:-}" ]; then
echo "GITHUB_OUTPUT is required (the manifest digest is exported there)." >&2
echo 'For local runs: export GITHUB_OUTPUT="$(mktemp)".' >&2
echo "For local runs: export GITHUB_OUTPUT=\"\$(mktemp)\"." >&2
exit 1
fi

Expand Down
24 changes: 24 additions & 0 deletions .github/scripts/start-local-services.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/usr/bin/env bash
# Start the local Docker Compose services and wait for them to answer.
# Usage: start-local-services.sh <compose-file>
#
# Waits 10 seconds, then polls the local gateway's liveness endpoint up to 30
# times, 2 seconds apart. It does not fail when the services never answer: the
# integration tests that follow report that.

set -euo pipefail

COMPOSE_FILE="${1:?Docker Compose file required}"

docker compose -f "$COMPOSE_FILE" up -d
# Wait for services to be ready
sleep 10
# Check services are responding
for i in {1..30}; do
if curl -f http://localhost:8082/ping/alive; then
echo "Services are ready!"
break
fi
echo "Waiting for services... (attempt $i/30)"
sleep 2
done
4 changes: 1 addition & 3 deletions .github/workflows/pr-image-smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,4 @@ jobs:
MODAL_TOKEN_ID: ${{ secrets.MODAL_TOKEN_ID }}
MODAL_TOKEN_SECRET: ${{ secrets.MODAL_TOKEN_SECRET }}
PE_UK_PRIVATE_HF_READ_TOKEN: ${{ secrets.PE_UK_PRIVATE_HF_READ_TOKEN }}
run: |
chmod +x .github/scripts/modal-image-smoke.sh
.github/scripts/modal-image-smoke.sh staging
run: .github/scripts/modal-image-smoke.sh staging
44 changes: 13 additions & 31 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,12 @@ jobs:
enable-cache: true

- name: Install dependencies
run: |
cd ${{ matrix.project }}
uv sync --extra test
working-directory: ${{ matrix.project }}
run: uv sync --extra test

- name: Run tests
run: |
cd ${{ matrix.project }}
uv run pytest tests/ -v
working-directory: ${{ matrix.project }}
run: uv run pytest tests/ -v

lint:
name: Lint
Expand All @@ -99,13 +97,7 @@ jobs:
run: uv tool install ruff==0.15.22

- name: Run ruff format check
run: |
for dir in projects/*/src libs/*/src; do
if [ -d "$dir" ]; then
echo "Checking format in $dir..."
uv run ruff format --check $dir
fi
done
run: uv run ruff format --check projects/*/src libs/*/src

quality-guards:
name: Quality guards
Expand Down Expand Up @@ -184,26 +176,16 @@ jobs:
./scripts/generate-clients.sh

- name: Start services
run: |
docker compose -f deployment/docker-compose.yml up -d
# Wait for services to be ready
sleep 10
# Check services are responding
for i in {1..30}; do
if curl -f http://localhost:8082/ping/alive; then
echo "Services are ready!"
break
fi
echo "Waiting for services... (attempt $i/30)"
sleep 2
done
run: .github/scripts/start-local-services.sh deployment/docker-compose.yml

- name: Install integration test dependencies
working-directory: projects/policyengine-apis-integ
run: uv sync --extra test

- name: Run integration tests (local services only)
run: |
cd projects/policyengine-apis-integ
uv sync --extra test
# Run tests against local Docker services only (not Modal staging)
uv run pytest tests/ -v -m "not requires_gcp and not beta_only"
working-directory: projects/policyengine-apis-integ
# Run tests against local Docker services only (not Modal staging)
run: uv run pytest tests/ -v -m "not requires_gcp and not beta_only"

- name: Show service logs on failure
if: failure()
Expand Down
11 changes: 3 additions & 8 deletions .github/workflows/publish-clients.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,9 @@ jobs:
./scripts/generate-clients.sh

- name: Build simulation API client from the entrypoint contract
run: |
cd projects/policyengine-simulation-entry/artifacts/clients/python
# Update version
DATE=$(date +%Y%m%d)
RUN_NUMBER="${{ github.run_number }}"
NEW_VERSION="0.${DATE}.${RUN_NUMBER}"
sed -i "s/^version = .*/version = \"${NEW_VERSION}\"/" pyproject.toml
uv build
env:
RUN_NUMBER: ${{ github.run_number }}
run: .github/scripts/build-simulation-api-client.sh projects/policyengine-simulation-entry/artifacts/clients/python

# PyPI trusted publishing: uploads accepted only from this workflow in the
# pypi environment (limited to main). No stored token.
Expand Down
1 change: 1 addition & 0 deletions changelog_entry.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
- Made CLAUDE.md and the Copilot instructions thin pointers to AGENTS.md, required workflow logic longer than one line to live in tested and shellchecked scripts, and registered the tracking-issue rule
- Added a rules registry (rules/registry.toml) with a standard-library tool that validates it, checks every rule is enforced where it claims, and renders docs/generated/rules.md and the Rules block in AGENTS.md
changed:
- Moved multi-line `run` blocks in the PR, image smoke and client publishing workflows into single-line steps and tested scripts, tested the dependency-update scripts, fixed the shellcheck finding in modal-precompute.sh, and enforced the CI-script testing and shellcheck rules with empty baselines
- Added CODEOWNERS for the rule machinery, pinned CI's ruff to 0.15.22 with an explicit rule selection, and documented that agents never bypass branch rules
- Ran the Modal image smoke for Stage 12 persistence changes and added policyengine-fastapi to the PR test matrix
- Ran the full PR workflow and the Modal image smoke only on the lowest unmerged pull request in a stack, behind a new CI complete gate job that fails on the others
Expand Down
16 changes: 7 additions & 9 deletions docs/generated/rules.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# Rules

Generated by `python -m scripts.rules render` (scripts/rules v0.1.0, tool sha256 `afc55ba219f1`) from `rules/registry.toml`. Do not edit by hand.
Generated by `python -m scripts.rules render` (scripts/rules v0.1.0, tool sha256 `86b191f2ebb3`) from `rules/registry.toml`. Do not edit by hand.

Every rule that governs `policyengine-sim-api` is listed here with how it is enforced. Blocking rules fail CI. The Gaps section lists what is written down but not yet enforced or proven. To add or change a rule, see `docs/engineering/skills/rules-registry.md`.

## Summary

| Tier | enforced | ratcheted | advisory | doc-only | Total |
| --- | ---: | ---: | ---: | ---: | ---: |
| mechanical | 26 | 3 | 1 | 14 | 44 |
| mechanical | 28 | 1 | 1 | 14 | 44 |
| metric | 0 | 0 | 0 | 0 | 0 |
| judgment | 0 | 0 | 0 | 0 | 0 |

Expand Down Expand Up @@ -60,9 +60,7 @@ Every rule that governs `policyengine-sim-api` is listed here with how it is enf

### Ratcheted (existing violations still tolerated)

- `sim.ci.multiline-run-is-script`: 20 remaining in `rules/baselines/ci-multiline-run.txt`
- `sim.ci.shell-scripts-pass-shellcheck`: 1 remaining in `rules/baselines/ci-shellcheck.txt`
- `sim.ci.workflow-scripts-tested`: 4 remaining in `rules/baselines/ci-untested-scripts.txt`
- `sim.ci.multiline-run-is-script`: 13 remaining in `rules/baselines/ci-multiline-run.txt`

### Exceptions and review dates

Expand Down Expand Up @@ -267,16 +265,16 @@ flowchart LR
| Rule | Statement | Tier | Status | Blocking | Enforced by | CI | Proof |
| --- | --- | --- | --- | --- | --- | --- | --- |
| `sim.ci.multiline-run-is-script` | A GitHub Actions run: block holds one line of code and no chained commands (&&, \|\|, ;); anything more lives in a tested script under .github/scripts/, called from a single-line step. | mechanical | ratcheted | yes | quality guard `ci-scripts` | `pr.yml:quality-guards` | yes |
| `sim.ci.shell-scripts-pass-shellcheck` | Every shell script under .github/scripts/ passes shellcheck. | mechanical | ratcheted | yes | quality guard `ci-scripts` | `pr.yml:quality-guards` | yes |
| `sim.ci.workflow-scripts-tested` | Every shell script a workflow calls is run by at least one test. | mechanical | ratcheted | yes | quality guard `ci-scripts` | `pr.yml:quality-guards` | yes |
| `sim.ci.shell-scripts-pass-shellcheck` | Every shell script under .github/scripts/ passes shellcheck. | mechanical | enforced | yes | quality guard `ci-scripts` | `pr.yml:quality-guards` | yes |
| `sim.ci.workflow-scripts-tested` | Every shell script a workflow calls is run by at least one test. | mechanical | enforced | yes | quality guard `ci-scripts` | `pr.yml:quality-guards` | yes |

```mermaid
flowchart LR
r_sim_ci_multiline_run_is_script("sim.ci.multiline-run-is-script (ratcheted)")
e_quality_guard_ci_scripts["quality guard ci-scripts"]
j_CI_pr_yml_quality_guards["CI pr.yml:quality-guards"]
r_sim_ci_shell_scripts_pass_shellcheck("sim.ci.shell-scripts-pass-shellcheck (ratcheted)")
r_sim_ci_workflow_scripts_tested("sim.ci.workflow-scripts-tested (ratcheted)")
r_sim_ci_shell_scripts_pass_shellcheck("sim.ci.shell-scripts-pass-shellcheck (enforced)")
r_sim_ci_workflow_scripts_tested("sim.ci.workflow-scripts-tested (enforced)")
r_sim_ci_multiline_run_is_script --> e_quality_guard_ci_scripts
e_quality_guard_ci_scripts --> j_CI_pr_yml_quality_guards
r_sim_ci_shell_scripts_pass_shellcheck --> e_quality_guard_ci_scripts
Expand Down
Loading
Loading