Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
cc71ea5
Tie source-conflict markers to the register shared with Axiom
MaxGhenis Oct 19, 2026
73da6d2
Capture sources for the first source-conflict entries (HI, OH, AR)
MaxGhenis Oct 19, 2026
5aad28e
Track known conflicts not yet registered in a checked backlog; pin th…
MaxGhenis Oct 19, 2026
9817ed2
Capture sources for IL, KS, CO and LA source-conflict entries
MaxGhenis Oct 19, 2026
e99d047
Seed the source-conflict backlog: 222 known conflicts not yet registered
MaxGhenis Oct 19, 2026
ee2e0b9
Mark Maryland's poverty-credit earned-income reading and capture the …
MaxGhenis Oct 19, 2026
138a0db
Pin the register at rulespec-us 455d34ef; drop the Maryland item it n…
MaxGhenis Oct 19, 2026
3b1d618
Pin the register to what rulespec-us will merge first; keep drafted e…
MaxGhenis Oct 19, 2026
901cd4b
Note what the Illinois and Kansas backlog items still need
MaxGhenis Oct 19, 2026
371dd6c
Address the independent review of 901cd4b6; move the data out of the …
MaxGhenis Oct 20, 2026
1b81648
Address the second review of the source-conflict guard (371dd6ce)
MaxGhenis Oct 20, 2026
c5b524b
Read YAML comments with PyYAML's own scanner; fail closed; close the …
MaxGhenis Oct 20, 2026
46eb1c2
Slice YAML comments by offset; test the real cache hand-off; restore …
MaxGhenis Oct 20, 2026
dbe1f63
Merge remote-tracking branch 'upstream/main' into source-conflict-mar…
MaxGhenis Oct 20, 2026
0f21a63
Pin the register to rulespec-us#1710's merge commit (5796861e)
MaxGhenis Oct 20, 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
1 change: 1 addition & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Fixes #
## Test plan

