Skip to content

Restore inputs as inputs, evict the fast cache on holder writes, reject non-numeric uprating and defined_for - #576

Merged
MaxGhenis merged 11 commits into
masterfrom
fix-restore-registry-holder-cache-enum
Oct 8, 2026
Merged

MaxGhenis merged 11 commits into
masterfrom
fix-restore-registry-holder-cache-enum

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Restored inputs must survive apply_reform; direct holder writes must invalidate the calculation fast cache; non-numeric uprating and defined_for configurations must receive named validation errors before arithmetic or entity mapping.

Draft: this changes core behaviour, so the merge decision remains Max's. No merge or draft-status change is part of this repair.

Behaviour

  • Dumps use master's derived_periods.txt and write __entities__/dump_format.txt on every successful dump. There is no parallel inputs.txt record.
  • Restore preserves derived marks and registers every non-derived value through the same input context as set_input, without running input-splitting helpers again. Explicit inputs, including already split months, ETERNITY inputs and Enum inputs, remain replayable after a reform.
  • Storage inputs written with put_in_cache(derived=False) were not necessarily in the original simulation's explicit input registry. Restore registers those values too. This intentionally follows storage provenance, so universal reform equivalence is not claimed for simulations whose explicit input registry disagrees with storage marks.
  • Ambiguous legacy dumps lacking both the format marker and calculated-period provenance warn that any calculated values restored as inputs will survive a reform. Marker-bearing all-input dumps and older dumps listing calculated periods restore without that ambiguity warning.
  • Holder writes and deletes evict affected fast-cache entries in their own simulation, respecting visible branch names, ETERNITY values and contained-period deletion. Master's helper-subperiod eviction, shared-key bookkeeping, disk directories, derived-write protection and clone/baseline fixes are retained.
  • Enum, string and date uprating or defined_for conditions remain rejected at registration and, for late changes, before uprating or condition mapping. Numeric conditions remain accepted. Rejected variable replacements preserve the previous variable.

Invariants

  1. Dump/restore preserves default-branch stored bytes, Enum metadata and derived marks; restored replay keys identify exactly the non-derived stored values.
  2. Explicit set_input values survive dump/restore and reform; calculated values remain derived and are dropped on reform. Reform round-trip equality is conditional on the original replay registry agreeing with storage provenance.
  3. Cached and uncached simulation trees return the same results for holder writes/deletes and input operations, in memory and on disk. A holder never evicts another simulation's fast cache or entries for unrelated variables.
  4. Master input-only uprating and derived-overwrite protection remain intact. Calculated helper subperiods do not count as explicit inputs.
  5. Non-numeric condition validation is independent of registration order and runs before entity mapping.

This repair

Merged canonical master 5d68130be487f0b87d47fc75ef300f36ad46f7ca with a history-preserving merge. Resolved holder, simulation and dumper conflicts without dropping master changes. Consolidated this PR's four fragments into one changed fragment.

Folded #566's unique coverage into this PR: its seven examples, two Hypothesis properties and fixture are copied byte-for-byte from c825cab722bf202f1815e63e63e245fe7b1b3ff2. No #566 production change or changelog was imported. #566 remains open and its branch is unchanged.

Added legacy-format, all-input-format, unrecorded storage-input, stale input-record, Enum replay, disk clone/branch and shared-key regressions. Expanded restore and disk-cache differential coverage, removing the obsolete calculated-month exception now addressed by master.

Remaining stack work

Verification

Changed Python files pass Ruff formatting and lint checks; git diff --check passes. The three #566 file blobs exactly match their originals.

The required foreground wrapper was attempted for the focused restore, holder-cache and non-numeric validation tests. It exited 75 before pytest started because the sandbox denied ps used by its priority check. No tests or country loads were run outside the wrapper; no full suite or new microsimulation was run. Current-head focused tests, country-load checks and CI are pending. Prior-head test counts are historical and do not validate this resolution. A fresh independent source review is recorded in the build report; execution remains a separate gate.

Downstream impact

Re-run pending at b8d98660a03d3fc3aa45d90b477737c759ec4938; the following measurements are historical, from 83597c22 against core master b78b0ba9, with identical country code/data on both arms:

  • policyengine-uk (main c7e826ea, Enhanced FRS 2024-25, year 2026): 36 of 36 arrays identical, including marginal_tax_rate; income tax £313.139bn on both.
  • policyengine-us (main fbe24ad1, Enhanced CPS, 3,000-household subsample, 2026, including marginal_tax_rate): 25 of 25 arrays identical; income tax $1,962.456bn on both.
  • Historical harness: ~/reviews/core-restore-cache-enum-2026-10-02/ab/.

