Repository navigation
Conversation
…cy's (failing) A simulation names a dynamic as well as a policy. Its parameter values are read as reform values, with the policy reader's periods, and refused when the running catalog version lacks a parameter they set. A parameter both set is refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
read_dynamic_reform reads a dynamic's parameter values with the policy reader's periods, and refuses one setting parameters the running catalog version lacks (MissingPolicyParametersError, now naming its source). combine_reforms joins a simulation's policy and dynamic values, refusing a parameter both set (OverlappingReformError). The read-only models gain the dynamics table and parameter_values.dynamic_id. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…un to reuse (failing) The service reads the country's model, a region and a dataset of the catalog version, and creates a report, its new simulations and runs, and its first report run in one transaction, or a simulation and its first run; a foreign-key refusal names the column. A report side may name a run that already succeeded instead. It records a Modal call's ID, or fails the run and its new simulation runs, only while the run is pending with no call ID. A run is reused only when it is the newest succeeded run of the same simulation on the same versions, its file is in the output bucket, a succeeded report run with the same options used it, and no report run refused to reuse it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…to reuse economy_run_records reads the country's model and a catalog version's region and dataset, and creates in one transaction a report, its first run and each side's new simulation and pending run, or a standalone simulation and its first run. A report side may name a succeeded run instead (find_reusable_run): the newest of the same simulation on the same versions, with its file in the output bucket, used by a succeeded report run with the same options, and never refused for reuse. A foreign-key refusal is EconomyRecordReferenceError, naming the table and column. A Modal call's ID is recorded, or the run failed (with the report run's new simulation runs), only while the run is pending with no call ID. The read-only models gain the catalog's regions and datasets columns. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n (failing) The store reads a request's catalog version, model, region and dataset, each side's policy and dynamic values, and each side's reusable run in one transaction, never giving both sides the same run; it refuses each missing row with its own error. It creates a report or a simulation, records a call or fails an unstarted run, each in its own transaction. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…action EconomyRunStore reads a request's catalog version, model, region and dataset (the region's default unless one is named), each side's combined policy and dynamic values and, when asked, each side's reusable run, in one transaction; the reform never reuses the baseline's run. A missing region or dataset is UnknownRegionError or UnknownDatasetError. Creating a report or simulation, recording a call and failing an unstarted run each take their own transaction. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A report run's coordinator takes the report run's ID, with no evaluation record, claims that run under its Modal call ID and starts a worker per simulation run it doesn't reuse; each worker claims its own simulation run under its own call ID, and a simulation run can be calculated on its own. The comparison path keeps its evaluation records. The tests fail because the coordinator still requires an evaluation record, the worker still mirrors its evaluation child row, the run store lets any call take a running simulation run and has no release, and the contract has no worker context naming a simulation run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…start (failing) The service finds the default v2 worker's economy functions, refuses each missing row with its code before writing, creates the records and runs, then starts the run's call: the coordinator, given the report with its run IDs and no evaluation record, or a standalone run's simulation function, given the run's ID. It records the call's ID, or fails the run and answers 503 naming the record. Reports reuse a succeeded run only when the output bucket is configured. The routes answer 201, 400, 422, 503 with Retry-After, and a JSON 500, and the OpenAPI document declares each. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… /simulations The Simulation Entrypoint's new routes find the v2 version manifest's default worker's economy functions, read the request's catalog rows and stored values (economy_submissions), create the records and runs in one transaction, and start the run's Modal call by name: the report run's coordinator with the report and its run IDs, and no Stage 12 evaluation record, or a standalone simulation run's simulation function with its run's ID (economy_calculation_inputs, economy_workers). They record the call's ID, or fail the run, and its new simulation runs, and answer 503 naming the record. A report side reuses a succeeded run when V2_SIMULATION_OUTPUT_BUCKET names the output bucket. The routes answer 201, 400 with the Public API's caller-row codes or the service's own, 422, 503 with Retry-After and a JSON 500, never with an exception's text. The household routes' failure log helpers move, unchanged, to route_support, which both use. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…atus (failing)
GET /reports/{report_id} and GET /simulations/{simulation_id} answer the
record with its newest run, by creation and then the larger ID: its status,
versions, error message and times, and a report run's simulation runs. An
unknown ID answers 404 (unknown_report, unknown_simulation), a database that
cannot be read 503, and the OpenAPI document declares each answer. The
staging economy check polls the report this way.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ion tables A detailed-save call now works on the Public API's run tables alone and creates or claims no Stage 12 evaluation record: - coordinate_report takes the report run named in its context, with no evaluation record. It claims the report run under its Modal call ID, checks the report input names that run and its two simulation runs, reuses each succeeded simulation run's stored output, and starts a worker for each other one with a context naming that simulation run. - Each worker claims its own simulation run under its own Modal call ID, as the household functions claim their jobs, and completes or fails it only while its call holds it. A run that already succeeded returns its stored output's descriptor; one another call holds, or that failed, is not calculated. - A simulation run can be calculated on its own: a worker given a simulation with no output plan plans it as a report comparing it with itself. - A restarted coordinator stops each worker its pre-empted attempt started and releases that worker's run before starting a new one. A failed or cancelled coordinator fails its report run and the simulation runs it left unfinished, never a reused run. The comparison path's calls keep their evaluation records, and the shared planning, worker start, child wait and aggregation move into one core module both coordinators use. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ment Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
economy_run_reads returns a report, its two simulations and its newest run
(created last, ties to the larger ID), or a simulation and its newest run,
with each run's status, versions, error message and times. The store reads
each in its own transaction. GET /reports/{report_id} and
GET /simulations/{simulation_id} (permission simulation:read) answer it,
404 for an unknown ID, 503 when the database cannot be read, and the JSON
500 otherwise. Results stay the Public API's to read.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…(failing)
Every deploy names V2_SIMULATION_OUTPUT_BUCKET, from the environment's
GitHub variable, and stops without a valid bucket name. Every staging deploy
creates a small report, current law against current law for Wales in 2026,
through the candidate's POST /reports, follows its run through
GET /reports/{report_id} and requires it to succeed; deployment_ready
requires the check, so a failure stops the production deploy.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eport
The entrypoint's deploy passes V2_SIMULATION_OUTPUT_BUCKET, from the
environment's GitHub variable of that name, to Cloud Run on every deploy,
and stops without a valid bucket name. The new staging_economy_check job
creates a small report on every staging deploy, after the v2 default has
moved, through the candidate's POST /reports, follows its run through
GET /reports/{report_id} and requires it to succeed within 30 minutes
(simulation-staging-economy-check.sh). deployment_ready requires its result
(simulation-staging-economy-ready.sh), so a failure stops the production
deploy. The release environment count now includes the new job.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The generated client sends the Public API's report and simulation bodies, keeps omitted SPM settings omitted, and reads every answer the routes declare: the created records, each error with its code and any record, the 422 with its code, and both reads. Run against a client generated from main, the test fails to import create_report; against this branch's, it passes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The entrypoint's README describes POST /reports, POST /simulations and their reads, the reuse rule and every answer; DEPLOYMENT.md the output bucket setting and the staging economy check; the persistence README the economy records, the reuse search, the store and the dynamic reader. The changelog fragment announces the routes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lier worker (failing) A run that changes just before its release must stop the worker the coordinator already started for the other run. A worker an earlier attempt started, whose cancellation didn't reach it, may claim its run before the restarted coordinator's new worker does; the coordinator must then wait for that worker instead of failing the report. The release test now checks each earlier worker is cancelled just before its run is released, and the uncancellable-worker test that the earlier call's completion is refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rker holding its run When a restarted coordinator can't release a simulation run because it changed meanwhile, it now stops the workers it already started before failing the report, as any other failed start does. When a worker it starts finds its run held by an earlier attempt's worker that Modal never cancelled, the coordinator waits for that worker's call, which records the run, instead of failing the report and the other call's run. The child wait asks each coordinator how to read a finished child's result. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… review found The detailed-save worker's calculation seam is a Protocol with the keyword-only signature it is called with. The precompute planner imports the worker's runtime parameters from their own module, a worker's context check no longer calls every run a comparison run, the persistence README says a coordinator fails and releases its simulation runs, and the docs say a standalone call's artifact prefix must contain the run's ID and that a restarted coordinator waits for an uncancelled earlier worker. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t (failing) - A report's refused policy or dynamic names its side: the store raises ReportSideError, and a report's 422 locates each reason at baseline.<field> or reform.<field>. - A create whose commit can't be confirmed raises EconomyRecordOutcomeUnknownError, and the route answers 500, which tells the Public API the record may exist, never a 503 saying it doesn't. - Every refusal's detail is built from the request, never from a store error's text. - The Modal call's request ID is the request's own X-Request-ID. - The staging check asks again after a read that got no answer, gives each request a time limit, and its job one longer than its deadline. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e its release (failing) A US national simulation run started on its own takes the segmented path, so its segments must accept the standalone role. A run whose earlier worker completes it just before a restarted coordinator releases it is used as stored, and one it fails fails the report with its own cause. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r finished before its release A simulation run started on its own is segmented as a report's baseline or reform is, so a US national one no longer fails in the deployed worker. When a restarted coordinator's release of a simulation run is refused because the earlier worker finished it meanwhile, the coordinator uses its stored output, or fails the report with the run's own recorded cause, or waits for another call that now holds it, instead of failing with simulation_run_changed. The pre-empted comparison-run test now checks its children are left running. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… 503 after a possible write - A report side's refused policy or dynamic is raised as ReportSideError, and a report's 422 locates each reason at baseline.<field> or reform.<field> rather than at a field the body doesn't have. - A create whose commit fails after its rows were inserted raises EconomyRecordOutcomeUnknownError: the commit may have been applied. The route answers the JSON 500 (internal_error), which the Public API reads as a record that may exist, instead of a 503 saying nothing was created, and logs economy_record_outcome_unknown. - Every refusal's detail is written from the request and the worker, never from an error's text, since the Public API passes 4xx details on to its callers. - The Modal call's request_id is the request's X-Request-ID, not the observability ID, which travels in the observability context. - The staging check gives each request 30 seconds, asks again after a read that got no answer, and its job stops after 40 minutes. - Two tests of this branch follow the changes: a report read's policy and dynamic refusals now arrive with their side, and refusal details no longer hold the store's text. Docstrings now say a run claimed meanwhile is answered pending, and that a reused run may still be refused; the unused CALLER_ROW_REFUSAL_CODES is gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ed run (failing) A simulation run that already failed fails its report run with simulation_run_failed and the run's own recorded cause, whether the coordinator finds it so while preparing or at its release. A run used as stored at its release is never written when the report then fails. New tests cover a run another call holds at its release, which is waited for, and one left pending, which gets a new worker. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ced (failing) A request whose X-Request-ID is longer than the call's context takes, or holds characters outside the observability runtime's, gets a new UUID as the call's request ID instead of failing with 500. An unknown commit is logged once, with its error type, and the staging check's submission gets 60 seconds, its other requests 30. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eused at its release unwritten A report run whose simulation run already failed records simulation_run_failed with the run's own recorded failure, whether the coordinator finds it so while preparing or at its release. A run the coordinator reads as succeeded or failed at its release is kept in its run states, so a later failure of the report writes nothing to it, and reading its stored output is timed as any reused output's read. Docstrings, the test helper for a cancelled earlier worker and the migration doc's segmentation section now match the code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…carry The request middleware keeps the caller's X-Request-ID unchecked, and a Modal call's context takes at most 255 characters, so a longer header made every economy submission answer 500. The routes now pass the request's ID only when it fits the observability runtime's rule (1 to 128 of [A-Za-z0-9._:-]), and otherwise a new UUID. Also from round 2's findings: - An unknown commit is logged once, by the route, with its error type in the attributes the observability runtime keeps. - The staging check's submission, which isn't asked again, gets 60 seconds, Cloud Run's own limit; its other requests keep 30. - ReportSide and SideRefusal type a report side and its refusal, and the refusal helpers use them. - Docstrings: the created answers' status, submit_report's errors, the module docstring's caller-row codes, the persistence README's new errors and the staging script's comments. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nshortened (failing) A report run whose simulation run already failed records that run's own failure as it is, as when a worker fails during the report, so its correlation ID is never shortened away; a run that recorded none gives simulation_run_failed. A run found failed is not written again, and the test of a run reused at its release checks the report failed in its aggregation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ened A report run whose simulation run already failed, found so while preparing or at its release, now records the failure that run recorded, as when a worker fails during the report, so its correlation ID is never shortened away; only a run that recorded none gives simulation_run_failed. The run-state and reused-output docstrings now say exactly which reads they cover. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The call keeps a request ID of 128 characters and replaces one of 129, on both POST routes, and a request whose state holds no usable ID gets a new one (call_request_id_for). - Every error SideRefusal names is raised from a report read with its side. This passes before the change too: the change derives the caught errors from that type, so the two can't drift apart. - The staging check asks again after a 429 or any 5xx Cloud Run answers in the service's place, not only a 503. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- call_request_id_for, public now, is tested at the 128-character limit on both POST routes and with no usable state value. Its docstrings, and the routes' module docstring, name the real header, X-PolicyEngine-Request-Id, say a replaced ID matches none of the request's log records (the observability ID still joins them), and that a failure before a handler runs gets the middleware's plain-text 500. - The errors a report read wraps with their side are taken from SideRefusal itself, so the type and the caught errors can't name different errors. - The staging check asks again after a 429 or any 5xx, which Cloud Run's front end may answer in the service's place, as it does after a 503. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mment and the docs A report run repeats a 512-character recorded failure with its trailing correlation ID. The migration doc and the simulation_run_failed comment say that code is recorded only for a run that recorded no failure, and the test invoker records the calls it restores. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…on' into v2plan/c1-9-economy-simulations-reports Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dline message (failing) C1 · 8's contract now has Stage12InvocationContext.simulation_run_id: a standalone run's context validates with it set, and a report run's with it unset. These pass before the change too, which only builds the context through the model instead of adding the key to its JSON. The staging check's deadline failure says what the last read found: the run's status, the HTTP status, or no answer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t read at the deadline - With C1 · 8 merged, a standalone simulation run's context sets Stage12InvocationContext.simulation_run_id through the model, so the contract validates it (a call names a report run or a simulation run, not both), instead of adding the key to the dumped JSON. - The staging check's deadline failure ends with what the last read found: the run's status, the status a read answered, or no answer. - The routes' module docstring says a failure outside a handler's try gets the middleware's plain-text 500 (review round 4). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…oks it up The output planning both coordinators run moved into coordination_core, so the comparison coordinator's stage-measurement test replaces the planner there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 tasks done
…fore its status - test_tables' list of the read-only input tables gains dynamics, regions and datasets, which this branch models. - The generated-client test asserts the report's run is present before reading its status, as Pyright requires for an optional field. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A running run, then a front end answering 503 until a 2-second deadline: the failure names the 503. It passes before and after; review round 5 found nothing tested that a later read replaces an earlier one in the message. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
economy_calculation_inputs._context takes one runs argument, a report run's IDs or a standalone simulation run's ID, and builds both the context's run fields and its artifact prefix from it, so a prefix naming another run can't be built (review round 5). The existing prefix tests cover both calls, before and after. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ctly Review round 6's wording notes: the queued 503s are the service's own answer, and _context's runs is a report run's IDs or a standalone simulation run's ID. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…on' into v2plan/c1-9-economy-simulations-reports Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1049
Summary
The Simulation Entrypoint creates economy reports and standalone economy simulations in the Public API's run tables, and starts their calculation on the v2 coordinator and workers by run ID. It replaces the draft #974, which was built on tables that no longer exist.
POST /reports(permissionsimulation:submit) takes the body the Public API sends:country,report_type: "economy",label,description,year,region_code,dataset_name(null for the region's default), each side'spolicy_idanddynamic_id,include_cliffs, andspmonly when given. In one transaction it creates each side's new simulation and first simulation run, the report (options ininputs, the reform's policy inpolicy_id) and its first report run (initial, no budget window); every run ispendingwith no Modal call ID. It then starts the report run's coordinator by name with aReportExecutionInputwhoseevaluation_idis the report run and each side'ssimulation_execution_idits simulation run, and a context whosereport_runnames the three runs. No Stage 12 evaluation record is passed. It answers 201 withreport_id,report_run_id, the run's status and each side's simulation, run and whether it was reused.POST /simulationscreates a standalone economy simulation and its first run, and starts that run's simulation function by name with the run'sSimulationExecutionInputand a context namingsimulation_run_id. It answers 201 withsimulation_id,simulation_run_idand the status.gs://<V2_SIMULATION_OUTPUT_BUCKET>/, a succeeded report run with the same options used it, and no report run refused to reuse it (an error message startingreused_simulation_). So both of a report run's simulation runs share its dataset and region, and a submission after a refused reuse calculates the simulation again. The two sides never name the same run. Without the bucket setting, nothing is reused.missing_parameters,overlapping_parameters). A foreign-key violation (SQLSTATE 23503) on insert is answered with 400, naming the kind of row by its column.modal_call_refused: …, and the route answers 503 naming the record. Each update applies only while the run ispendingwith no call ID, so a run its coordinator already claimed is left alone and answered with 201.unknown_region,unknown_datasetorunknown_dynamicfor the caller's own rows, andunknown_catalog_version,unknown_policy,no_economy_worker,dataset_not_in_bundleormissing_referencefor the service's state; 422; 503 withRetry-After(not_configured,manifest_unavailable,database_unavailable, ormodal_call_refusedwithreport_id/simulation_id); 500internal_error. Nodetailholds an exception's text. The OpenAPI document declares every answer, and the regenerated client reads each.GET /reports/{report_id}andGET /simulations/{simulation_id}(simulation:read) return the record with its newest run's status, versions, error message and times. Results stay the Public API's to read.staging_economy_checkwhen the household check runs: it creates a small report (current law against current law forcountry/walesin 2026) through the candidate, follows it and requires its run to succeed within 30 minutes.deployment_readyrequires it, so a failure stops the production deploy.libs/policyengine-simulation-persistencegains read-only models of the catalog's regions and datasets columns, the dynamics table andparameter_values.dynamic_id; a dynamic reader beside the policy reader; the economy records, reuse search and reads; andEconomyRunStore, each step in a transaction of its own. Typed SQLAlchemy Core only; no DDL.route_support, which both route modules use.Builds on, and what merges first
56b78135and again at3cb8d1fb: the coordinator takes a report run's ID and claims it, each worker claims its own simulation run, and a worker can calculate a standalone simulation run. Coordinate economy runs through the run tables instead of the evaluation records #1051 merges first; this PR then mergesmainagain, and its diff shrinks to its own changes. Before Coordinate economy runs through the run tables instead of the evaluation records #1051 is deployed, a deployed coordinator would refuse these calls (it requires an evaluation record, and its context model refusessimulation_run_id), and each run would be failed by the stuck-run sweep; the staging economy check is what would catch that.main, which this branch starts from.Environment variables and secrets
V2_SIMULATION_OUTPUT_BUCKET(new for the service)V2_SIMULATION_OUTPUT_BUCKETinbetaandproddeploy_entrypointjob (environmentrelease_environment) → deploy stepenv: V2_SIMULATION_OUTPUT_BUCKET_VALUE→deploy-cloud-run-simulation-entry.shvalidates it and writes it to--env-vars-fileSIMULATION_SERVICE_DATABASE_URLSIMULATION_SERVICE_DATABASE_URL_SECRET_NAME--set-secrets/readypasses. The role's grants cover every table and column written here (B1 · 1).MODAL_TOKEN_ID,MODAL_TOKEN_SECRETMODAL_TOKEN_ID_SECRET_NAME,MODAL_TOKEN_SECRET_SECRET_NAME--set-secretson every deploySIMULATION_V2_MODAL_ENVIRONMENT,SIMULATION_V2_MANIFEST_NAME,APP_ENVIRONMENTSIMULATION_ENTRYPOINT_AUTH_ISSUER,SIMULATION_ENTRYPOINT_AUTH_AUDIENCE,SIMULATION_ENTRYPOINT_TEST_AUTH_CLIENT_ID,SIMULATION_ENTRYPOINT_TEST_AUTH_CLIENT_SECRETbetastaging_economy_checkjob,environment: release_environment, stepenv:POST /reportsandGET /reports/{id}.The coordinator and workers' own settings (
policyengine-modal-worker-database,V2_SIMULATION_OUTPUT_BUCKETin the Stage 12 runtime secret) are unchanged.Tests
Each change was a failing test first, committed with "(failing)" in its subject and seen failing for the expected reason (missing names, routes, jobs or scripts, or assertions), then the change that makes it pass. One commit's test passed before its change too, because the change only derives the caught errors from a type; its message says so.
Run by node ID, only the tests this PR adds or changes:
libs/policyengine-simulation-persistence: 41 test functions, 59 cases (plus the changed input-tables case intest_tables.py), intest_economy_run_records.py,test_economy_run_reuse.py,test_economy_run_store.py,test_economy_run_reads.pyandtest_dynamic_reform.py, against a PostgreSQL 17 database built by the Public API's v2 migration chain atmastercd513daccwith its privileges applied and verified, acting as the simulation service's role.projects/policyengine-simulation-entry: 92 test functions, 168 cases: the newtest_economy_*.pyandtest_staging_economy_check.pyfiles, and the changed cases oftest_openapi.py(route table, the old gateway's contract, the new routes' answers),test_config.pyandtest_deployment_assets.py(the release environment count).projects/policyengine-apis-integ:test_economy_generated_client.py, 8 cases, against the client regenerated from this branch with./scripts/generate-clients.sh. Against a client generated frommainit fails to importcreate_report.Before each commit: CI's pinned Ruff (format and lint), the rules registry render, the quality guards (shellcheck, file length, locked installs),
uv lock --check, and Pyright for both changed packages. No other tests were run here; CI runs the rest.Review
Four read-only adversarial rounds (the review-code format):
policy_idrather thanbaseline.policy_id/reform.policy_id; a create whose commit failed after its inserts answered a 503 naming nothing, which the Public API reads as nothing created. Both fixed test-first, with the Public API's answer rules: every refusal'sdetailis written from the request, never from an error's text.[A-Za-z0-9._:-]), else a new UUID.simulation_run_idfield, and the deadline message): no bugs; the calls match the merged coordinator and workers exactly.CI
The first run failed two jobs, fixed test-only in
31c4c7f8:test_tables' list of input tables lacked the three this PR models, and Pyright refused an optional field read in the generated-client test. A later run failed one executor test that #1051 had moved code under; #1051 fixed it, and3cb8d1fbmerges that fix. Every check passes on3cb8d1fb.Not done or not verified
country/walesmust be in the staging catalog for the default worker's .py version; not verified.ON DELETE SET NULL) as current law, though the Public API deletes no policies;REFUSED_REUSE_PREFIXcopies the coordinator'sreused_simulation_error codes with no test tying the two. The household path (Calculate stored households as household jobs through /household-calculations #1046) still answers a create whose commit failed after its insert with a 503; a separate change.🤖 Generated with Claude Code