- [ ] For policy changes: YAML tests whose expected values come from an external source (statute, official form or worksheet, official calculator); otherwise mark not applicable.
- [ ] If this PR takes a reading where a statute and a regulation, form, instructions, manual or agency practice disagree: a source-conflict register entry and a `# Source conflict <jurisdiction>:<id>` marker (CONTRIBUTING.md#source-conflicts); otherwise mark not applicable.
- [ ] `make format`
- [ ] Changelog fragment in `changelog.d/`

Expand Down
4 changes: 4 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ Follow the repository guidance in `CLAUDE.md` for commands, style, changelog ent

Follow [Test design and CI cost](CLAUDE.md#test-design-and-ci-cost) before adding or expanding tests. Use variable-named YAML tests for policy calculations; reserve Python tests for behavior YAML cannot exercise. Preserve Core/data compatibility and isolation coverage, avoid repeated full-model construction, and do not add CI runners or parallel heavy processes without explicit authorization. Document the purpose and measured cost of new simulation-heavy Python tests in the PR.

## Statutes against forms, instructions and agency practice

When a statute conflicts with a regulation, form, instructions, manual or stated agency practice, follow the statute; forms and instructions fill gaps where the statute is silent. Register the choice and mark the code as [CONTRIBUTING.md](CONTRIBUTING.md#source-conflicts) describes. Never leave it only in a comment or a PR body.

## Partner API contract tests

Files under `policyengine_us/tests/policy/baseline/partners/**` are API partner contract tests.
Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ changelog.d/medicaid-ce-exclusions.md
- Follow the exact order of operations specified in regulations
- Verify behavior at edge cases (income just below/above thresholds, exact boundary conditions)
- Consider real-world examples to validate implementation, including official calculators
- **Statute governs (ruled 2026-10-19).** Where a statute conflicts with a regulation, form, instructions, manual or stated agency practice, follow the statute; forms and instructions fill gaps where the statute is silent. Record every such choice in the shared source-conflict register (rulespec-us `known-source-conflicts.yaml`) and mark the deciding line `# Source conflict <jurisdiction>:<id>`; see CONTRIBUTING.md#source-conflicts. `policyengine_us/tests/code_health/test_source_conflicts.py` enforces it.

## Parameter and variable references
- One `reference` entry per source document; URLs that differ only by `#page=` are one source. Do not split a multi-page table into per-page entries.
Expand Down
24 changes: 24 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,30 @@ Every policy change here must also be correct in [rulespec-us](https://github.co
- An `encoded-correct` claim names the module and a companion case in its `.test.yaml` that exercises the same situation as your YAML test.
- Use `queued` only when the signed encoder is blocked; record the blocker in the issue. Each billed encoder run requires separate approval. Label `queued` issues `pe-parity`. Reuse your YAML test's external expected values as the companion tests; don't copy values computed by policyengine-us.

## Source conflicts

When a statute conflicts with a regulation, form, instructions, manual or stated agency practice, PolicyEngine US's rule is to follow the statute. Forms and instructions fill gaps where the statute is silent. Every such conflict is recorded in the register that PolicyEngine shares with Axiom: `known-source-conflicts.yaml` in [rulespec-us](https://github.com/TheAxiomFoundation/rulespec-us). Its fields are defined in that repository's `tools/README.md`, under "Source-conflict register". A PR that takes such a reading, or finds one already in the code, does three things:

1. **Add or update the entry in rulespec-us.**
- Give each source's text and its kind (statute, regulation, form, instructions, manual, guidance, agency practice, ...).
- Give the alternatives, each with an id, and the years.
- Add an implementation for each file here: `repo: PolicyEngine/policyengine-us`, a commit, the path, `follows` (what the code does), and `adopts` with a `basis` (the reading it intends, and why).
- Add the YAML test cases that pin each reading, by commit, path and case name. Pin only commits on `main`: the register records shipped code. An open PR goes under `proposed` until it merges.
- Describe correspondence with an agency in `unpublished_sources`. Never quote it or name the person.
2. **Mark the deciding line** in the variable or parameter file: `# Source conflict <jurisdiction>:<id>`, for example `# Source conflict us-hi:claim-of-right-deduction-floor`.
3. **Refresh the pinned copy** once the entry is on rulespec-us `main`: `python scripts/update_source_conflicts.py --commit <rulespec-us commit>`. Delete the item from `source_conflicts/candidates.yaml` if it was there.

`policyengine_us/tests/code_health/test_source_conflicts.py` checks structure, not whether the code follows a statute. It fails when:

- the vendored register (`source_conflicts/known-source-conflicts.yaml`) differs from its pin (`source_conflicts/pin.yaml`: rulespec-us commit and sha256);
- a comment that starts like a marker is not exactly `# Source conflict <jurisdiction>:<id>` alone on its own line. A comment starts like one when it has one or more `#`, then "source conflict" in any case, with any separator or none between the words (a space, hyphen, underscore, dash, dot or zero-width space), followed either by something shaped like a global id (after an optional colon, or after "conflicts") or by no word character and no colon. A file whose comments cannot be read (it doesn't tokenize or parse) fails too;
- a marker names an entry that isn't in the pinned copy, or whose entry doesn't list the file;
- an implementation the register marks `cites_entry: true` is missing here or has lost its marker;
- the count of comments that read like a conflict between sources ("the instructions say", "in practice", "discrepancy", "Source conflict:", ...) changes in a file without a marker. A new file or a higher count means register and mark the conflict, or reword the comment; a lower count means lower `KNOWN_UNMARKED`. A file with any valid marker is exempt, so the count doesn't prove every conflict in it is marked;
- a backlog item (`source_conflicts/candidates.yaml`) is malformed, already registered, cites a code path that isn't a file git tracks here (absolute, with `..`, or under a hidden directory such as `.github`), or contains a decision-queue id, a session id, a local path or an email address anywhere in the file: in its raw text, comments and keys included, and in every parsed value and key (escapes, `!!binary`, sets). Homoglyphs, such as a Cyrillic letter in "Source", are not caught; review catches those.

A model that departs from the statute with no official source behind it is a bug, not a source conflict. Fix it, or open an issue.

## Repo-specific anti-patterns

- Branching on upstream (`git push upstream <branch>`) is preferred when you have write access, but fork PRs are fine here: PR CI needs no repository secrets (the only one, `CODECOV_TOKEN`, uploads with `fail_ci_if_error: false`, and codecov passes tokenless) and downloads no gated data, so fork PRs run the full suite green.
Expand Down
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,17 @@ bundle installer published by `policyengine`, for example:
```bash
uvx --from policyengine policyengine bundle install --country us --venv .venv
```

## Statutes, forms and agency practice

When a statute conflicts with a regulation, form, instructions, manual or
stated agency practice, PolicyEngine US's rule is to follow the statute.
Forms and instructions fill gaps where the statute is silent. Each such
conflict is recorded in the source-conflict register that PolicyEngine shares
with the Axiom Foundation, including any code that does not follow the rule
yet: `known-source-conflicts.yaml` in
[TheAxiomFoundation/rulespec-us](https://github.com/TheAxiomFoundation/rulespec-us).
For each conflict it gives what each source says, a worked example, the
reading this model takes and why, and the code and tests that implement it.
The code marks the deciding line with `# Source conflict <jurisdiction>:<id>`. See
[CONTRIBUTING.md](CONTRIBUTING.md#source-conflicts).
1 change: 1 addition & 0 deletions changelog.d/source-conflict-markers.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add a code-health guard tying source-conflict markers to the source-conflict register shared with Axiom (statute governs; forms fill gaps).
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,15 @@ def load_known_dead_url_prefixes(data_dir: Path = DATA_DIR) -> tuple:

# (dead URL prefix, date verified dead, where its content lives now)
KNOWN_DEAD_URL_PREFIXES = load_known_dead_url_prefixes()
SCANNED_ROOTS = (REPO, REPO.parent / "docs")
# source_conflicts/ holds the shared register, its backlog and source captures.
SCANNED_ROOTS = (REPO, REPO.parent / "docs", REPO.parent / "source_conflicts")
SCANNED_SUFFIXES = {".ipynb", ".md", ".py", ".yaml", ".yml"}
THIS_FILE = Path(__file__).resolve()
# A byte-pinned copy of rulespec-us's register: a dead link in it can only be
# fixed upstream, then re-vendored, so it is not scanned here.
VENDORED = {
(REPO.parent / "source_conflicts" / "known-source-conflicts.yaml").resolve()
}


def url_host(prefix: str) -> str:
Expand Down Expand Up @@ -407,12 +413,20 @@ def scanned_files():
path.suffix.lower() not in SCANNED_SUFFIXES
or not path.is_file()
or path.resolve() == THIS_FILE
or path.resolve() in VENDORED
or DATA_DIR in path.resolve().parents
):
continue
yield path


def test_the_vendored_register_is_not_scanned():
# It is a byte-pinned copy; a dead link in it is fixed in rulespec-us.
(vendored,) = VENDORED
assert vendored.is_file()
assert vendored not in {path.resolve() for path in scanned_files()}


def test_package_and_docs_have_no_known_dead_reference_urls():
violations = []
for path in scanned_files():
Expand Down
Loading
Loading