From 3fdc4973c2bec50ab2c82d0129d87a87a202cc62 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 08:08:13 +0000 Subject: [PATCH 1/5] Order CONTRIBUTING's source-conflict steps the way the guard enforces them The section told one PR to add the register entry, mark the deciding line and refresh the pin. One PR can't: test_source_conflicts.py rejects a marker whose entry isn't in the pinned copy, the register pins only commits on main, and squash-merging means a ruling PR's own commits never reach main. Describe the three steps in order: a candidate in the ruling PR, the register entry pinned to the squash commit after it merges, then the marker, pin refresh and candidate deletion in a follow-up PR. The ratchet bullet now points to the same steps. Co-Authored-By: Claude Opus 5.5 --- CONTRIBUTING.md | 19 ++++++++++++------- ...tributing-source-conflict-order.changed.md | 1 + 2 files changed, 13 insertions(+), 7 deletions(-) create mode 100644 changelog.d/contributing-source-conflict-order.changed.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5bcec99b80a..826f8450628 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -103,16 +103,21 @@ When sources conflict, PolicyEngine US follows these principles (2026-10-20): - **The agency breaks true ties.** Where the text and the canons don't settle a reading, follow the administering agency's reading, so the model matches what households receive. - **Canons:** definitions control; one meaning per section; no double deduction without a clear statement; specific over general; read timing rules so statutory deadlines can be met; and the operative words govern over form shortcuts. -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: +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". When a PR takes such a reading, or finds one already in the code, the conflict goes into the register in three steps, in this order. One PR can't do all three: the guard below rejects a marker whose entry isn't in the pinned copy of the register, the register pins only commits on `main`, and PolicyEngine US squash-merges, so a ruling PR's own commits never reach `main`. -1. **Add or update the entry in rulespec-us.** +1. **The ruling PR records the conflict in `source_conflicts/candidates.yaml`**: its sources, readings, `pe_us.follows`, `code`, `prs` and `state`. It adds no marker. You can also add the entry to rulespec-us now, listing the open PR under `proposed`. +2. **After the ruling PR merges, add or complete 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. + - Add an implementation for each file here: `repo: PolicyEngine/policyengine-us`, the squash commit on `main`, 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. - 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 :`, 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 `. Delete the item from `source_conflicts/candidates.yaml` if it was there. +3. **Once that entry is on rulespec-us `main`, a follow-up PR here marks the deciding line and refreshes the pin.** + - Mark the deciding line in the variable or parameter file: `# Source conflict :`, for example `# Source conflict us-hi:claim-of-right-deduction-floor`. + - Refresh the pinned copy: `python scripts/update_source_conflicts.py --commit `. + - Delete the item from `source_conflicts/candidates.yaml`. The guard fails a candidate whose id is already in the pinned copy, so the deletion goes in the same PR as the refresh. + +The marker comes only in the follow-up PR, because the guard rejects a marker whose entry isn't in the pinned copy, or whose entry doesn't list the marker's file. `policyengine_us/tests/code_health/test_source_conflicts.py` checks structure, not whether the code follows a statute. It fails when: @@ -120,7 +125,7 @@ Every such conflict is recorded in the register that PolicyEngine shares with Ax - a comment that starts like a marker is not exactly `# Source conflict :` 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; +- 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 reword the comment, and if it describes a real conflict, record it as a candidate (step 1); the marker follows in step 3. 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. diff --git a/changelog.d/contributing-source-conflict-order.changed.md b/changelog.d/contributing-source-conflict-order.changed.md new file mode 100644 index 00000000000..70360f9e58c --- /dev/null +++ b/changelog.d/contributing-source-conflict-order.changed.md @@ -0,0 +1 @@ +Describe the source-conflict steps in CONTRIBUTING.md in the order the guard enforces: candidate in the ruling PR, register entry after it merges, marker and pin refresh in a follow-up PR. From 468b203d6084632fc585567be5dc39b4b8247290 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 2 Nov 2026 16:33:57 -0500 Subject: [PATCH 2/5] Add the register step that sets cites_entry, and fix the merge-method reason Step 2 pins the merging commit with cites_entry false and moves a proposed row; a new step 4 re-pins to the follow-up with cites_entry true. Step 1 names the fields the guard requires, and code already on main can skip it. Co-Authored-By: Claude Opus 5.5 --- CONTRIBUTING.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 826f8450628..70511525968 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -103,19 +103,22 @@ When sources conflict, PolicyEngine US follows these principles (2026-10-20): - **The agency breaks true ties.** Where the text and the canons don't settle a reading, follow the administering agency's reading, so the model matches what households receive. - **Canons:** definitions control; one meaning per section; no double deduction without a clear statement; specific over general; read timing rules so statutory deadlines can be met; and the operative words govern over form shortcuts. -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". When a PR takes such a reading, or finds one already in the code, the conflict goes into the register in three steps, in this order. One PR can't do all three: the guard below rejects a marker whose entry isn't in the pinned copy of the register, the register pins only commits on `main`, and PolicyEngine US squash-merges, so a ruling PR's own commits never reach `main`. +The register that PolicyEngine shares with Axiom records every such conflict: `known-source-conflicts.yaml` in [rulespec-us](https://github.com/TheAxiomFoundation/rulespec-us). That repository's `tools/README.md` defines its fields, under "Source-conflict register". When a PR takes such a reading, or finds one already in the code, the conflict goes into the register in four steps, in this order. One PR can't do them all: the guard below rejects a marker whose entry isn't in the pinned copy of the register, and the register pins only commits reachable from `main`, which a ruling PR's commits aren't until it merges (and a squash merge never makes its own commits reachable). -1. **The ruling PR records the conflict in `source_conflicts/candidates.yaml`**: its sources, readings, `pe_us.follows`, `code`, `prs` and `state`. It adds no marker. You can also add the entry to rulespec-us now, listing the open PR under `proposed`. +1. **The ruling PR records the conflict in `source_conflicts/candidates.yaml`**: its `id`, `program`, `question`, years, at least two `sources`, `readings`, `pe_us` (`follows`, `code`, `prs`) and `state`. It adds no marker. You can also add the entry to rulespec-us now, listing the open PR under `proposed`. 2. **After the ruling PR merges, add or complete 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`, the squash commit on `main`, the path, `follows` (what the code does), and `adopts` with a `basis` (the reading it intends, and why). + - Add an implementation for each file here: `repo: PolicyEngine/policyengine-us`, the commit on `main` that merged the ruling PR (its squash commit, when squash-merged), the path, `cites_entry: false` (the file has no marker yet), `follows` (what the code does), and `adopts` with a `basis` (the reading it intends, and why). If step 1 listed the PR under `proposed`, move that row to `implementations`. - 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. - Describe correspondence with an agency in `unpublished_sources`. Never quote it or name the person. 3. **Once that entry is on rulespec-us `main`, a follow-up PR here marks the deciding line and refreshes the pin.** - Mark the deciding line in the variable or parameter file: `# Source conflict :`, for example `# Source conflict us-hi:claim-of-right-deduction-floor`. - Refresh the pinned copy: `python scripts/update_source_conflicts.py --commit `. - - Delete the item from `source_conflicts/candidates.yaml`. The guard fails a candidate whose id is already in the pinned copy, so the deletion goes in the same PR as the refresh. + - Delete the item from `source_conflicts/candidates.yaml` if it was there. The guard fails a candidate whose id is already in the pinned copy, so the deletion goes in the same PR as the refresh. +4. **After the follow-up merges, finish the entry in rulespec-us**: re-pin each implementation to the follow-up's commit on `main` and set `cites_entry: true`. Then refresh the pin here again, so the guard checks that the marker stays. + +For code already on `main` that the PR doesn't change, step 1 is optional: pin the implementation to a current `main` commit, and the follow-up PR is the first PolicyEngine US PR. The marker comes only in the follow-up PR, because the guard rejects a marker whose entry isn't in the pinned copy, or whose entry doesn't list the marker's file. From 0efd23474a60cbcdc40d02e3efccf6fee127aff7 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 2 Nov 2026 17:16:08 -0500 Subject: [PATCH 3/5] Correct the source-conflict order for code on main and the ratchet Say what the ruling PR adds for step 2 to pin. Add the KNOWN_UNMARKED deletion the guard forces in step 3. Name the path merged PRs use when a conflict-sounding comment describes a real conflict: keep it, record a candidate and add the allowance. Tie the reason to `implementations`, since a `proposed` row doesn't satisfy the guard. Extend the changelog to the fourth step. Co-Authored-By: Claude Opus 5.5 --- CONTRIBUTING.md | 15 ++++++++------- .../contributing-source-conflict-order.changed.md | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 70511525968..0300c8a3f31 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -103,32 +103,33 @@ When sources conflict, PolicyEngine US follows these principles (2026-10-20): - **The agency breaks true ties.** Where the text and the canons don't settle a reading, follow the administering agency's reading, so the model matches what households receive. - **Canons:** definitions control; one meaning per section; no double deduction without a clear statement; specific over general; read timing rules so statutory deadlines can be met; and the operative words govern over form shortcuts. -The register that PolicyEngine shares with Axiom records every such conflict: `known-source-conflicts.yaml` in [rulespec-us](https://github.com/TheAxiomFoundation/rulespec-us). That repository's `tools/README.md` defines its fields, under "Source-conflict register". When a PR takes such a reading, or finds one already in the code, the conflict goes into the register in four steps, in this order. One PR can't do them all: the guard below rejects a marker whose entry isn't in the pinned copy of the register, and the register pins only commits reachable from `main`, which a ruling PR's commits aren't until it merges (and a squash merge never makes its own commits reachable). +Every such conflict belongs in the register that PolicyEngine shares with Axiom: `known-source-conflicts.yaml` in [rulespec-us](https://github.com/TheAxiomFoundation/rulespec-us). That repository's `tools/README.md` defines its fields, under "Source-conflict register". When a PR takes such a reading, or finds one already in the code, the conflict goes into the register in four steps, in this order. One PR can't do them all: the guard below rejects a marker unless the pinned copy of the register lists the marker's file under its entry's `implementations`, and the register pins an implementation only to a commit reachable from `main`, which a ruling PR's commits aren't until it merges (and a squash merge never makes the PR's own commits reachable). -1. **The ruling PR records the conflict in `source_conflicts/candidates.yaml`**: its `id`, `program`, `question`, years, at least two `sources`, `readings`, `pe_us` (`follows`, `code`, `prs`) and `state`. It adds no marker. You can also add the entry to rulespec-us now, listing the open PR under `proposed`. +1. **The ruling PR records the conflict in `source_conflicts/candidates.yaml`**: its `id`, `program`, `question`, `years`, at least two `sources`, `readings`, `pe_us` (`follows`, `code`, `prs`) and `state`. It adds no marker. It also adds what step 2 pins to a commit on `main`: the YAML test cases for its reading and, if the Axiom corpus lacks a source's text, a capture under `source_conflicts/sources/`. You can also add the entry to rulespec-us now, listing the open PR under `proposed`. 2. **After the ruling PR merges, add or complete 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`, the commit on `main` that merged the ruling PR (its squash commit, when squash-merged), the path, `cites_entry: false` (the file has no marker yet), `follows` (what the code does), and `adopts` with a `basis` (the reading it intends, and why). If step 1 listed the PR under `proposed`, move that row to `implementations`. + - Add an implementation for each file here: `repo: PolicyEngine/policyengine-us`, the commit on `main` that merged the ruling PR (its squash commit, for a squash merge), the path, `cites_entry: false` (the file has no marker yet), `follows` (what the code does), and `adopts` with a `basis` (the reading it intends, and why). If step 1 listed the PR under `proposed`, move that row to `implementations`. - 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. - Describe correspondence with an agency in `unpublished_sources`. Never quote it or name the person. 3. **Once that entry is on rulespec-us `main`, a follow-up PR here marks the deciding line and refreshes the pin.** - Mark the deciding line in the variable or parameter file: `# Source conflict :`, for example `# Source conflict us-hi:claim-of-right-deduction-floor`. - Refresh the pinned copy: `python scripts/update_source_conflicts.py --commit `. - Delete the item from `source_conflicts/candidates.yaml` if it was there. The guard fails a candidate whose id is already in the pinned copy, so the deletion goes in the same PR as the refresh. + - Delete the file's line from `KNOWN_UNMARKED` in `policyengine_us/tests/code_health/test_source_conflicts.py` if it has one. A valid marker exempts the file from the ratchet, so the guard fails a leftover line. 4. **After the follow-up merges, finish the entry in rulespec-us**: re-pin each implementation to the follow-up's commit on `main` and set `cites_entry: true`. Then refresh the pin here again, so the guard checks that the marker stays. -For code already on `main` that the PR doesn't change, step 1 is optional: pin the implementation to a current `main` commit, and the follow-up PR is the first PolicyEngine US PR. +For code already on `main` that the PR doesn't change, you can skip step 1: pin the implementation to a current `main` commit. The follow-up PR is then the first PolicyEngine US PR, unless step 2 needs a YAML test case or a source capture that isn't on `main` yet. A PR here adds those first. -The marker comes only in the follow-up PR, because the guard rejects a marker whose entry isn't in the pinned copy, or whose entry doesn't list the marker's file. +The marker comes only in the follow-up PR, because the guard rejects a marker whose entry isn't in the pinned copy, or whose entry doesn't list the marker's file under `implementations`. A `proposed` row doesn't count. `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 :` 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; +- a marker names an entry that isn't in the pinned copy, or whose entry doesn't list the file under `implementations`; - 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 reword the comment, and if it describes a real conflict, record it as a candidate (step 1); the marker follows in step 3. 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; +- 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 reword a comment that describes no conflict. If it describes a real conflict, keep it, record the conflict as a candidate (step 1) and add or raise the file's `KNOWN_UNMARKED` line in the same PR; the marker in step 3 then exempts the file, and its line comes out. 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. diff --git a/changelog.d/contributing-source-conflict-order.changed.md b/changelog.d/contributing-source-conflict-order.changed.md index 70360f9e58c..56cdfea4beb 100644 --- a/changelog.d/contributing-source-conflict-order.changed.md +++ b/changelog.d/contributing-source-conflict-order.changed.md @@ -1 +1 @@ -Describe the source-conflict steps in CONTRIBUTING.md in the order the guard enforces: candidate in the ruling PR, register entry after it merges, marker and pin refresh in a follow-up PR. +Describe the source-conflict steps in CONTRIBUTING.md in the order the guard enforces: candidate in the ruling PR, register entry after it merges, marker and pin refresh in a follow-up PR, then `cites_entry: true` in the register and a second pin refresh. From 83f368c4438896266cff46b2c7bf9c37e648b274 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 2 Nov 2026 17:35:34 -0500 Subject: [PATCH 4/5] Cover entries with two files and conflicts already listed Step 2 asks for an implementation only where a file holds a deciding line, and step 3 marks each file the entry lists: a second file with no marker fails the guard at step 4. Say that verify reads the file's text for cites_entry. Step 1 now covers a conflict the backlog or the pinned copy already lists, and an early entry cites only texts already on main. Co-Authored-By: Claude Opus 5.5 --- CONTRIBUTING.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0300c8a3f31..a9b693a4444 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -105,15 +105,15 @@ When sources conflict, PolicyEngine US follows these principles (2026-10-20): Every such conflict belongs in the register that PolicyEngine shares with Axiom: `known-source-conflicts.yaml` in [rulespec-us](https://github.com/TheAxiomFoundation/rulespec-us). That repository's `tools/README.md` defines its fields, under "Source-conflict register". When a PR takes such a reading, or finds one already in the code, the conflict goes into the register in four steps, in this order. One PR can't do them all: the guard below rejects a marker unless the pinned copy of the register lists the marker's file under its entry's `implementations`, and the register pins an implementation only to a commit reachable from `main`, which a ruling PR's commits aren't until it merges (and a squash merge never makes the PR's own commits reachable). -1. **The ruling PR records the conflict in `source_conflicts/candidates.yaml`**: its `id`, `program`, `question`, `years`, at least two `sources`, `readings`, `pe_us` (`follows`, `code`, `prs`) and `state`. It adds no marker. It also adds what step 2 pins to a commit on `main`: the YAML test cases for its reading and, if the Axiom corpus lacks a source's text, a capture under `source_conflicts/sources/`. You can also add the entry to rulespec-us now, listing the open PR under `proposed`. +1. **The ruling PR records the conflict in `source_conflicts/candidates.yaml`**: its `id`, `program`, `question`, `years`, at least two `sources`, `readings`, `pe_us` (`follows`, `code`, `prs`) and `state`. If the backlog already lists the conflict, it updates that item instead. If the pinned copy already holds the conflict, it adds no item, because the guard fails a registered id. It adds no marker. It also adds what step 2 pins to a commit on `main`: the YAML test cases for its reading and, if the Axiom corpus lacks a source's text, a capture under `source_conflicts/sources/`. You can also add the entry to rulespec-us now, listing the open PR under `proposed`, if every text it cites is in the corpus or captured on `main` already. 2. **After the ruling PR merges, add or complete the entry in rulespec-us.** - - Give each source's text and its kind (statute, regulation, form, instructions, manual, guidance, agency practice, ...). + - Give each source's text and its kind (statute, regulation, form, instructions, manual, guidance, agency practice, ...). Cite a text the corpus lacks under `pending_texts`, pinned to its capture's commit on `main`. - Give the alternatives, each with an id, and the years. - - Add an implementation for each file here: `repo: PolicyEngine/policyengine-us`, the commit on `main` that merged the ruling PR (its squash commit, for a squash merge), the path, `cites_entry: false` (the file has no marker yet), `follows` (what the code does), and `adopts` with a `basis` (the reading it intends, and why). If step 1 listed the PR under `proposed`, move that row to `implementations`. + - Add an implementation for each file here that holds a deciding line (the candidate's `pe_us.code` can list more): `repo: PolicyEngine/policyengine-us`, the commit on `main` that merged the ruling PR (its squash commit, for a squash merge), the path, `cites_entry: false` (the file doesn't name the global id yet; `verify` reads the file's text, so a `# Candidate: ` comment counts), `follows` (what the code does), and `adopts` with a `basis` (the reading it intends, and why). If step 1 listed the PR under `proposed`, move that row to `implementations`. - 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. - Describe correspondence with an agency in `unpublished_sources`. Never quote it or name the person. 3. **Once that entry is on rulespec-us `main`, a follow-up PR here marks the deciding line and refreshes the pin.** - - Mark the deciding line in the variable or parameter file: `# Source conflict :`, for example `# Source conflict us-hi:claim-of-right-deduction-floor`. + - Mark the deciding line in each variable or parameter file the entry lists under `implementations`: `# Source conflict :`, for example `# Source conflict us-hi:claim-of-right-deduction-floor`. - Refresh the pinned copy: `python scripts/update_source_conflicts.py --commit `. - Delete the item from `source_conflicts/candidates.yaml` if it was there. The guard fails a candidate whose id is already in the pinned copy, so the deletion goes in the same PR as the refresh. - Delete the file's line from `KNOWN_UNMARKED` in `policyengine_us/tests/code_health/test_source_conflicts.py` if it has one. A valid marker exempts the file from the ratchet, so the guard fails a leftover line. From 68f7bfb843779a0f35ee676087b6e62fcb8dbf57 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 2 Nov 2026 17:48:26 -0500 Subject: [PATCH 5/5] Name the whole id in the candidate comment `verify` matches only the global id, so the comment needs the jurisdiction as well as the local id. Co-Authored-By: Claude Opus 5.5 --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a9b693a4444..44774aa7167 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -109,7 +109,7 @@ Every such conflict belongs in the register that PolicyEngine shares with Axiom: 2. **After the ruling PR merges, add or complete the entry in rulespec-us.** - Give each source's text and its kind (statute, regulation, form, instructions, manual, guidance, agency practice, ...). Cite a text the corpus lacks under `pending_texts`, pinned to its capture's commit on `main`. - Give the alternatives, each with an id, and the years. - - Add an implementation for each file here that holds a deciding line (the candidate's `pe_us.code` can list more): `repo: PolicyEngine/policyengine-us`, the commit on `main` that merged the ruling PR (its squash commit, for a squash merge), the path, `cites_entry: false` (the file doesn't name the global id yet; `verify` reads the file's text, so a `# Candidate: ` comment counts), `follows` (what the code does), and `adopts` with a `basis` (the reading it intends, and why). If step 1 listed the PR under `proposed`, move that row to `implementations`. + - Add an implementation for each file here that holds a deciding line (the candidate's `pe_us.code` can list more): `repo: PolicyEngine/policyengine-us`, the commit on `main` that merged the ruling PR (its squash commit, for a squash merge), the path, `cites_entry: false` (the file doesn't name the global id yet; `verify` reads the file's text, so a `# Candidate: :` comment counts), `follows` (what the code does), and `adopts` with a `basis` (the reading it intends, and why). If step 1 listed the PR under `proposed`, move that row to `implementations`. - 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. - Describe correspondence with an agency in `unpublished_sources`. Never quote it or name the person. 3. **Once that entry is on rulespec-us `main`, a follow-up PR here marks the deciding line and refreshes the pin.**