Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
f75ec29
Add an Axiom parity check and finder for country-model PRs
MaxGhenis Oct 10, 2026
505822f
Add the pe-parity drain planner, corpus-pin check and replay fixes
MaxGhenis Oct 10, 2026
ca3edef
Finder: match issues by ancestor and part citations; extend a documen…
MaxGhenis Oct 10, 2026
f4c7616
Harden the reusable workflow: separate checkouts, warn-mode crash han…
MaxGhenis Oct 10, 2026
d38b954
Share one review_finding extractor between the lint and the drain
MaxGhenis Oct 10, 2026
b94b1bc
Address the independent review: close false negatives in the check, l…
MaxGhenis Oct 10, 2026
f59b156
Name the finder subcommand suggest (find stays as an alias)
MaxGhenis Oct 10, 2026
fc40e1d
Drain plan: block the encoder owner's module list; budget in signing …
MaxGhenis Oct 10, 2026
9f04a68
Address round 2 of the independent review
MaxGhenis Oct 10, 2026
233aef0
Drain plan: block a class of source by citation pattern; record d1262
MaxGhenis Oct 10, 2026
6fdf581
Drain plan: take the encoder owner's blocked citations as data
MaxGhenis Oct 10, 2026
f858521
Fix round 3 review findings: lint, negation, drain batches, comment o…
MaxGhenis Oct 10, 2026
3dc3748
Fix round 4 review findings: read free text towards the safe side
MaxGhenis Oct 10, 2026
d787a40
Drain plan: read the encoder owner's dispatch verdicts as holds
MaxGhenis Oct 10, 2026
988c02a
Drain plan: hold work on modules a signed run does not edit
MaxGhenis Oct 10, 2026
47f2793
Fix round 5 review findings; test each rule over its neighbourhood
MaxGhenis Oct 10, 2026
6269fb0
Drain document: no document is ready and undispatched; rulespec-uk ha…
MaxGhenis Oct 10, 2026
d09c652
Fix round 6 review findings: a strict claim grammar, closed output forms
MaxGhenis Oct 11, 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
30 changes: 30 additions & 0 deletions .github/workflows/axiom-parity-tool.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: axiom-parity tool