Partner-impact gate: this core PR's workflow runs core and country smoke jobs; it does not run US partner baseline snapshots. The hub's downstream country-load/A/B checks and any partner-impact assessment remain for Max before merge. No partner baseline file was changed.

axiom: n/a: core engine, no policy change

🤖 Generated with Claude Code

MaxGhenis and others added 4 commits October 2, 2026 16:36
…ct non-numeric uprating and defined_for

Three bugs found by the review of #563, all present on master b78b0ba:

- restore_simulation put every value back with put_in_cache, which records
  no input, so apply_reform on a restored simulation dropped its inputs.
  dump_simulation now lists each variable's input periods in inputs.txt and
  restore_simulation records exactly those in _user_input_keys.
- Holder.set_input, put_in_cache and delete_arrays left the simulation's
  fast cache untouched, so calculate kept returning the replaced value.
  Every holder write and delete now drops the entries it changes, in the
  holder's own simulation and only for branches that simulation reads.
- An Enum, str or date variable with uprating, and a variable defined_for
  one, raised TypeError in the middle of a calculation. Both are now
  rejected when the variables are registered.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A dump written before inputs were recorded holds the arrays and nothing
else, so the test stays right when another change adds its own sidecar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Next to delete_arrays, git merged it with other changes to that method
without a conflict but left their lines inside the new helper.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hat was calculated from it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis and others added 2 commits October 2, 2026 17:40
…ime checks, legacy-dump warning

- Test that an input set on a branch (which shares the input record) does
  not mark the default branch's calculated value as an input, with a branch
  step in the restore property.
- replace_variable keeps the existing variable when the replacement is
  rejected.
- calculate repeats the uprating check before it uprates, for an uprating
  assigned on a class that declares uprating itself; the defined_for message
  is tested for str and date as well as Enum.
- The message for an inherited uprating points to replace_variable.
- restore_simulation warns when a dump does not record its inputs.
- Changelog: say that such systems no longer load, including a group
  variable defined_for a person Enum, which master masked on summed indices.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…is shared

policyengine-core#561 makes a branch copy the input record instead of
sharing it; the test now holds either way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis added a commit that referenced this pull request Oct 3, 2026
A dump does not record which values were inputs, and restore_simulation
stored every value with put_in_cache, so a restored simulation had an empty
input record and the helpers replaced restored inputs (review finding 1).
Restore now stores each value as a recorded input, the rule #576 applies to
dumps without an inputs.txt.

Tests: a restored month keeps its value under a yearly input (divide and
dispatch); an input on a nested branch drops the sum its parent branch
calculated; an input stored for twelve months from March is kept. The
order-independence property now creates up to two nested branches, with
calculations at each level.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… three cache paths

- calculate checks the defined_for variable's type before calculating and
  mapping it. Mapped to a group entity, an Enum's indices were summed into
  numbers and str or date values failed inside the mapping, so the
  values-based check missed a cross-entity defined_for set after
  registration.
