Repository navigation
Segment and cache Stage 12 US simulations - #712
Merged
Merged
Conversation
anth-volk
force-pushed
the
feat/stage12-segmentation-cache
branch
2 times, most recently
from
October 1, 2026 12:30
d5f6837 to
16c7701
Compare
anth-volk
force-pushed
the
feat/stage12-segmentation-cache
branch
from
October 12, 2026 12:32
16c7701 to
069ec46
Compare
This was referenced Oct 13, 2026
anth-volk
force-pushed
the
feat/stage12-segmentation-cache
branch
4 times, most recently
from
October 14, 2026 20:24
f22d344 to
f510ed9
Compare
Cancellation state-update and cancel-request failures previously logged only the simulation role and error type. They now also log the report's evaluation_id and the child's simulation_execution_id, in the message and as record attributes, and still omit the error's own message. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Only a failed spawn used to cancel already-started children. Any other coordinator failure after the first spawn, such as a failed dispatch placeholder write, a failed invocation attach, a failed restore, or a retained-artifact error, reached the report failure handler, which marked the report failed but left every started worker running until its own timeout. The handler now stops every started, unfinished child with the existing cancellation path: it records the child as incomplete with cancelled_after_coordinator_failure, cancels an unsegmented call, and lets a segmented call stop itself once it reads that record. Children that already finished are left unchanged. Child, dispatch and wait-timeout failures keep their own peer cancellation, and cancellation or state-update errors are logged without replacing the original failure. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The child-wait deadline used to start when observation began, after claiming, output planning (including the country model load), retained-artifact reads and dispatch. Setup time therefore came out of the coordinator's remaining Modal time rather than the children's window. The 3,000 s window now opens when coordinate_report starts, so setup shortens the children's window and the coordinator always keeps 2,100 s of its 5,100 s Modal timeout after the window: up to 900 s for the production-result wait and 1,200 s to read results, aggregate, write artifacts and record the outcome. The timeout summary now says the window is measured from the coordinator's start. A new test runs the coordinator with 0, 400 and 2,000 s of simulated setup, checks that the window closes 3,000 s after entry, and checks the declared Modal timeouts still leave the production wait plus at least 600 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eouts The coordinator stopped waiting 3,000 s after it started, although a US simulation worker may run for 3,900 s and each of its region-group calls for 3,600 s, so US simulations could never use the time Modal allowed them. The coordinator's 5,100 s timeout was not derived from either. Following the rule from #793, a new dependency-free stage12_timeouts module defines the worker timeouts, sets the shared child-wait window to the longest of them (3,900 s), and derives the coordinator's Modal timeout as that window plus the 900 s production-result wait plus 600 s to read results, aggregate, write artifacts and record the outcome: 5,400 s. A complete testing-environment US report took 288 s, so 600 s of finishing time is ample. The Modal app, coordinator and comparison step all read these constants. The last poll no longer sleeps past the deadline, so the window closes on time instead of up to 1 s late and the finishing time is not eroded. Tests read the declared Modal timeouts and check that the window covers every simulation worker's timeout and that the coordinator's timeout leaves the production wait plus at least 600 s. They also check that setup time shortens the window and that an exhausted window gives the children no extra time. Only the Modal timeout values change: no image, secret or environment variable attachment changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adopt #793's cancellation practice in the concurrent coordinator: - Every simulation the coordinator stops, after a peer failure, a wait timeout or a coordinator failure, is recorded as failed with simulation_cancelled, and its error_summary gives the reason, for example "Cancelled after the baseline simulation failed.". The cancelled_after_peer_failure and cancelled_after_coordinator_failure codes and the incomplete status for cancellations are gone; the wait timeout stays the report's error code. - Only a running record changes, so a simulation that already succeeded or failed keeps its outcome. Workers already treat any status other than running as a cancellation, so a segmented worker still sees the failed record and cancels its own region-group calls. - An unsegmented simulation is always cancelled through Modal, even when its record could not be written. A segmented simulation is cancelled through Modal only when its record could not be written, because Modal does not cancel spawned calls recursively. Cancellation and state-update errors are still logged without replacing the original failure. Carry over #793's tests for a cancelled and recorded reform, a finished baseline left alone, a restored call cancelled on retry, finished siblings left unchanged, and both unfinished children cancelled on timeout. Add tests for the Modal fallback after a failed state write, for segmented and unsegmented simulations. ImmediateCall now counts cancellations, and the entry and persistence fixtures use the new cancellation record. Add a changelog line, as #793 did. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rebase onto #789 reports a closed child-wait window to telemetry before cancellation and report failure records are written, as every other Stage 12 failure is. This test makes those writes fail and checks that the timeout still reached telemetry first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk
added a commit
that referenced
this pull request
Oct 19, 2026
#712 landed on main after this stack branched. Before its C901 merge_segment_results is split, pin the checks its tests did not reach: a result set that omits a segment, a result naming another region group, segments with different entities or column dtypes, and an entity without its identifier. The serializer refuses frames without an identifier, so that pin decodes every segment to one directly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk
added a commit
that referenced
this pull request
Oct 19, 2026
… merge steps C901 and PLR0912 flag merge_segment_results. It now indexes the results (rejecting an incomplete set or a duplicate index), decodes each planned segment in partition order (rejecting an omitted segment or one naming another role or region group), and merges each entity's frames (rejecting different entities, dtypes, a missing identifier or duplicate IDs), each in its own function. The checks and their order are unchanged, and the previous commit's pins keep their assertions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk
added a commit
that referenced
this pull request
Oct 19, 2026
#712 landed on main after this stack branched and extended run_single_simulation, which C901 and PLR0915 now flag. Before it is split, pin the paths its tests did not reach: a simulation that is no longer active is refused, a claim another writer won stops the run as cancelled, and a failed status read in the cancellation check is logged by error type only and treated as not cancelled, so the run completes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk
added a commit
that referenced
this pull request
Oct 19, 2026
…ord steps C901 and PLR0915 flag run_single_simulation, which #712 extended with segmented calculation and cancellation. It now reads as its phases: claim the simulation (refusing one that is no longer active or that another writer claimed), build the cancellation check, write the input, calculate (segmented, standard or injected, always as a SimulationCalculation), write the artifact, and record success or failure, each phase but the writes in its own function. The order of reads, writes and reports is unchanged, and the previous commit's pins keep their assertions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk
added a commit
that referenced
this pull request
Oct 19, 2026
#712 landed on main after this stack branched. Before its C901, PLR0912 and PLR0915 fetch_stage12_cache is split, pin what its two tests did not reach: each manifest rejection (no bucket or manifest, schema, bundle, year set, artifact list, artifact count and deployment digest), each artifact rejection (a non-object entry, a filename with a directory, a duplicate filename, a non-integer size and a same-size file with another digest, which leaves no partial file), and the storage client built with no credentials, a credentials object or credentials encoded as a string. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk
added a commit
that referenced
this pull request
Oct 19, 2026
…d file steps C901, PLR0912 and PLR0915 flag fetch_stage12_cache. It now validates the whole manifest first (schema, installed bundle and years; the artifact list and its counts; then the deployment digest), builds the storage client from the deployment's credentials only when none is given, and fetches each artifact through a partial file. The checks, their order and the environment variables it reads are unchanged; the module stays self-contained for the image build. The previous commit's pins keep their assertions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 19, 2026
anth-volk
added a commit
that referenced
this pull request
Oct 20, 2026
…nd aggregate phases Group the coordinator's Modal identity and its injectable seams, and split submitted-parent validation into invocation, claim-state and production checks. The Modal coordinate_report function keeps its signature. Rebased onto #712, the phases carry #712's coordinator: one shared child-wait window opened before any setup, nonblocking probes of both children, cancellation of the unfinished children on any failure, and failure details with correlation IDs. CoordinatorDependencies gains #712's sleep, monotonic and wait_timeout_seconds seams. The report-level failure handling moves into _ReportCoordination.fail, and the run's child plans, calls and descriptors live on _ReportCoordination, so a failure in any phase can stop the started children. The coordination pins expect #712's single child-wait span and correlation-ID suffixes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk
added a commit
that referenced
this pull request
Oct 20, 2026
#712 landed on main after this stack branched. PLR2004, which this PR enables, flags five magic values in its code; each is now a named constant, with no behaviour change: - the date-range period's two parts (_DATE_RANGE_PART_COUNT, the name simulation_runtime already uses); - the segment poll's consecutive-error limit (CONSECUTIVE_POLL_ERROR_LIMIT, as in segmented_national); - the national partition's 20 region groups (NATIONAL_PARTITION_GROUP_COUNT, next to US_REGION_GROUPS); - worker validation's cache years, artifact count and probe year, from the cache precompute's PRECOMPUTE_YEARS: one dataset per year and one baseline per US region group per year make the 63 artifacts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk
added a commit
that referenced
this pull request
Oct 20, 2026
… method #712 landed on main after this stack branched. PLR0913, which this PR enables, flags its eleven-argument _observe_simulations. It becomes _ReportCoordination._observe_children, which takes the calls and descriptors and reads the child plans, store, runtime, progress, stage plan, clock, sleep and wait window from the coordination it belongs to. The polling, failure and timeout logic is unchanged, and #712's runtime tests cover every path through it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk
added a commit
that referenced
this pull request
Oct 20, 2026
…one site #712 landed on main after this stack branched. PLR0913, which this PR enables, flags its seven-argument report_failure. The telemetry scope, default error code, attributes and segment index that say where a failure was caught now travel as one FailureSite; the error, runtime and progress stay separate. The coordinator's three calls, the worker's and the segment runner's pass the same values as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk
added a commit
that referenced
this pull request
Oct 20, 2026
…e group #712 landed on main after this stack branched. PLR0913, which this PR enables, flags Stage12SegmentRunner's nine-argument constructor. The Modal module, region groups, sleep and the two poll intervals that tests replace now travel as one SegmentRunnerSeams, whose defaults are the deployed ones; the simulation, app name, runtime and cancellation check stay separate. run_segmented_simulation passes nothing new. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk
added a commit
that referenced
this pull request
Oct 20, 2026
…s one group #712 landed on main after this stack branched. PLR0913, which this PR enables, flags its eight-argument build_stage12_simulation. The country module, region, dataset selection and loaded dataset the worker resolves in its own stages now travel as one ResolvedSimulationInputs. The builder reads the scoping strategy from the region, as _build_simulation already does, so it no longer takes the region code it only deleted. The SPM parameters, normalised policy and execution input stay separate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk
added a commit
that referenced
this pull request
Oct 20, 2026
…e group #712 landed on main after this stack branched. PLR0913, which this PR enables, flags the cache precompute's seven-argument run_precompute. Its five Modal function handles now travel as one CachePrecomputeFunctions, as the baseline precompute's PrecomputeFunctions already do; the bucket and echo stay separate. The Modal app's entrypoint passes the same five functions, and its secrets and environment are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk
added a commit
that referenced
this pull request
Oct 20, 2026
…ript #712 landed on main after this stack branched. PLR0913, which this PR enables, flags the six-argument ScriptedChildCall its runtime tests use. The result, wait error, cancel error and during-wait hook that script a call's outcome now travel as one ChildCallScript; the call ID and clock stay separate. Both constructions script the same outcomes as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk
added a commit
that referenced
this pull request
Oct 20, 2026
#712 landed on main after this stack branched. Before its eight-return qualifying_cache_identity is split, pin the ineligible cases its tests did not reach: no or a non-region-group scoping strategy, a missing, empty or non-list region group, a non-state or non-text region code, and a scope without a cache key. Each gets no cache identity. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk
added a commit
that referenced
this pull request
Oct 20, 2026
… and scope checks PLR0911 flags the eight returns of qualifying_cache_identity. It now asks three questions: is this a current-law baseline on the default dataset, what is its group of state regions, and what is the region-group scope's cache key. Each check is pure, so asking them in this order gives the same result; the previous commit's pins and #712's identity tests keep their assertions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk
added a commit
that referenced
this pull request
Oct 20, 2026
… added #712 landed on main after this stack branched. Before its C901 verify_determinism_impl is split, pin what its tests did not reach: every kind of frame difference (an entity missing on one side, a column on only one side, a dtype change and a value change) in its sorted order, the rejection of a probe that is not a planned baseline, and that the stored baseline is downloaded only when it is not already in the data folder. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk
added a commit
that referenced
this pull request
Oct 20, 2026
C901 flags verify_determinism_impl. The comparison of cached and fresh frames moves into _frame_differences, which lists each difference in the same sorted order; the probe check, download, cache-hit check and pass record stay in verify_determinism_impl. The previous commit's pins keep their assertions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk
added a commit
that referenced
this pull request
Oct 20, 2026
#712 landed on main after this stack branched. Before its C901 merge_segment_results is split, pin the checks its tests did not reach: a result set that omits a segment, a result naming another region group, segments with different entities or column dtypes, and an entity without its identifier. The serializer refuses frames without an identifier, so that pin decodes every segment to one directly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk
added a commit
that referenced
this pull request
Oct 20, 2026
… merge steps C901 and PLR0912 flag merge_segment_results. It now indexes the results (rejecting an incomplete set or a duplicate index), decodes each planned segment in partition order (rejecting an omitted segment or one naming another role or region group), and merges each entity's frames (rejecting different entities, dtypes, a missing identifier or duplicate IDs), each in its own function. The checks and their order are unchanged, and the previous commit's pins keep their assertions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk
added a commit
that referenced
this pull request
Oct 20, 2026
#712 landed on main after this stack branched and extended run_single_simulation, which C901 and PLR0915 now flag. Before it is split, pin the paths its tests did not reach: a simulation that is no longer active is refused, a claim another writer won stops the run as cancelled, and a failed status read in the cancellation check is logged by error type only and treated as not cancelled, so the run completes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk
added a commit
that referenced
this pull request
Oct 20, 2026
…ord steps C901 and PLR0915 flag run_single_simulation, which #712 extended with segmented calculation and cancellation. It now reads as its phases: claim the simulation (refusing one that is no longer active or that another writer claimed), build the cancellation check, write the input, calculate (segmented, standard or injected, always as a SimulationCalculation), write the artifact, and record success or failure, each phase but the writes in its own function. The order of reads, writes and reports is unchanged, and the previous commit's pins keep their assertions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk
added a commit
that referenced
this pull request
Oct 20, 2026
#712 landed on main after this stack branched. Before its C901, PLR0912 and PLR0915 fetch_stage12_cache is split, pin what its two tests did not reach: each manifest rejection (no bucket or manifest, schema, bundle, year set, artifact list, artifact count and deployment digest), each artifact rejection (a non-object entry, a filename with a directory, a duplicate filename, a non-integer size and a same-size file with another digest, which leaves no partial file), and the storage client built with no credentials, a credentials object or credentials encoded as a string. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk
added a commit
that referenced
this pull request
Oct 20, 2026
…d file steps C901, PLR0912 and PLR0915 flag fetch_stage12_cache. It now validates the whole manifest first (schema, installed bundle and years; the artifact list and its counts; then the deployment digest), builds the storage client from the deployment's credentials only when none is given, and fetches each artifact through a partial file. The checks, their order and the environment variables it reads are unchanged; the module stays self-contained for the image build. The previous commit's pins keep their assertions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk
added a commit
that referenced
this pull request
Oct 20, 2026
…PERF, PLW, PT, PTH, RUF, SIM and others) (#802) * Exempt the rules tool's unittest tests from pytest-style rules The shared rules tool needs only the standard library, so its tests use unittest on purpose. Registered as sim.quality.unittest-ruff-exemptions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Resolve ruff group 8a findings in policyengine-fastapi - JWTDecoder takes auto_error as keyword-only (FBT001/FBT002); the only caller, the gateway, already passes it by keyword. - JWTDecoder.__call__ declares its bearer dependency with Annotated (B008). - The 403s raised while validating a token chain the caught error (B904). - Iterate dicts directly instead of over .keys() (SIM118). - Name the ignored error code on the trace-filter type: ignore (PGH003). - Explain NoOpObservability.span's unused arguments: it implements the Observability protocol (ARG002). - Check expected exceptions with pytest.raises(match=...) (PT017). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Resolve ruff group 8a findings in the simulation contract - Merge the two branches that both fall back to the package version (SIM114), and collapse the nested as_of check (SIM102). - The test modal.Dict stand-in takes create_if_missing as keyword-only, matching Modal's own from_name (FBT001/FBT002). - Request the mock_modal fixture with usefixtures, and prefix the fake HF fetchers' unused positional parameters with an underscore (ARG). - Use tuples for parametrize names (PT006), a dict literal (C408), and match the expected validation messages (PT011). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Resolve ruff group 8a findings in the entry project - CallerAuthenticator reads its bearer dependency marker from a module-level default (B008). The Annotated form isn't usable there: the module postpones annotations and FastAPI resolves them against the callable's __globals__, which a callable instance lacks. The OpenAPI document is unchanged. - Keep the three telemetry try/except blocks, whose except lines carry the BLE001/S110 reasons, and suppress SIM105 on their try lines. - Mark the failing test backends' request() with @OverRide, prefix the positional-only parameters of the _decoder replacements with an underscore, and explain the fake decoders' unused parameters (ARG). - Drop the unused tmp_path requests (ARG001) and use tuples for parametrize names (PT006). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Resolve ruff group 8a findings in the gateway project - require_auth declares its bearer dependency through a module-level Annotated alias (B008); the module postpones annotations, and FastAPI resolves the alias through the function's globals. The OpenAPI documents are unchanged. - Chain the caught error into the 400, 404 and 503 HTTPExceptions and the route-resolution ValueErrors (B904). A BLE001 suppression that the chained raise made unused keeps its reason as a comment. - Keep the telemetry try/except blocks, whose except lines carry the BLE001/S110 reasons, and suppress SIM105 on their try lines. - Use a conditional expression for the resolved version (SIM108) and Path.open in the OpenAPI generator (PTH123). - Test stand-ins: keyword-only create_if_missing and use_bundle_default (FBT), ClassVar registries (RUF012), and explained unused parameters of the FunctionCall.get stand-in and the side-effect fixture (ARG). - Tests: usefixtures for mock_modal and the import-isolation fixture, tuple parametrize names, bare @pytest.fixture, a split assertion, an explicit check=False, dict() over a comprehension, and the decoder class passed directly instead of through a lambda. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Resolve ruff group 8a findings in the executor sources and fixtures - Assign spm directly instead of through setattr (B010), use dict.fromkeys (C420), list unpacking (RUF005), one combined with-statement in the same enter/exit order (SIM117), a membership test (SIM109), merged upper-casing branches (SIM114), extend over an append loop (PERF401), and Path.open/Path.unlink (PTH). - Keep, with reasons: the per-child and per-call try/except loops (PERF203), the credentials temp file that stays open across the yield (SIM115), HDFStore.keys(), which isn't a dict view (SIM118), the FastAPI lifespan hook's app parameter (ARG001), and the precompute local entrypoint's positional force flag, whose signature Modal turns into the CLI (FBT001/FBT002). - Keep the telemetry and best-effort-cancel try/except blocks, whose except lines carry the BLE001/S110 reasons, and suppress SIM105 on their try lines. - Fixtures: prefix the identity stubs' positional-only parameters and the fake local_entrypoint's **kwargs with an underscore (ARG). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Resolve unused-argument findings in the executor tests - Request side-effect fixtures with @pytest.mark.usefixtures, and drop an unused monkeypatch request (ARG001/ARG002). - Prefix unused parameters of monkeypatched replacements with an underscore, after checking that every caller passes them positionally, and rename unused *args/**kwargs to *_args/**_kwargs (ARG001/ARG005). - Keep, with reasons, parameters that callers pass by keyword: the _load_dataset and _reduce replacements, the fake _build_simulation and economic_impact_analysis keyword-only parameters, FunctionCall.get stand-ins' timeout, and the Stage 12 writer stand-ins' prefix. Fake model versions that duck-type .py's MicrosimulationModelVersion keep their parameters too. - Mark the failing child store's create_or_resolve_simulation with @OverRide. - Use tuples for parametrize names (PT006). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Resolve the remaining ruff group 8a findings in the executor tests - Pass booleans by keyword: DatasetSelection's is_default (FBT003), and keyword-only routed/accepted test parameters, the routed helper flag, the output-builder helper's options and the modal.Dict stand-ins' environment_name/create_if_missing, matching Modal (FBT001/FBT002). - Match the real messages in pytest.raises (PT011); one native-only check keeps its broad ValueError, with a reason, because the rejecting layer's message varies. Make regex patterns raw or escaped (RUF043), give pytest.fail a message (PT016), and move the GCP credential cleanup check into a helper so pytest.raises wraps one call (PT012/SIM117). - Use Path methods (PTH), literals and dict() (C408/C416), a list literal (SIM905), unpacking (RUF005), extend (PERF401), ClassVar annotations (RUF012), an underscore for an unused unpacked value (RUF059), explicit check=False (PLW1510) and an ASCII operator in a comment (RUF003). - Pass regions.get, checks.append and nullcontext directly instead of through lambdas; nothing patches them (PLW0108). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Resolve ruff group 8a findings in the CI script and integration tests - The package update script stops overwriting its loop variable (PLW2901) and replaces the pyproject through Path.replace (PTH105). - The integration conftest uses bare @pytest.fixture (PT001). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Enable ruff group 8a (arguments, idioms and pytest style) Adds ARG, ASYNC, B, C4, FBT, PD, PERF, PGH, PLW, PT, PTH, RUF and SIM to the selected rule families. Every finding outside the shared rules tool is fixed or carries an inline reason. Complexity (C90, PLR) is left for group 8b. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Register group 8a in the ruff-lint rule sim.quality.ruff-lint now lists ARG, ASYNC, B, C4, FBT, PD, PERF, PGH, PLW, PT, PTH, RUF and SIM. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Sync the rules tool from the Public API's group 8a The shared scripts/rules tool takes the Public API's group 8a fixes: comprehensions, explicit check=False, bound loop variables and the removed unused workflow-parser parameter. The rendered rules page picks up the tool's new hash. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fix the ruff group 8a findings in the code #743 and #744 added #743 and #744 landed on main after this stack branched. Group 8a, which this PR enables, flags their new code; each fix follows this PR's conventions: - F821: this PR's CI-script commit dropped the package updater's os import, but #744's verify-dispatch reads os.environ, so restore it. - PTH123: the routing and sentinel CLIs open their files through Path. - RUF100: the sentinel's report helper prints with an explicit file, so its T201 suppression is unused. - ARG: tests request serialized_workflow through usefixtures (and the patched_modal fixture keeps it with a reason), drop unused monkeypatch requests, and prefix unused parameters of positional stand-ins. - FBT002: the routing-plan test helper takes force by keyword. - PT012/PT018/B905/RUF043: one call per pytest.raises, one condition per assert, an explicit non-strict zip over call chains of different lengths, and a raw match pattern. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fix the ruff group 8a findings in the code #789 added #789 landed on main after this stack branched. Group 8a, which this PR enables, flags its new code; each fix follows this PR's conventions: - ARG001/ARG002: open_connection_once and the telemetry-recording test runtime keep keyword parameters their protocols pass, with reasons; the test's random.uniform stand-in prefixes its unused positional bound. - PERF203: the connection retry loop catches each refused attempt by design, with a reason. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fix the ruff group 8a findings in the code #787 added #787 landed on main after this stack branched. Group 8a, which this PR enables, flags its new code; each fix follows this PR's conventions: - ARG002: the shared in-memory object store keeps upload_bytes's content_type, which Stage12ArtifactStore passes by keyword, with the reason this PR gave the per-file fake it replaced. - PT011: the invalid SPM provenance test expects pydantic's ValidationError, which every rejected value raises, not any ValueError. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fix the ruff group 8a findings in the code #795 added #795 landed on main after this stack branched. Group 8a, which this PR enables, flags its new code; each fix follows this PR's conventions: - ARG002: the child-deadline tests take the bare_country fixture through pytest.mark.usefixtures, like the runner tests. - ARG005: their _reduce stand-ins keep country_module, which run() passes by keyword, with the same reason. - ARG001: _crashing_call prefixes the clock it takes only to match _stuck_call's signature. - PT006: the child-timeout telemetry test names its parameters in a tuple. - SIM105: _cancel_call keeps its try/except, whose except line carries the BLE001/S110 reason, like the best-effort cancel it replaced. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fix the ruff group 8a findings in the code #791 added enables, flags its new tests; each fix follows this PR's conventions: - ARG002: its fake Modal calls, recording runtimes and rejecting aggregate writer keep the timeout, status_code and prefix keywords of the interfaces they stand in for, with the reasons this PR gave the other fakes. - PT011: the stage-and-role summary test matches the message it raises rather than accepting any ValueError. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fix the ruff group 8a findings in the code #758 added #758 landed on main after this stack branched. Group 8a, which this PR enables, flags its new code; each fix follows this PR's conventions: - B008: CallerAuthorizer.require_permission builds the authenticate dependency marker once and reads it from the nested dependency's default. The Annotated form can't be used: the module postpones annotations, and FastAPI resolves them against the module's globals, which don't hold the authorizer's dependency. FastAPI sees the same dependency, so each route's permission check is unchanged. - SIM105: the permission-missing event keeps its try/except, whose except line carries the BLE001/S110 reason, like the module's other telemetry. - ARG: the permission tests' fake decoders explain their unused parameters and the _decoder replacements prefix theirs; each table-driven test underscores the PROTECTED_ROUTES column it doesn't read, so the test ids stay the same. - ARG001: the deployed enforcement test requests permission_enforcement through pytest.mark.usefixtures; pytest still sets it up first, so the test skips before fetching a token. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fix the ruff group 8a findings in the code #781 added #781 landed on main after this stack branched. Group 8a, which this PR enables, flags its new code; each fix follows this PR's conventions: - ARG: stand-ins whose callers pass a parameter positionally prefix it with an underscore (the progress store's reads and writes, the patched Simulation.ensure, _country_module, get_region, Function.from_name, the preempted _set_modal_call_attributes, time.sleep and the on_first_ensure stand-ins), and unused *args/**kwargs become *_args/**_kwargs. Parameters callers pass by keyword keep a reason: the impact-analysis, _load_dataset, dispatch, run_simulation_impl and _configure_modal_observability stand-ins, the spawn stand-in's observability_context, the _reduce stand-in's country_module, the fake FunctionCall.get's timeout and the generated client's sync_detailed. - ARG001/ARG002: the contract's missing-record test and the bundle manifest test request their fixtures through pytest.mark.usefixtures. - B018: the failed-analysis test assigns the property it reads inside pytest.raises. - SIM105: the Modal app's stage-duration telemetry keeps its try/except, whose except line carries the BLE001/S110 reason. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Exempt tests from the unused-argument rules file-wide Test fakes and stubs mirror real signatures, because callers pass those arguments by keyword: run() passes country_module=, the runtime passes keyword-only options, and stand-ins replace modal.FunctionCall.get and JWTDecoder.__call__. Fixtures also request other fixtures only for their side effects. Tests are among the files allowed file-level exceptions, so ARG001, ARG002 and ARG005 join the tests' and the deployed-service tests' exemptions. That removes 72 inline suppressions from 16 test files. Application code keeps its six unused-argument suppressions and their reasons. Comments and configuration only: no code changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Swallow best-effort errors with contextlib.suppress Nine blocks ran a best-effort call and swallowed any error with try/except Exception: pass: - telemetry context and events in the entry app, its auth, the Modal app and the gateway; - a Modal call cancel during segmented national cleanup. Each block carried three suppressions: SIM105 on the try, and BLE001 and S110 on the except. Each is now with contextlib.suppress(Exception), with the old reason as a comment above it. It suppresses the same exceptions and runs the same calls, so behaviour is unchanged. That removes 18 suppression lines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fix the ruff group 8a findings in the code #712 added #712's segmentation and cache code raised 16 group 8a findings. Each is fixed in the code, with no new suppression: - PERF203: the policy-period parser and the segment-cancellation loop move their try/except into a helper called once per item. The parser's error helper is now typed NoReturn, since it always raises. The cancellation helper keeps #712's BLE001 reason. - RUF022: stage12_runtime's __all__ is sorted. - Tests: subprocess.run states check=False (PLW1510), match= patterns are raw or escape a literal dot (RUF043), compound asserts are split (PT018), the import guard extends its list from a generator (PERF401), boolean helper and parametrized arguments are keyword-only (FBT001/FBT002), and the segment-function stand-in is an instance whose spawn method takes Modal's observability_context by keyword (ARG004), as modal.Function.from_name returns an object. The rebase onto #712 also dropped this branch's PERF203 suppression in coordination.py, since #712 replaced the loop it was on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Lint the executor as Python 3.13, its required version Ruff reads no project's requires-python here, so it linted the executor with its default target version. PERF203 applies only below Python 3.11, so the retry loop in database_retry.py needed a suppression that the executor's own Python (3.13) doesn't call for. Add the executor to per-file-target-version as py313, remove that suppression, and fix the eight findings the new target version reports: tomllib is now sorted with the standard library (I001, five files), and datetime.timezone.utc becomes datetime.UTC (UP017, three files). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Use or underscore the arguments behind five ARG suppressions - NoOpObservability now subclasses the Observability protocol it already implemented, and span() is marked @OverRide, which ruff exempts from ARG002. - The executor's in-memory object store records each object's content type, and the gateway's mock FunctionCall records the timeout each get() was called with, so both fakes use the arguments they mirror. - The gateway's import_gateway_models fixture only requested the module isolation fixture for its side effect. The test that used it now requests isolation with usefixtures and imports the models module itself, named in GatewayImportModuleNames.models. - The executor's FastAPI lifespan takes _app: Starlette and FastAPI pass the app positionally. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Make the precompute entrypoint's force option keyword-only Modal 1.4.2 builds a command-line option for every non-variadic parameter of a local entrypoint and calls it with keyword arguments, so a keyword-only force still gives --force/--no-force. The test already calls main(force=True). This removes the FBT001/FBT002 suppression. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Replace the SIM115, SIM118 and PT011 suppressions with code - setup_gcp_credentials enters the credentials temp file on an ExitStack held by a with statement around the yield. The file still stays open across the yield; the finally block restores GOOGLE_APPLICATION_CREDENTIALS first, and the stack then closes, and so deletes, the file, the same order as before. - The UK local-authority check calls HDFStore.keys(include="pandas"), the default spelled out, and keeps a comment on why `in store` differs. - The native SPM test passes check= to pytest.raises (pytest 8.4), so the typed error code is part of the raises check instead of a later assert. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Restore the lower-case coverage pragma comment in gateway auth The contextlib.suppress change rewrote "# pragma: no cover" as the prose "# Pragma: no cover". Put back the original lower-case spelling. The comment sits on its own line above the with statement, so coverage.py excludes no statement because of it either way. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Anthony Volk <14987227+anth-volk@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
anth-volk
added a commit
that referenced
this pull request
Oct 20, 2026
… (ALL) (#804) * Import test modules with from-imports instead of aliases Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Group test helper parameters into composite fixtures and option objects Split the long deployment-workflow test into focused tests and hoist closure-free test doubles to module level. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pin contract and persistence validation behaviour before refactoring Characterize every branch of the comparison report record's state validation, the live column type checks, Hugging Face URI parsing, the SPM non-US and historical-wrapper rules, and the v2 application name limits. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Split the comparison report record's state validation into report and comparison phases Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Name the contract's URI, version and name-length limits Parse SPM wrapper and model release versions through one helper, and keep the keyword signatures of the public contract builders that the gateway and executor call. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Split the live column type check into enum and plain type checks Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep the observability initializer's keyword-only identity arguments Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pin the order and messages of entry settings validation Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Split entry settings validation into caller auth, old gateway and Stage 12 checks Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pin the Stage 12 adapter's complete report input and check order Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Split the Stage 12 annual-comparison adapter into eligibility checks and a report builder Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pin the Stage 12 dispatch calls, acknowledged parent and skip event Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Group Stage 12 dispatch inputs into a prepared report, origin and worker version Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pin entry app telemetry, Stage 12 route failures and backend readiness Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Split the entry app factory into a shared runtime and route registration steps The routes keep their names, decorators, parameters and registration order, so the published OpenAPI document is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Compare the old gateway's health status with HTTPStatus.OK Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pin gateway route resolution from the active routing state Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Split gateway active-state route resolution into pinned, latest and requested-version steps Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pass the resolved region to _build_simulation instead of its code and scoping strategy Every caller already held a RegionResolution, so the builder now takes it whole. Also name the simulation runtime's preview, date-range and state-code limits. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pass artifact key inputs to the dataset and baseline key builders as dataclasses The payloads are built from the same fields in the same way; the golden digest tests still pass unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pass baseline identity facts as one request and split out the artifact scope check Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pass the precompute's Modal function handles to run_precompute as one group The precompute local entrypoint keeps its --force CLI signature. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pin every input the national reduce passes to the output builder Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pass the national reduce's run facts to build_national_output as one group Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pass fan-out poll timing to the batch and segmented runners as one backoff Also name the segmented runner's consecutive poll-error limit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pin the Stage 12 bundle schema version check Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Name the supported Stage 12 bundle schema version Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pin every Stage 12 parity mismatch code, path and comparison order Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Split Stage 12 parity comparisons into entity, column and report-value checks Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pin parity qualification receipts, check order and default runner lifecycle Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Split parity qualification into existing-run, provenance, calculation and aggregate steps Group the injectable runners and the reviewed tolerances into two option objects, and update the qualification script and tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pin Stage 12 coordination validation, claims, failures, stages and aggregate output Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pass each role's retained frames and descriptor to the aggregate builder together Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Split Stage 12 report coordination into claim, plan, dispatch, wait and aggregate phases Group the coordinator's Modal identity and its injectable seams, and split submitted-parent validation into invocation, claim-state and production checks. The Modal coordinate_report function keeps its signature. Rebased onto #712, the phases carry #712's coordinator: one shared child-wait window opened before any setup, nonblocking probes of both children, cancellation of the unfinished children on any failure, and failure details with correlation IDs. CoordinatorDependencies gains #712's sleep, monotonic and wait_timeout_seconds seams. The report-level failure handling moves into _ReportCoordination.fail, and the run's child plans, calls and descriptors live on _ReportCoordination, so a failure in any phase can stop the started children. The coordination pins expect #712's single child-wait span and correlation-ID suffixes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pin how the Modal child invoker looks up, spawns and restores children Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pass the child invoker one spawn request instead of six keywords Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pin result comparison logs, spans and the failed state-update path Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pass the result comparison its coordinator services as one group Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pin the Stage 12 worker's reuse path and calculator timing Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pass the single-simulation worker its seams as one dependencies group The Modal run_single_simulation_us and _uk functions keep their signatures. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pin Stage 12 worker validation results, order and failures Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pass Stage 12 worker validation its environment and checks as one group Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pin routing state validation messages and check order Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Group a deployed app's versions in the routing publisher and split its validation The update_version_registry command-line options are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pin the artifact fetch layer's receipt, credential and download edge cases Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Split the image artifact fetch into freshness, credential and download steps The helpers stay in the self-contained image setup module with lazy imports. Changing fetch_artifacts's source changes its Modal build-layer definition, so the next simulation image build re-runs the artifact fetch layer once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pin the release sentinel's polling, retry statuses and problem order Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Split the release sentinel's deployment check into submit, poll and compare steps Group the job timeout, poll interval, sleep and clock as one JobPolling, and name the HTTP statuses the sentinel compares with. The check command's options are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pin the routing publisher CLI's mode order, outputs and argument errors Also pin how a publication id names its owning run, and the restore refusals for no plans and a missing routing store. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Split the routing publisher CLI into one command per mode The modes keep their precedence and output, and the help text is unchanged. Also split the plan-restore loop out of restore_routing_plans and name the parts of a workflow run's publication id. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pin the package update helper's bundle-versions and verify-dispatch output Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Split the package update helper's bundle and dispatch checks into helpers The helper's subcommands, output lines and messages are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pin the Stage 12 connection retry's delay sequence and budget cut Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pass the Stage 12 connection retry its clock, sleep and jitter as one timing The persistence store still calls the opener with only the connect step and the operation name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pin the workflow expression evaluator and routing command discovery Pin what the GitHub expression evaluator returns for each operator and status function, and the argv and refusal messages routing command discovery produces, before splitting both test helpers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Split the workflow expression evaluator into a tokenizer and a parser Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Split routing command discovery into script, array and per-command checks Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Group the routing and package-update test parameters into cases Give the fake git its reported state as one FakeGitState, pass a planned release's versions to _plan together, and parametrize each multi-value test case as one argument. The parametrized test ids are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Take the Public API's group 8b rules tool refactor Copy the Public API's scripts/rules, whose group 8b commits split the command line, the registry reader, validation, the workflow job parser and the mermaid graph builder, and pin their behaviour with new tests. The two copies were identical apart from that refactor and its tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Select every ruff rule (ALL) Every rule family now passes, so ruff.toml selects ALL instead of the per-group list. It ignores COM812 and ISC001, which conflict with ruff format (ruff's documentation says to disable them with the formatter), and D203 and D213, which are mutually exclusive with the kept D211 and D212. The per-file target version, namespace packages and per-file ignores are unchanged. The sim.quality.ruff-lint rule's enforcer codes become ["ALL"], and its statement names the four ignores and why. The rules-registry skill says every rule applies apart from the listed and registered per-file ignores, that a ruff upgrade PR fixes any new rule's violations, and that an exception is an inline noqa with a reason. The rendered rules page and the AGENTS.md rules block are regenerated, and the changelog fragment describes the complexity refactors and the switch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pin the poll, deadline and budget timing the segmented runner #795 extended #795 landed on main after this stack branched and gave the segmented national runner a child deadline, a parent budget and a clock beside its poll backoff. PLR0913, which this PR enables, flags the eight-argument initializer; these tests pin the deployed defaults and how given values are taken before the timing is passed as one group. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pass the segmented runner the poll and deadline timing #795 added as one group enables, flags the segmented national runner's initializer once #795's child deadline, parent budget and clock sit beside the poll backoff. The four now travel as one SegmentedRunTiming with the same defaults, which the previous commit pins; run_segmented_national keeps its signature. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Set the fake child call's cancel failures #795 added after construction #795 landed on main after this stack branched. PLR0913 flags the six-argument initializer of the segmented national tests' fake child call once #795 added before_each_poll and cancel_failures. No test passes cancel_failures to the initializer; the two that use it set it on the call, so it now starts at zero and stays an attribute. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pin how the tracked_stage #791 added scopes and records each stage #791 landed on main after this stack branched. PLR0913, which this PR enables, flags its six-argument tracked_stage, which other tests cover only through the coordinator and worker. These tests pin it directly before its stage plan, progress and runtime are passed as one group: the span or operation it opens, the attributes it passes, the role it records and the failing stage it keeps without a runtime. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pass the tracked_stage #791 added its stage plan, progress and runtime as one group enables, flags its six-argument tracked_stage. Each process's stage plan, progress and runtime now travel as one StageTracking: the worker builds one per run, and the coordinator derives its own from the report it coordinates. The previous commit's pins keep their assertions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Compare the word count in the redaction helper #791 added without a magic 2 #791 landed on main after this stack branched. PLR2004, which this PR enables, flags the len(words) == 2 test in its _before_last_word. Splitting once from the right yields at most two parts, so len(words) > 1 is the same test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Parametrize the permission tests #758 added by one route case #758 landed on main after this stack branched. PLR0913, which this PR enables, flags its six-argument log-only permission test. As this PR's other table-driven tests do, each protected route is now one case: the PROTECTED_ROUTES table holds a ProtectedRoute per route, and the four tests over it take one route argument, which also drops the underscored columns two of them didn't read. The parametrized test ids are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pin the runtime and job progress the segmented runner #781 reports to #781 landed on main after this stack branched and gave the segmented national runner a job progress reporter beside its runtime. PLR0913, which this PR enables, flags the now six-argument initializer; these tests pin that a runner built without one reports no job progress, and that run_segmented_national_impl gives its runner the runtime and job progress it was called with, before the two are passed as one group. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pass the segmented runner its runtime and the job progress #781 added as one group #781 landed on main after this stack branched. PLR0913, which this PR enables, flags the segmented national runner's initializer once #781's job progress reporter sits beside its runtime. The two now travel as one SegmentedRunReporting, whose progress defaults to reporting nothing as before; run_segmented_national_impl and dispatch_run_simulation keep their signatures, and the previous commit's pins keep their assertions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Name the numbers #712's segmentation and cache code compares against #712 landed on main after this stack branched. PLR2004, which this PR enables, flags five magic values in its code; each is now a named constant, with no behaviour change: - the date-range period's two parts (_DATE_RANGE_PART_COUNT, the name simulation_runtime already uses); - the segment poll's consecutive-error limit (CONSECUTIVE_POLL_ERROR_LIMIT, as in segmented_national); - the national partition's 20 region groups (NATIONAL_PARTITION_GROUP_COUNT, next to US_REGION_GROUPS); - worker validation's cache years, artifact count and probe year, from the cache precompute's PRECOMPUTE_YEARS: one dataset per year and one baseline per US region group per year make the 63 artifacts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Observe the children the #712 coordinator waits for as a coordination method #712 landed on main after this stack branched. PLR0913, which this PR enables, flags its eleven-argument _observe_simulations. It becomes _ReportCoordination._observe_children, which takes the calls and descriptors and reads the child plans, store, runtime, progress, stage plan, clock, sleep and wait window from the coordination it belongs to. The polling, failure and timeout logic is unchanged, and #712's runtime tests cover every path through it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pass the failure reports #712 added where each failure was caught as one site #712 landed on main after this stack branched. PLR0913, which this PR enables, flags its seven-argument report_failure. The telemetry scope, default error code, attributes and segment index that say where a failure was caught now travel as one FailureSite; the error, runtime and progress stay separate. The coordinator's three calls, the worker's and the segment runner's pass the same values as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pass the segment runner #712 added its lookup and polling seams as one group #712 landed on main after this stack branched. PLR0913, which this PR enables, flags Stage12SegmentRunner's nine-argument constructor. The Modal module, region groups, sleep and the two poll intervals that tests replace now travel as one SegmentRunnerSeams, whose defaults are the deployed ones; the simulation, app name, runtime and cancellation check stay separate. run_segmented_simulation passes nothing new. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pass the Stage 12 simulation builder #712 added its resolved inputs as one group #712 landed on main after this stack branched. PLR0913, which this PR enables, flags its eight-argument build_stage12_simulation. The country module, region, dataset selection and loaded dataset the worker resolves in its own stages now travel as one ResolvedSimulationInputs. The builder reads the scoping strategy from the region, as _build_simulation already does, so it no longer takes the region code it only deleted. The SPM parameters, normalised policy and execution input stay separate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pass the cache precompute #712 added its Modal function handles as one group #712 landed on main after this stack branched. PLR0913, which this PR enables, flags the cache precompute's seven-argument run_precompute. Its five Modal function handles now travel as one CachePrecomputeFunctions, as the baseline precompute's PrecomputeFunctions already do; the bucket and echo stay separate. The Modal app's entrypoint passes the same five functions, and its secrets and environment are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pass the scripted child call #712's tests added its outcome as one script #712 landed on main after this stack branched. PLR0913, which this PR enables, flags the six-argument ScriptedChildCall its runtime tests use. The result, wait error, cancel error and during-wait hook that script a call's outcome now travel as one ChildCallScript; the call ID and clock stay separate. Both constructions script the same outcomes as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pin which simulations the Stage 12 cache identity #712 added qualifies #712 landed on main after this stack branched. Before its eight-return qualifying_cache_identity is split, pin the ineligible cases its tests did not reach: no or a non-region-group scoping strategy, a missing, empty or non-list region group, a non-state or non-text region code, and a scope without a cache key. Each gets no cache identity. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Split the Stage 12 cache eligibility #712 added into baseline, region and scope checks PLR0911 flags the eight returns of qualifying_cache_identity. It now asks three questions: is this a current-law baseline on the default dataset, what is its group of state regions, and what is the region-group scope's cache key. Each check is pure, so asking them in this order gives the same result; the previous commit's pins and #712's identity tests keep their assertions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pin the frame comparison and probe checks of the determinism check #712 added #712 landed on main after this stack branched. Before its C901 verify_determinism_impl is split, pin what its tests did not reach: every kind of frame difference (an entity missing on one side, a column on only one side, a dtype change and a value change) in its sorted order, the rejection of a probe that is not a planned baseline, and that the stored baseline is downloaded only when it is not already in the data folder. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Compare the determinism check's frames #712 added in their own function C901 flags verify_determinism_impl. The comparison of cached and fresh frames moves into _frame_differences, which lists each difference in the same sorted order; the probe check, download, cache-hit check and pass record stay in verify_determinism_impl. The previous commit's pins keep their assertions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pin every check of the segment merge #712 added #712 landed on main after this stack branched. Before its C901 merge_segment_results is split, pin the checks its tests did not reach: a result set that omits a segment, a result naming another region group, segments with different entities or column dtypes, and an entity without its identifier. The serializer refuses frames without an identifier, so that pin decodes every segment to one directly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Split the segment merge #712 added into indexing, decoding and entity merge steps C901 and PLR0912 flag merge_segment_results. It now indexes the results (rejecting an incomplete set or a duplicate index), decodes each planned segment in partition order (rejecting an omitted segment or one naming another role or region group), and merges each entity's frames (rejecting different entities, dtypes, a missing identifier or duplicate IDs), each in its own function. The checks and their order are unchanged, and the previous commit's pins keep their assertions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pin the claim and cancellation-check paths of the worker #712 extended #712 landed on main after this stack branched and extended run_single_simulation, which C901 and PLR0915 now flag. Before it is split, pin the paths its tests did not reach: a simulation that is no longer active is refused, a claim another writer won stops the run as cancelled, and a failed status read in the cancellation check is logged by error type only and treated as not cancelled, so the run completes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Split the Stage 12 worker #712 extended into claim, calculate and record steps C901 and PLR0915 flag run_single_simulation, which #712 extended with segmented calculation and cancellation. It now reads as its phases: claim the simulation (refusing one that is no longer active or that another writer claimed), build the cancellation check, write the input, calculate (segmented, standard or injected, always as a SimulationCalculation), write the artifact, and record success or failure, each phase but the writes in its own function. The order of reads, writes and reports is unchanged, and the previous commit's pins keep their assertions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pin every check of the image-build cache fetch #712 added #712 landed on main after this stack branched. Before its C901, PLR0912 and PLR0915 fetch_stage12_cache is split, pin what its two tests did not reach: each manifest rejection (no bucket or manifest, schema, bundle, year set, artifact list, artifact count and deployment digest), each artifact rejection (a non-object entry, a filename with a directory, a duplicate filename, a non-integer size and a same-size file with another digest, which leaves no partial file), and the storage client built with no credentials, a credentials object or credentials encoded as a string. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Split the image-build cache fetch #712 added into manifest, client and file steps C901, PLR0912 and PLR0915 flag fetch_stage12_cache. It now validates the whole manifest first (schema, installed bundle and years; the artifact list and its counts; then the deployment digest), builds the storage client from the deployment's credentials only when none is given, and fetches each artifact through a partial file. The checks, their order and the environment variables it reads are unchanged; the module stays self-contained for the image build. The previous commit's pins keep their assertions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drop init_simulation_observability's never-passed service_version None of the four callers (the entry app, the gateway app and its test factory, and the executor's FastAPI app) or the tests passes service_version, and build_runtime falls back to the package version when it is absent. Without it the function takes five arguments, so its PLR0913 suppression goes. Modal workers still pass service_version through init_process_observability. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pass create_initial_batch_state its route as one BatchRoute The batch's reported version and its Modal app name always travel together: the gateway takes both from its route resolution, and the batch scheduler from its batch context. create_initial_batch_state now takes them as one frozen BatchRoute(version, app_name), so it has five arguments and needs no PLR0913 suppression. The stored batch state is unchanged. The gateway, the scheduler and the tests pass a BatchRoute. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pass resolve_spm_selection the route's versions as one SPMRouteVersions The policyengine.py version, the model version and the route provenance are read only together, to decide whether a route with no SPM capability predates canonical SPM. resolve_spm_selection now takes them as one frozen SPMRouteVersions, so it has four arguments and needs no PLR0913 suppression. Its decisions are unchanged. The gateway, the executor and the tests pass an SPMRouteVersions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Call Stage 12 runtime functions through v2_app's module-level import v2_app.py already imports policyengine_simulation_executor.stage12_runtime at module level, and that package's __init__ imports its submodules eagerly, so the four in-function imports of the same package loaded nothing new and each needed a PLC0415 suppression. The Modal function bodies now call stage12_runtime.<name> instead. Attribute lookups happen at call time, so tests that patch stage12_runtime.run_single_simulation or stage12_runtime.coordinate_report still take effect. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Import the image setup module's standard-library modules at module level Splitting fetch_artifacts into six functions gave each one its own in-function imports of json, logging, os and pathlib.Path, raising the module's PLC0415 suppressions from 7 to 14. Those modules are always importable in the image-build step: Modal imports this module from the function's mounted source package (the function is not serialized), and stage12_cache_image.py, which also runs before add_local_python_source, already imports json, os and pathlib at module level. The module now imports them, and typing.TYPE_CHECKING, at module level, which removes twelve suppressions. test_image_setup_module_stays_self_contained now allows module-level imports of standard-library modules only (sys.stdlib_module_names), and still rejects any import of the executor package. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * List google.oauth2 and policyengine_uk as lazily loaded packages Add both to banned-module-level-imports, so ruff keeps them out of module-level imports (TID253) and accepts their in-function imports without a PLC0415 suppression. That removes the two remaining suppressions in _image_setup.py. stage12_cache_image.py was the only application module that imported google.oauth2 at module level; the import moves into _storage_client, the one function that uses it. The storage-client test now patches google.oauth2.service_account.Credentials directly, as the image setup tests already do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Update the changelog entry for the removed PLR0913 suppressions Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Anthony Volk <14987227+anth-volk@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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 #711
Fixes #792
Same-repository draft PR check
PolicyEngine/policyengine-sim-api, not a personal fork.Summary
persist_failure_record.pe-uk-private-hf-read-tokenHugging Face secret. See "Fixes after review" below.Merged main after #799 and the .py 6.7.5 update (
28759410)Main is merged in as a merge commit, as before. Main brought .py 6.7.5 (#818, #820, #822, #826), ruff rule groups 2 to 7 (#773, #777, #779, #783, #785, #799), the Auth0 permission checks on the entrypoint routes (#758), job stage reporting (#781) and the policyengine-observability 3.0.2 lockfiles (#816).
Conflicts
Twelve files conflicted.
_require_artifact_digesthelper is kept; its merged call sites already use it. Main's_mark_child_failedis dropped, because this PR had already replaced it with_record_child_failureand the cancellation records. The wait-window constant still comes fromstage12_timeouts.PrecomputedSimulationstays in the aggregation module, and main's module-levelbuild_spm_comparison_provenanceimport is kept.TYPE_CHECKINGimports against this PR's cache, segmentation and cancellation additions. Each kept both: this PR's behaviour, in main's style.Ruff rules from main (
28759410,2a81eba1)Main's new rule groups found 372 findings in this PR's code. All are fixed. The only suppressions this PR adds are nine catch-all handlers (BLE001), each with its reason: they record a redacted cause and raise a fixed error
from None, or log a failed best-effort write or cancellation without replacing the original failure. Ruff accepts a catch-all only if it re-raises, chains or logs the original error with its message, which these handlers deliberately avoid. Main's coordinator suppresses its own catch-all for the same reason.Stage12CachedSimulation,qualifying_cache_identityand the cache outcomes move fromstage12_cache.runtimetostage12_runtime.cached_simulation.should_segment_simulationmoves intostage12_runtime.partition. The simulation module now imports both at module level, and thestage12_cachepackage no longer imports .py.Simulation.ensurekeeps a process-wide in-memory cache of up to 100 simulations. When a stored baseline fails the output plan,Stage12CachedSimulation.ensurerecalculates and saves it. It then calls the cache's publicclear(), instead of replacing one private entry, so the incomplete output cannot be served from memory; the nextensureloads the saved file. A new test covers this.modal.Function, which replaces this PR's copy of theRemoteFunctionprotocol; Stage 12 code must not import the v1precompute_models. The fan-out's Modal module parameter is aModuleType. The otherAnyparameters are typed where a precise type exists.tryblocks move into helpers. The image build's two manifest type checks raiseTypeError.Stage12Cancellationis renamedStage12CancellationError(N818); it is internal to this PR..py 6.7.5
The lockfiles are main's; this PR doesn't change them. The test that runs the real bundle and US model, and checks that the planned baseline file is the one .py saves and loads, passes on 6.7.5. Between 6.4.1 and 6.7.5,
Simulation.ensureonly gained reruns of saved outputs that .py marks as outdated. The in-memory cache copy has existed since 6.0.0.Environment variables
This merge adds or removes no environment variable, workflow input, secret or Modal secret attachment. The conflict resolutions and ruff fixes touch only Python modules and tests. The deployed v2 application name follows the .py version, as on main.
Validation
On .py 6.7.5, merged with main at
35857b2e:ruff check .andruff format --check projects/*/src libs/*/srcwith the pinned ruff 0.15.22 pass.python -m scripts.rules guard --base-ref origin/mainandscripts/run_quality_guards.pypass. Shellcheck passes on the changed scripts.Fixes from the second review (
b68e5545,ea82c92f)Main is merged in first (
d007697a, after #765), with ruff's group 1 rules applied to this PR's files.Baseline cache files now have the names .py uses
Simulation.storage_id + ".h5". A US simulation always resolves an SPM selection, sostorage_idis<id>-spm-<sha256 of the selection>. The cache named baselines<simulation_id>.h5.compute_baseline_impltherefore never found the file it had just calculated ("Stage 12 baseline calculation produced no cache file"), which would fail every Stage 12 deploy, and no worker could have loaded a baked baseline.normalize_runtime_spmon the same parameters, which are now built by one sharedsimulation_runtime_params. Itsfilenamemirrorsstorage_id. The selection is part of the identity digest, so an explicit selection equal to the defaults shares the file, and a different selection gets its own. Plan entries carry the file name, and the manifest, the determinism check and the image use it.test_planned_baseline_file_is_the_one_the_worker_saves_and_loadsplans with the real bundle, builds the worker's model with the real US model throughbuild_stage12_simulation, and compares the name from .py's ownoutput_dataset_filepathwith the planned name. It fails with the old name.The v2 manifest stays readable by readers released before the cache
V2WorkerVersionandV2WorkerValidationgainedcache, and the manifest models forbid unknown fields. Readers already deployed would reject the published manifest, and a cold reader would then have no valid manifest. The manifest is published before the reading revision is promoted, staging deploys do not promote, and a rollback or revert would read the same document.publish_v2_manifestwrites the manifest under a newactive-with-cachekey, and the same manifest without the cache fields underactive. The loader and the publisher readactive-with-cache, falling back toactiveonly before the first publish that writes both keys. Removing theactivecopy needs a later change, once no deployed or rollback revision reads it.activecopy and rejectsactive-with-cache, and main's publisher (as after a revert) still publishes on top of it. A contract test pins the exact fields earlier readers accept.No forced rebuild of the immutable cache
force_recomputeinput, which main deploys pass through, also reached the Stage 12 cache step. Forced uploads hit create-only paths, and HDF5 baselines are not byte-reproducible. The store therefore raised "immutable Stage 12 cache path contains other data", failing staging and skipping production.--force, andselect_workselects only missing files. The v1 precompute still receivesforce_recompute. To replace a bad file, delete it and deploy again: the run recomputes it and repeats the determinism check.A failed determinism check cannot be skipped by running again
determinism/us/<digest>.json, keyed by the planned baseline identities. A run checks when it computed baselines or when no pass is recorded, andpublish_manifest_implrefuses to publish without the record. The check now also requires the stored baseline to load as a cache hit, so it cannot compare two fresh calculations.Not changed
Policy effective periods stay strict ISO 8601: a date such as
2027-4-6is rejected withinvalid_policy_period. This is intended.Environment variables
No variable is added or removed.
MODAL_TOKEN_IDandMODAL_TOKEN_SECRET(GitHub Actions secrets) andSTAGE12_CACHE_BUCKET(GitHub Actions variable, in the step'senv). The script writes to the runner'sGITHUB_OUTPUT. Only the positional force argument is gone.stage12-evaluation-gcp-credentialsModal secret that uploads the baselines, in the same bucket under a newdeterminism/prefix.Validation
Merged with main at
9cd0ca3c, on .py 6.4.1:ruff 0.15.22's lint and format checks, shellcheck on the changed script, and the rules-registry and ci-scripts guards also pass.
Fixes after review
A failed simulation's own state error no longer replaces its failure (
b6c691d1)Before this fix, the coordinator read a failed child's record, to choose its failure detail, outside any guard. A reproduction in which one database read of the failed baseline's record fails recorded the report as
report_coordination_failedinstead ofsimulation_invocation_failed, recorded the failed baseline itself assimulation_cancelled("Cancelled after the coordinator failed."), and gave the reform the same wrong reason. A failing write of that record was already caught, but it was logged without the report or simulation identifiers.evaluation_id,simulation_execution_id, the role and the error type, in the message and as record attributes. The error's message is not logged.simulation_invocation_failedorsimulation_dispatch_failed, or the typed failure the worker recorded).simulation_cancelled, and it is not cancelled through Modal.Each failure is reported once
#789's contract is that each failure is reported once. After the rebase, an unexpected error reached telemetry twice: once through #789's
report_original_failure("Stage 12 execution failed", with the error), and again through this PR'sfailure_detail_from_exception, which calledlog_and_redact_exception. That second report was a standard-library ERROR log with the stack trace,runtime.record_exceptionand agateway_error_redactedevent. It happened in the worker, child-wait, child-dispatch and report-coordination handlers. A segmented simulation's failure was also reported twice: the fan-out logged the segment error, and the worker then reported the wrappingStage12ExecutionError.report_failurehelper builds the redacted detail without logging it and emits onereport_original_failurerecord. That record carries the samecorrelation_idas the persistedSimulation failed (correlation_id=...)summary, plusevaluation_idand, for a simulation,simulation_execution_idand the role. Without a runtime, the one standard-library log line includes the correlation ID.failure_detail_from_exceptionis replaced byredacted_failure_detail, which only describes a failure and never reports it.The Stage 12 precompute app mounts only the read-only Hugging Face secret (
eef03c3c)src/modal/stage12_precompute_app.pyruns on every Stage 12 deployment. It mounted the retiringpolicyengine-data-credentialsandhuggingface-token(required_keys=["HF_TOKEN"]) Modal secrets on its image build and all five functions.policyengine bundle install ... --country us) and all five functions now mountpe-uk-private-hf-read-tokenwithrequired_keys=["HUGGING_FACE_TOKEN"]. The secret is declared through the sameHF_MODAL_SECRET_NAMEandHF_RUNTIME_ENV_NAMEconstants asapp.pyandv2_app.py. The functions keepstage12-evaluation-gcp-credentials. The app never readHF_TOKENitself, and nothing else changes.hf://policyengine/populace-us/.... This covers the bundle install in the image build and any dataset materialization duringensure_datasetsor a calculation. PolicyEngine 6.3.4's runtime code names no other Hugging Face repository for the US.policyengine/populace-usis a public, ungated dataset repository. The token's scope covers only the two private UK repositories, and this public repository needs no grant. The same token already downloads these artifacts in the v2 US worker image build. That layer was rebuilt with only this secret in today's 6.3.4 deployment (run 38545891000, staging) and installedpopulace_us_2024.h5frompolicyengine/populace-us. No credential was widened.test_deployment_sources_exclude_legacy_hf_secret_namesnow includes this app. A new declaration test checks the secret on the image build and every function. Both fail on the previous app source.Environment variables and wiring
HUGGING_FACE_TOKENPE_UK_PRIVATE_HF_READ_TOKEN, copied into Modal secretpe-uk-private-hf-read-tokenpreparejob validates the GitHub value, then runsmodal secret create pe-uk-private-hf-read-token HUGGING_FACE_TOKEN=... --env=<modal_environment> --forcewhendeploy_existing_stackordeploy_stage12_v2is true.deploy_stage12_v2needs: prepare, and its implicitsuccess()condition means the sync has succeeded, in the same Modal environment, before "Build the independent Stage 12 cache" runs. The sync is in thepreparejob, not in the same job as the precompute step. The app attaches the secret to the image build and every function.GOOGLE_APPLICATION_CREDENTIALS_JSONvars.STAGE12_GCP_CREDENTIALS_SECRET_NAME, copied into Modal secretstage12-evaluation-gcp-credentialsstage12-sync-modal-secrets.sh, in the samedeploy_stage12_v2job, runs before the precompute step.POLICYENGINE_DATA_FOLDERSTAGE12_CACHE_BUCKETvars.STAGE12_CACHE_BUCKETenv:.MODAL_TOKEN_ID,MODAL_TOKEN_SECRETmodal secret createandmodal runenv:.release_environment: beta, Modalstaging) and production (prod, Modalmain) call the reusable workflow withdeploy_stage12_v2: trueandsecrets: inherit, from bothsimulation-deploy.ymlandstage12-deploy.yml.modal secret listshowedpe-uk-private-hf-read-tokenandstage12-evaluation-gcp-credentialsin bothstagingandmain, each used on 2026-10-14. Secret values and the GitHub secret were not inspected.PE_UK_PRIVATE_HF_READ_TOKENis granted to this repository and is valid. Today's successful deployments, which validate and synchronize it, indicate that it is. The precompute app has not run with the new secret, because no deployment was run.Rebased onto main after #789
The branch is rebased onto
5484278455e7fd48bb8d874f0e26245895d2730c, the merge of #789. The earlier merge commit from main is gone, and each commit of this PR now builds on #789.Connection opener
Stage12PersistenceStore.replace_simulation_if_status, which this PR adds, opens its connection through_transaction, like every other store operation. Only the connect step can be retried; its statements and commit are never retried.runtime_store(runtime), so a refused connection is retried and each retry is logged with the operation name. The Cloud Run entrypoint keeps the non-retrying default.Failure recording
report_original_failure, before any database write. This covers worker failures, segment failures, child dispatch failures, child failures seen while observing, and report failures. See "Fixes after review" for how the report now carries the correlation ID.persist_failure_record, which logs a failed write instead of raising it. A failed simulation's own record is updated by the coordinator through_record_child_failure, which logs a failed read or write with the report and simulation identifiers, like the cancellation records.replace_simulation_if_statuswrites.Stage12ExecutionErrorafter being reported where they occur. The report handler therefore reports only other errors, so each failure is reported once.child_wait_timeout, before the cancellation records and the report record are written. A new test makes those writes fail and checks that the report still reached telemetry first._mark_child_failedis folded into this PR's_replace_running_simulation, which the dispatch, child-failure and cancellation paths share. It only changes a running record, which this PR requires.evaluation_idandsimulation_execution_id, and an unfinished child is then cancelled through Modal. They do not usepersist_failure_record, because the coordinator must know whether the record was written.Known open issue (fixed after review)
The coordinator read a failed child's record outside the failure-recording guard, so an error on that read replaced the child's failure. This is fixed in
b6c691d1; see "Fixes after review". #789's test for this case,test_coordinator_reports_a_child_failure_once_when_its_record_is_unwritable, is no longer markedxfailand passes.Connection retries and the finishing time
#789 retries a refused connection for up to 60 s. The last attempt can start just before 60 s and then wait up to the 10 s connect timeout, so one store operation can take about 70 s when the pooler refuses throughout. After the wait window closes:
If the coordinator ever exceeded 5,400 s, Modal would stop it and the report would stay running without a failure record. The timeouts are unchanged in this rebase.
Replaces #793 and covers #792
This PR replaces #793 and adopts its practices. Issue #792 asks for one shared wait deadline that fits inside the coordinator's timeout, and for every unfinished worker to be cancelled and recorded as failed. Observing baseline and reform concurrently was always part of this PR.
Wait window and timeouts
This PR follows #793's rule: the shared wait equals the workers' Modal timeout, and the coordinator's timeout is that wait plus 900 s for the production-result wait plus 600 s to finish.
coordinate_reportstarts. It covers claiming, output planning (including loading the country model), retained-artifact reads, dispatch, and the concurrent wait for both simulations. Setup time therefore shortens the simulations' window, not the time the coordinator needs afterwards.PRODUCTION_RESULT_WAIT_TIMEOUT_SECONDS);stage12_timeouts.py, defines the worker timeouts, the window and the finishing time, and derives the coordinator timeout from them. The Modal app declaration, the coordinator and the comparison step all read it.timeoutvalues ofrun_single_simulation_us,run_single_simulation_ukandcoordinate_reportchange. Their images,worker_secrets(the GCP, Hugging Face and comparison-runtime Modal secrets) and environment are unchanged. The new constants module ships in the existingpolicyengine_simulation_executorpackage source that every Stage 12 image already includes. No CI, Docker or Terraform changes.Cancellation
Recorded as failed. Every simulation the coordinator cancels is recorded as
failedwithsimulation_cancelled. Itserror_summarygives the reason:simulation_wait_timeout;The earlier
incompletestatus and thecancelled_after_peer_failureandcancelled_after_coordinator_failurecodes are gone. Nothing else needed them: workers treat any status other thanrunningas a cancellation, and the entry service passes statuses and codes through unchanged.Only running records change. A simulation that has already succeeded or failed keeps its own outcome.
Always stopped in Modal. An unsegmented simulation is always cancelled through Modal, even if its record could not be written. A segmented simulation reads its failed record, cancels its own region-group calls and exits. Modal does not cancel spawned calls recursively, so the coordinator cancels a segmented simulation through Modal only if it could not write the record.
Startup failures too. Any coordinator failure after the first spawn stops every started, unfinished simulation. Examples are a failed dispatch placeholder write, a failed invocation attach (including the simulation whose attach failed), a failed restore and a retained-artifact error. A call restored from an earlier coordinator attempt is stopped too. Before this update, only a failed spawn did this.
Errors never mask the original failure. Cancellation and state-update errors, including errors reading or writing the failed simulation's own record, are logged, and the report keeps the original failure.
Traceable logs. When recording or requesting a cancellation fails, the log includes
evaluation_idand the simulation'ssimulation_execution_id, in the message and as record attributes, alongside the role and error type. The error message itself is not logged. Share one Stage 12 child wait deadline and cancel unfinished children #793 used event-name messages with these fields only as attributes. This PR also puts them in the message, because the observability log handler exports only the message.Carried over from #793
fixtures.fake_modal, and the setup-time and exhausted-window tests.simulation_cancelledfailed records, the running-only guard, and Modal cancellation even when the state write fails.ImmediateCall.cancelnow counts cancellations.changelog_entry.yamlkeeps Release Stage 12 database sessions and keep original failures visible #789's line and this PR's line.call.gettimeouts, because this PR polls both simulations without blocking.Validation of the review fixes
uv sync --extra testenvironment: 1149 passed, 22 skipped, 3 deselected, 0 xfailed (was 1134 passed and 1 xfailed).tests/test_stage12_*.py): 213 passed.test_coordinator_reports_a_child_failure_once_when_its_record_is_unwritableno longer has itsxfailmarker and passes. Its expected report attributes now include the identifiers and the correlation ID.format --checkandcheckon the 10 changed Python files: clean. Ruffformat --checkon everyprojects/*/srcandlibs/*/srcdirectory: clean.git diff --check: clean.Merged main after #791
#791 stores a redacted failure cause: the type, the stage, the simulation role and the message. For every unexpected error, this PR stored a generic message with a correlation ID instead. Main is merged in as a merge commit rather than a rebase, because 16 of this PR's 25 commits touch the three files that conflicted. This PR keeps its own failure model and uses #791's cause within it:
redacted_failure_detailstores the redacted cause, followed by the correlation ID, for exampleOperationalError in stage12_child_state_create: ... (correlation_id=3f9c...). The cause is shortened, never the ID, to fit the 512-character summary. Typed input and SPM failures, failures a worker already recorded, and cancellations are unchanged.tracked_stage, so a failure names its stage. The coordinator waits on both simulations at once, so it records each one's role while checking it.simulation_invocation_failed, where Record a redacted Stage 12 failure cause instead of only the exception class #791 always usedreport_coordination_failed.format --checkon everysrcdirectory, andgit diff --checkall pass.Rebased onto main after #762, #763 and #795
.github/scripts/stage12-validate-v2-workers.sh, which conflicted with this PR's--cache-manifest-sha256argument. The script now takes the digest as a second required argument and passes it tovalidate_v2_worker_release. The workflow passessteps.stage12_cache.outputs.manifest_digest, the same pattern Move the deploy pipelines' multi-line steps into tested scripts #763 uses to pass step outputs to its other scripts.Build the independent Stage 12 cachestep (stage12-cache-precompute.sh), which runs earlier in the samedeploy_stage12_v2job and writesmanifest_digesttoGITHUB_OUTPUT.RUNNER_TEMPandGITHUB_OUTPUTcome from the runner.MODAL_TOKEN_IDandMODAL_TOKEN_SECRETcome from GitHub Actions secrets, unchanged.test_deploy_workflow_scripts.pychecks that the workflow passes the digest and that the script forwards it. A new test checks that the script fails, without callinguv, when the digest is missing.rules-registry,ci-scripts), pinned Ruff andbash -npass.Rebased onto main after #757 and #787
changelog_entry.yamlkeeps Accept null SPM provenance in Stage 12 aggregate artifacts #787's line and this PR's line.test_coordinator_records_bounded_timeout_without_an_aggregatecombines Accept null SPM provenance in Stage 12 aggregate artifacts #787's valid aggregate stub with this PR'swait_timeout_seconds=0.Stage12ArtifactStore. This PR's four aggregator stubs that must not run now return_aggregate_payload(...), as Accept null SPM provenance in Stage 12 aggregate artifacts #787 did for main's, so an unexpected run shows up as a written aggregate instead of as a rejection hidden by the failure the test expects.rules-registry,ci-scripts) pass.Rebased onto the rules registry (#752)
eef03c3); the only differences are main's changes and the registry entry below.rules-registryguard from Add a rules registry that makes every enforced and unenforced rule visible #752 flaggedtest_stage12_only_deployment_skips_existing_stack_integration_suite, which this PR adds, as a guard test no rule references. It is now one of the nodes ofsim.stage12.deployment-scope. The rule's statement now also says that a Stage 12-only deploy never runs the existing stack's complete integration suite.docs/generated/rules.mdand theAGENTS.mdRules block were re-rendered.python -m scripts.rules guard --base-ref origin/main,scripts/run_quality_guards.pyand the rules tool's own tests pass.tests/test_deployment_assets.py: 113 passed.Validation after rebasing onto #789
uv sync --extra test, plus--lockedto confirm the lockfile is current: PolicyEngine 6.3.4 (policyengine-us 2.2.1, policyengine-uk 2.126.12, policyengine-core 3.33.0).tests/test_stage12_*.py): 198 passed, 1 xfailed.Reconcile segmented Stage 12 execution with current typed provenanceonwards. The four commits before it fail one worker-validation test,test_us_validation_normalizes_the_frontend_reform_interval, exactly as they did before this rebase: main's reform-interval normaliser predates them, and that commit updates the test.format --checkandcheckon the 49 changed Python files: clean. Ruffformat --checkon everyprojects/*/srcandlibs/*/srcdirectory, as CI runs it: clean.git diff --check: clean.Validation of the cancellation and timeout update (before rebasing onto #789)
Full executor suite on Python 3.13 with
uv run --frozen: 809 passed, 23 skipped, 3 deselected. Before that update it was 786 passed.Stage 12 executor tests (
tests/test_stage12_*.py): 181 passed.tests/test_stage12_runtime.pygained 23 tests in that update, covering startup cancellation, cancellation records, Modal fallback, logs, and timing.Regression checks. I reverted each behaviour in turn, and each time the matching new tests failed:
The startup-cancellation, cancellation-log and coordinator-entry tests also failed against the head before that update.
Simulation entry suite: 162 passed. Stage 12 persistence suite: 14 passed. Their cancellation fixtures now use the failed
simulation_cancelledrecord.Scoped Pyright on the coordination, comparison and timeouts modules: 0 errors.
Prior validation before the earlier rebases
projects/policyengine-simulation-executor/tests/test_simulation_output_geographic.py, which this branch does not modify.testingenvironment, including US and UK worker validation and a cache-hit check for the 20-segment, three-year cache.invalid_policy_period, the baseline becameincompletewithcancelled_after_peer_failure(the behaviour at that commit; a cancelled baseline is nowfailedwithsimulation_cancelled), and the report failed with the original validation summary.Earlier testing-environment deployment note
A fresh UK dataset download was blocked because the configured Hugging Face credential could not access the private UK dataset. The testing deployment reused the previously validated UK and coordinator images from the same commit and rebuilt the US image layer with the validated Stage 12 cache.
Earlier current-main reconciliation
The executor keeps main's PolicyEngine 6.3.4 requirements and frozen lockfile; it does not restore older country packages.
Conflict resolution preserves main's output-planning instrumentation and UK metadata validation while retaining concurrent baseline/reform monitoring and segmented US calculation.
Segment merging accepts the current typed
SPMProvenancecontract. Every segment must supply the same complete receipt, including configuration, forecast hash, years and runtime versions; incomplete or inconsistent receipts fail. The merged logical simulation keeps one compact receipt, not a legacyspm_configobject or a growing receipt list. Five regressions cover preservation, missing receipts and mismatched scenario, runtime versions and years.The worker-validation unit test expects the complete reform date interval, matching main's normalizer rather than discarding its end date.
The earlier live results above describe an earlier commit on an older PolicyEngine version, not a fresh 6.3.4 qualification. No deployment has been run on .py 6.3.4 or any later version; the branch is now on 6.7.5.
🤖 Generated with Claude Code