on:
pull_request:
paths:
- axiom-parity/**
- .github/workflows/axiom-parity*.yml
push:
branches: [main]
paths:
- axiom-parity/**

jobs:
test:
name: Lint and test axiom-parity
runs-on: ubuntu-latest
timeout-minutes: 15
defaults:
run:
working-directory: axiom-parity
steps:
- uses: actions/checkout@v6
- name: Install uv
uses: astral-sh/setup-uv@v8.1.0
- name: Check formatting
run: uvx ruff format --check .
- name: Lint
run: uvx ruff check .
- name: Test
run: uv run --group dev pytest
120 changes: 120 additions & 0 deletions .github/workflows/axiom-parity.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# Reusable check: does a policy PR carry a well-formed, verifiable `axiom:` line?
#
# Call it from a country-model repo:
#
# on:
# pull_request:
# types: [opened, edited, synchronize, reopened, ready_for_review]
# jobs:
# axiom-parity:
# uses: PolicyEngine/.github/.github/workflows/axiom-parity.yml@main
# with:
# mode: warn # warn-only rollout; switch to enforce later
# permissions:
# contents: read
# pull-requests: write # for the finder comment; read-only on fork PRs
#
# What it does is documented in CONTRIBUTING.md ("Mirror policy changes in
# Axiom") and axiom-parity/README.md.

name: Axiom parity

on:
workflow_call:
inputs:
mode:
description: "warn (never fails the job) or enforce (fails on a missing or unverifiable axiom line)"
type: string
default: warn
package:
description: "Policy package directory; default is the repo name with - replaced by _"
type: string
default: ""
rulespec:
description: "Default rulespec repo; default TheAxiomFoundation/rulespec-<country>"
type: string
default: ""
comment:
description: "Keep a finder comment on the PR with suggested axiom lines"
type: boolean
default: true
tooling-ref:
description: "Ref of PolicyEngine/.github whose axiom-parity package runs"
type: string
default: main

jobs:
axiom-parity:
name: Axiom parity
runs-on: ubuntu-latest
timeout-minutes: 10
# pull_request only: its merge ref is what the diff below reads. pull_request_target
# checks out the base branch, so the check would inspect the wrong diff.
if: github.event_name == 'pull_request'
steps:
# The PR and the tool go in separate directories, so nothing in the PR
# can stand in for the tool's code.
- name: Check out the PR merge commit
uses: actions/checkout@v6
with:
path: pr
fetch-depth: 2
persist-credentials: false

- name: Check out the axiom-parity tool
uses: actions/checkout@v6
with:
repository: PolicyEngine/.github
ref: ${{ inputs.tooling-ref }}
path: tool
sparse-checkout: axiom-parity
persist-credentials: false

- name: Install uv
uses: astral-sh/setup-uv@v8.1.0

- name: Check the axiom line
id: check
env:
GITHUB_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
PR: ${{ github.event.pull_request.number }}
MODE: ${{ inputs.mode }}
PACKAGE: ${{ inputs.package }}
RULESPEC: ${{ inputs.rulespec }}
run: |
set +e
args=(--repo "$REPO" --pr "$PR" --local-repo pr --base HEAD^1 --head HEAD
--mode "$MODE" --summary "$GITHUB_STEP_SUMMARY" --json "$RUNNER_TEMP/axiom-parity.json" --annotations)
[ -n "$PACKAGE" ] && args+=(--package "$PACKAGE")
[ -n "$RULESPEC" ] && args+=(--rulespec "$RULESPEC")
uv run --quiet --project tool/axiom-parity axiom-parity check "${args[@]}"
status=$?
echo "status=$status" >> "$GITHUB_OUTPUT"
exit 0

- name: Suggest axiom lines
if: always() && steps.check.outcome == 'success'
continue-on-error: true
env:
GITHUB_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
PR: ${{ github.event.pull_request.number }}
TITLE: ${{ github.event.pull_request.title }}
PACKAGE: ${{ inputs.package }}
COMMENT: ${{ inputs.comment && github.event.pull_request.head.repo.full_name == github.repository }}
run: |
args=(--repo "$REPO" --pr "$PR" --title "$TITLE" --local-repo pr --base HEAD^1 --head HEAD
--check-json "$RUNNER_TEMP/axiom-parity.json" --summary "$GITHUB_STEP_SUMMARY")
[ -n "$PACKAGE" ] && args+=(--package "$PACKAGE")
[ "$COMMENT" = "true" ] && args+=(--comment)
uv run --quiet --project tool/axiom-parity axiom-parity suggest "${args[@]}"

- name: Verdict
env:
STATUS: ${{ steps.check.outputs.status }}
run: |
if [ "$STATUS" != "0" ]; then
echo "::error title=Axiom parity::the axiom line is missing or does not verify; see the job summary"
exit 1
fi
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
__pycache__/
.venv/
.pytest_cache/
.hypothesis/
*.egg-info/
36 changes: 30 additions & 6 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,20 +63,44 @@ Fragment `<type>` drives the [SemVer](http://semver.org/) bump:
PolicyEngine rules are also encoded as Axiom RuleSpec, in `TheAxiomFoundation/rulespec-<country>` (for example [rulespec-us](https://github.com/TheAxiomFoundation/rulespec-us) and [rulespec-uk](https://github.com/TheAxiomFoundation/rulespec-uk)). Some country-model PRs change policy: a new program, a parameter or threshold update, an eligibility rule, a bug fix. Those PRs must also leave the same provision correct in the matching rulespec repo. Say how in one line of the PR description:

```text
axiom: <legal id> encoded-correct # an existing Axiom module already handles the case; cite the companion test that shows it
axiom: <rulespec PR> encoded # the provision was encoded or repaired alongside this PR
axiom: <rulespec issue> queued # the signed encoder is blocked; a dispatch-ready encoding issue, labelled pe-parity
axiom: n/a: <reason> # infrastructure, data, UI, microsimulation-only or emulator-mapping changes
axiom: <legal id> encoded-correct (<companion test>) # an existing Axiom module already handles the case
axiom: <rulespec PR> encoded # the provision was encoded or repaired alongside this PR
axiom: <rulespec issue> queued # the signed encoder is blocked; a dispatch-ready pe-parity issue
axiom: n/a: <category>: <reason> # the change touches no provision of law
```

A PR that touches several provisions can make several claims, separated by `;` or on separate `axiom:` lines. For example:

```text
axiom: uk:statutes/ukpga/2004/12/190 encoded-correct (`190.test.yaml::earnings_above_basic_amount`); TheAxiomFoundation/rulespec-uk#367 queued
```

The n/a categories are:

- `infra`: CI, build, caching or simulation plumbing;
- `data`: datasets, calibration or uprating of survey data;
- `ui`;
- `microsim-only`: model structure, inputs, aggregates or behavioural responses;
- `emulator-mapping`;
- `docs-tests`;
- `metadata`: labels, references or units;
- `not-law`: contributed reforms, proposals, or announcements not yet legislated.

"Axiom doesn't encode this yet" is not an n/a reason. That is what `queued` is for. An n/a claim must be the PR's only claim; if part of a PR needs Axiom work, claim that part and leave the rest to the description.

An `encoded-correct` claim that names several modules needs a companion test for each one.

Use `queued` only when the signed encoder is blocked; record the blocker in the issue. Each billed encoder run requires separate approval. A `queued` issue must be ready for the encoder to run without further research:

- the module path and corpus citation;
- the module path and corpus citation (a citation path such as `us/statute/26/32/d`, not a corpus release file);
- the operative law, quoted verbatim;
- the required outputs;
- a `review_finding` that can be pasted into the encoder's input as is;
- companion tests whose expected values come from the same external source as the PolicyEngine tests (statute, official form or worksheet, official calculator).

[rulespec-us#1416](https://github.com/TheAxiomFoundation/rulespec-us/issues/1416) is an example. RuleSpec modules come from the signed encoder and are never written by hand. If you are an external contributor and can't do this, write `axiom: needed` and a maintainer will follow up.
Put each element under its own heading: `## Law (verbatim)`, `## Required outputs`, `## review_finding (paste as-is)` and `## Companion tests`. [rulespec-us#1416](https://github.com/TheAxiomFoundation/rulespec-us/issues/1416) is an example. RuleSpec modules come from the signed encoder and are never written by hand. If you are an external contributor and can't do this, write `axiom: needed` and a maintainer will follow up.

The [Axiom parity workflow](.github/workflows/axiom-parity.yml) checks the line on every PR that changes parameters, variables or reforms. It confirms that each cited issue, PR, module and companion test exists and that queued issues are dispatch-ready. It also comments with suggested lines, found by mapping the changed files' `reference` URLs to Axiom modules and open issues, and drafts a dispatch-ready issue when nothing covers a provision. Run it locally with `uvx --from "git+https://github.com/PolicyEngine/.github#subdirectory=axiom-parity" axiom-parity suggest --help`.

## Peer review

Expand Down
Loading
Loading