- Regression tests for three cache paths no test pinned (each one a
  surviving mutant in GPT-6.1 Sol's review): a write under an ancestor
  branch name in a nested branch, a write to disk storage, and an ETERNITY
  value requested without a period.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis added a commit that referenced this pull request Oct 3, 2026
…ly for read branches

Review round 3:

- Holder._set no longer drops a record entry when a calculated value is
  stored over an input. A calculate_add at a variable's own period stores
  the unchanged input and lost its record; a clone, which shares its
  source's record until #561, deleted the source's entries; and ETERNITY
  variables keep entries under several periods. The case the cleanup was
  for, calculate_add caching a sum over an input, is what #571 stops.
- Master now gives a storage that shares nothing a frozenset for _shared
  (#578). The drop step deleted from it directly; it now leaves that to
  InMemoryStorage._stop_sharing_dropped_keys.
- The helpers evicted calculate's fast cache for a sub-period stored under
  any branch name, including one the simulation does not read. They now
  evict only for branches it reads, as #576's holder-write property
  requires.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis and others added 3 commits October 7, 2026 17:01
…e eviction

Keep master input-only uprating, shared storage and clone/baseline changes. Retain holder write/delete eviction and runtime non-numeric validation. Both dump records coexist at this merge step; the following repair removes the parallel input record.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…576)

Write a format marker so modern all-input dumps can be distinguished from ambiguous legacy dumps. Register non-derived restored values through the input context without dispatching helpers again; preserve calculated marks. Cover cached storage inputs, stale input records, Enum replay and differential round trips, and remove the obsolete calculated-month helper exception now fixed on master.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ths (#576)

Copy the three #566 fixture/test files unchanged from c825cab without its production change or fragment. Add disk clone/branch and shared-key cache regressions plus an on-disk cache/reference differential property, preserving master provenance and eviction. Keep #566 open and its branch unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Correct the inherited attribute documentation to use the latest visible earlier input rather than a calculated or defaulted cache entry. No runtime change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis
MaxGhenis marked this pull request as ready for review October 8, 2026 21:45
@MaxGhenis
MaxGhenis merged commit 7ad23dc into master Oct 8, 2026
19 checks passed
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

US + core hub merge audit, core#576 at b8d98660a03d3fc3aa45d90b477737c759ec4938 (squash)

MaxGhenis added a commit that referenced this pull request Oct 9, 2026
…ger period (#581)

* Count only inputs as already set when a set_input helper splits a longer period

set_input_divide_by_period and set_input_dispatch_by_period treated any value
stored for a sub-period as already set, including values the simulation had
calculated (a cached default or formula result). The same annual input then
gave different months depending on what was calculated first: a divided
input skipped calculated months and shared the rest among the others, or
failed as inconsistent after the year had been read; a dispatched input
reused a calculated month for every later month.

The helpers now read the simulation's record of inputs (_user_input_keys):
a recorded sub-period keeps its input (and, for dispatch, passes it on to
the later sub-periods, as before), and a calculated one is replaced. After
storing, they drop the variable's calculated values over overlapping periods
(the sums calculate_add caches, the twelfths calculate_divide caches) under
the input's branch and the branches it reads through, record what they store
as inputs, and evict calculate's fast cache for those periods.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Evict calculate's fast cache for the periods a set_input helper drops

calculate does not put sums or twelfths in its fast cache today, so this is
defensive: a value held there for a period whose stored value the helper
drops goes with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Restore dumped values as inputs; test nested branches and rolling years

A dump does not record which values were inputs, and restore_simulation
stored every value with put_in_cache, so a restored simulation had an empty
input record and the helpers replaced restored inputs (review finding 1).
Restore now stores each value as a recorded input, the rule #576 applies to
dumps without an inputs.txt.

Tests: a restored month keeps its value under a yearly input (divide and
dispatch); an input on a nested branch drops the sum its parent branch
calculated; an input stored for twelve months from March is kept. The
order-independence property now creates up to two nested branches, with
calculations at each level.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Keep the property test off disk file names Windows rejects

On disk, a value calculated for a period like year:2013:2 is stored in a
file named after the period, and Windows rejects ":" in file names
(policyengine-core#526). The Windows CI jobs failed on such a read in the
on-disk examples. In disk mode the property now skips those periods.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Record which dumped values were inputs; forget an input a calculation replaces

Review round 2:

1. Restoring every dumped value as an input froze calculated values through
   a later reform. The dumper now writes, next to each variable's arrays,
   the periods whose value was an input (inputs.txt), and restore records
   exactly those; a dump without the file restores every value as an input.
   This is policyengine-core#576's dumper change, taken byte for byte so the
   two PRs merge without conflict.

2. A value stored by put_in_cache over an input (say a calculate_add sum over
   an input set for twelve months) left the input's record entry in place,
   so the helpers and apply_reform kept treating the calculated value as an
   input. Holder._set now drops the entry, in both of its forms for twelve
   months from the first of a month, when it stores a value outside
   set_input.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Test a calculated value replacing an input with put_in_cache, not calculate_add

With policyengine-core#571, calculate_add no longer caches a sum over an input
a plain read finds, so the input is kept and the test's premise (master's
calculate_add overwriting it) did not hold. Storing the calculated value with
put_in_cache replaces the input with or without #571.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Drop the record cleanup on calculated writes; evict the fast cache only for read branches

Review round 3:

- Holder._set no longer drops a record entry when a calculated value is
  stored over an input. A calculate_add at a variable's own period stores
  the unchanged input and lost its record; a clone, which shares its
  source's record until #561, deleted the source's entries; and ETERNITY
  variables keep entries under several periods. The case the cleanup was
  for, calculate_add caching a sum over an input, is what #571 stops.
- Master now gives a storage that shares nothing a frozenset for _shared
  (#578). The drop step deleted from it directly; it now leaves that to
  InMemoryStorage._stop_sharing_dropped_keys.
- The helpers evicted calculate's fast cache for a sub-period stored under
  any branch name, including one the simulation does not read. They now
  evict only for branches it reads, as #576's holder-write property
  requires.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant