Repository navigation
Conversation
MaxGhenis
force-pushed
the
w4/receipt-composition
branch
from
October 12, 2026 17:05
b840fdf to
e0052bc
Compare
MaxGhenis
force-pushed
the
w4/receipt-nonfinite-reforms
branch
from
October 12, 2026 17:05
c8e8537 to
1ec7c76
Compare
PolicyEngine parameters take infinite values (an uncapped threshold is
.inf), and the Budget dashboard's salary_sacrifice_cap measure carries
one, but JSON cannot hold them, so its reform could not be receipted.
Reform payload digests now encode inf as {"$nonfinite": "Infinity"} and
-inf as {"$nonfinite": "-Infinity"}; a payload using the key $nonfinite
itself is refused, so no two payloads share a digest. NaN stays refused.
Scenario parameter changes are converted once, at the digest. A payload
without infinities digests exactly as before (differential property test
against the previous definition).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis
force-pushed
the
w4/receipt-composition
branch
2 times, most recently
from
October 13, 2026 13:50
e0052bc to
b5d43d0
Compare
MaxGhenis
force-pushed
the
w4/receipt-nonfinite-reforms
branch
from
October 13, 2026 13:50
1ec7c76 to
cab895b
Compare
Round-1 review of #588: - M1: rebuilding mappings before strict conversion dropped one of two equal keys, so payloads refused before (colliding keys) were accepted. Infinities are now encoded inside _strict_json_value, after the collision check, and the reserved key is checked on the converted text. - M2: the -Infinity spelling is pinned by exact canonical bytes. - L1: a tuple case for the reserved key and for inf. - L3: keys convert to an exact str, so a str subclass's __eq__ cannot hide the reserved key or a collision. - L4: docs say what the encoding guarantees and what a JavaScript reform verifier must do. - L5: an infinite modifier default binds by float.hex(); NaN is refused. - L6: the differential test now generates nested payloads with int and colliding keys, tuples, nulls, strings, dates, numpy scalars and large integers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The composition docstring and docs gave "a single simulation modifier" as an example of how to run a composition. On policyengine-uk 2.126.12 that is not the same run: the engine applies a Scenario's own parameter changes before the construction steps that read parameters (the Universal Credit rebalancing inputs and the parameter-switched structural reforms) and runs its modifier after them, so a change made inside a modifier reaches neither. The text now says the changes apply where the engine applies a Scenario's own, and how to run a composition on 2.126.12. Reported by the dashboard workstream's review of its combiner; no digest changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e text Round-2 review of #588: - The 2.126.12 composition recipe merged reform dictionaries into parameter_changes, but that version reads a scalar in a reform dictionary as applying from 2023 and in parameter_changes from 2000. The recipe now assembles one Scenario from each member's own Scenario (Scenario.from_reform for a dictionary): the scenario members' merged parameter_changes, every member's dated_reforms and every member's modifier, in member order. The modifier-ordering caveat is qualified to applied_before_data_load=False. - Dates render with datetime.date.isoformat, so a subclass cannot choose its text or forge the reserved infinity object. - Docs and docstring state the one change to finite digests: keys or dates that are subclasses overriding how str or date render now digest by their characters and their date. - Tests pin the typed infinity spellings, a str subclass overriding __str__, key normalization in content digests, and the date rendering; each of the review's three surviving mutants, and a date mutant, is now killed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Round-3 review of #588: a str subclass whose iterator yields characters with hostile equality escaped its quotes in the canonical writer and could spell the reserved infinity object, so a payload with no infinity digested like one with an infinity. String values now convert to an exact str, as keys already do. The docs and docstring extend the one documented change to finite digests to string values. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #587
Stacked on #579, which is stacked on #571. Review only
git diff b840fdf6..HEAD.Why
The UK Budget dashboard's
salary_sacrifice_capmeasure setsgov.hmrc.pensions.salary_sacrifice_pension_captoinf, which is how PolicyEngine writes an uncapped threshold. Reform digests are RFC 8785 over strict JSON, which has no infinities, so the measure could not be receipted (W5 reported it).What changes
infas{"$nonfinite": "Infinity"}and-infas{"$nonfinite": "-Infinity"}. NumPy infinities encode the same way.$nonfiniteitself is refused, so no two payloads share a digest.Invariants (tested)
canonical_sha256(strict_json_value(...))).inf,-infand every finite cap get distinct digests.414 receipt, wheel and canonical tests pass, and the schema check passes.
axiom: n/a: receipt digest encoding, no policy encoding
🤖 Generated with Claude Code