Repository navigation
Conversation
The tests store a US and a UK economy result through a port of the simulation service's writer, then expect the rows to give the same result back, in any row order, and expect a typed error when a part every result has is missing. The rebuild itself is a stub. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
economy_results.transformations reverses the simulation service's writer part by part, giving the single-year result format the app reads today: the budget, programs, deciles, poverty, inequality, labour supply, congressional districts, UK areas with their disclosure fields, cliffs, the SPM receipt and the data version. An optional part with no rows is null; a missing required part raises EconomyResultUnreadableError. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The integration tests store US and UK reports, runs and results as the simulation service writes them and read them through the routes as the runtime role: the newest run and its result, a running or failed newest run, a report with no run, other countries, an unreadable result, a household report, lists in the order asked, and simulations. The unit tests check the routes' answers over stand-in services. The services and routes do not exist yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tables
GET /v2/reports/{id} reads the report in its country with its newest run,
whatever that run's status and versions, the versions the Public API runs
now, and the run's result once it succeeded, rebuilt from the result rows.
GET /v2/reports lists up to 25 named economy reports and their newest runs
in one query, in the order named, without results. GET
/v2/simulations/{id} reads a simulation of the country's model and its
newest run. A report with no run answers its definition; household reports
and budget windows are refused with 422 until their own routes exist. A
failed database read is a typed storage error, answered with 503.
The read test's expected result takes its model version from the run, as
the simulation service's writer stores it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…(failing) The tests define the requests the Public API sends to the simulation service's POST /simulations and POST /reports, and how each of its answers is read: the created IDs, refusals of the caller's data with their reasons, an internal error, an unreadable created answer, and every other answer or no answer as the service being unavailable. The client does not exist yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SimulationServiceReportClient posts to the Cloud Run simulation service's POST /simulations and POST /reports with the same HTTP client, gateway credentials and request headers as every other simulation call, and reads the created IDs, refusals with their reasons, internal errors, unreadable created answers and unavailability. post_simulation and post_report are the only code that sends a request, so the generated client replaces just them once released. The SPM settings test now expects the whole selection, as the household calculation client sends it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n checks (failing) The tests expect a household-only country, a country without a countries row and a policy the country does not store to be refused before the simulation service is asked; POST /v2/reports and POST /v2/simulations to answer the created record as the tables hold it, with 201; malformed bodies, another country and oversized bodies to be refused; each failure of the simulation service to get its own answer; and a created report the Public API cannot read back to be unavailable. The submission service and the POST routes do not exist yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
POST /v2/reports and POST /v2/simulations go through SimulationSubmissionService, which refuses a country without economy calculations (422) and a policy the country does not store (404) before the simulation service is asked, then sends the request and reads the created record back from the tables (201). Report options are include_cliffs and the SPM choice; no request names a PolicyEngine.py version. The simulation service's refusals answer 422 with its reasons, its internal errors and unreadable created answers 500, and its unavailability 503, as does a created record the Public API cannot read back. C1 · 9's missing-parameter refusal belongs in the same submission checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The tests expect a saved report to name a stored report of its country by report_id, and a saved simulation a stored simulation of its country's model by simulation_id: a missing or foreign one is not found (404), checked with a SELECT in the write's transaction and, if it goes between the check and the write, by the RESTRICT foreign key (SQLSTATE 23503). A request naming both a detailed-save ID and a v1 number, or one that would leave a saved record naming both, is refused (400). The references are not accepted yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
user_report_associations.report_id and user_simulation_associations .simulation_id now take a detailed-save ID on create and update: a report stored in the country, or a simulation of the country's model, checked with a SELECT in the write's own transaction (404 otherwise). A record deleted between the check and the write is refused by B1 · 11's RESTRICT foreign key, whose SQLSTATE 23503 on that key is answered as not found too. A request naming both a detailed-save ID and a v1 number, or an update that would leave a saved record naming both, is refused with 400; only the production migration writes saved records holding both. The shared report read now carries report_id. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ntracts (failing) The tests expect /v2/reports and /v2/simulations to be logged as reading Supabase and, on create, computing on the Cloud Run simulation service, whatever the route groups' flags select; a detailed_save_reports_v2 workflow contract; and the new integration tests in the cross-database integration workflow. The OpenAPI path list now includes the four new paths the read and create commits added. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
/v2/reports joins the report route group. Requests to /v2/reports and /v2/simulations are logged as reading Supabase, and on create as computing on the Cloud Run simulation service, without reading the route groups' database, entrypoint or compute flags. The detailed_save_reports_v2 workflow contract lists their five requests, the saved-record contracts gain report_id and simulation_id, the generated migration contracts are regenerated, and the cross-database integration workflow runs the three new integration test files. Adds the changelog fragment and the Stage 12A documentation of saved detailed-save records. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… route tests The saved-simulation route test expected a simulation_id to be refused, and the saved-report payload test refused a body naming a report_id and a legacy_report_id at validation. Saved records now take a detailed-save ID, and a body naming both is refused by the service with 400, which the service tests cover. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…wer 502 (failing) A submission that may have reached the simulation service must not be answered 503, which invites the caller to submit again and create a second record. Only a connection never made and a gateway token that cannot be fetched stay unavailable. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A connection never made and a gateway token that cannot be fetched stay 503. An answer lost after sending, a proxy's 502 or 504, the service's own 500 and a created answer that cannot be read are now 502, as for household calculations, since the service may have created the report or simulation and started its run, and a retry would create a second one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…its ID (failing) The simulation service has answered that it created the report or simulation, so the record exists: a 503 invites a second submission and loses the ID the caller needs to read it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Once the simulation service has answered with a report's or simulation's ID, the record exists. Any failure to read it back (not stored in the country, the database unavailable, or another read failure) now answers 502 with report_id or simulation_id and a message to read it with the GET route, instead of a 503 that dropped the ID and invited a second submission. The OpenAPI document gives the 502's two shapes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lier one (failing) The shared submission mappings already answer a missing v2 database configuration with 503 "V2 storage is unavailable", so each route's own V2ConfigurationError entry is never used. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ppings SUBMISSION_ERROR_MAPPINGS, which both routes list first, already answers V2ConfigurationError with 503 "V2 storage is unavailable"; each route's own mapping now covers only its storage error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An update points a saved simulation at another simulation of its country through the reference it already holds, and refuses one that would leave both references set; nothing compares the simulations' economy or household kind, and the plan asks for no such check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the newest Covers the ID tiebreak for a report's read and list and for a simulation's read. The larger ID is stored first, so insertion order cannot pass the tests; reversing the tiebreak makes both fail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd typed SPM receipts (failing) scripts/generate_economy_result_golden.py runs report_run_result from PolicyEngine/policyengine-sim-api at a named commit, read with git show, on the US and UK fixture results and on variants with labour supply deciles -1 and 0, DC and Wyoming at-large districts and a negative budget. tests/fixtures/economy_result_rows_golden.json holds what it stored at cb1264a5 (writer last changed in c02145fd); each case rebuilds exactly. The two SPM tests fail: a stored receipt is still read as a plain dict, so one of another shape is not refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The receipts sim's writer stores fit the Public API's comparison type, which the v1 economy output already validates them with. Receipts of another shape, such as a baseline and reform that differ, now make the result unreadable instead of passing through. Answers keep the package names as runtime version keys, as the Public API serves them elsewhere. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mypy reads .py as untyped; importing the module by name keeps the script clean under an explicit mypy run without a suppression. Generating again from cb1264a5 writes the same fixture. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d created record (failing) A 400 answers 422 only for the codes naming the caller's region, dataset or dynamic, with the service's code and reason; any other 400 answers 503. A 503 passes on the service's code and reason, and a 503 naming the record the service created returns its report_id or simulation_id. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mulation service's code, reason and created record The simulation service also uses 400 for its own states, such as a catalog version missing while a release deploys. A 400 now answers 422 only with a code in CALLER_ROW_REFUSAL_CODES (unknown_region, unknown_dataset, unknown_dynamic); any other 400 answers 503. Refusals and the service's 503 give its code and reason. A 503 naming the record the service created and whose run it failed answers 503 with the report_id or simulation_id (UnavailableAfterReportCreatedResponse, UnavailableAfterSimulationCreatedResponse), logged as v2_report_unavailable_after_creation or v2_simulation_unavailable_after_creation. SimulationServiceUnavailableError carries created_record_id, and its docstring no longer says nothing was created. The changelog says exactly when a create answers 422, 502 and 503. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t lost answers ask to submit again (failing) A 503 that cannot be read, one whose report_id or simulation_id is not a UUID, and one naming only another record's ID may follow a record the simulation service created, so each should answer 502, not the 503 that says nothing was created. Every 502 for an answer that may follow a created record should ask the caller to submit again and say that this creates a second record if the first request went through. The route tests send each of these answers through the real SimulationServiceReportClient, over a mock transport, to the route's JSON, as they do the two 503s that keep their answer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mit lost answers again A 503 the Public API cannot read (such as Cloud Run's own, sent in the simulation service's place), one whose report_id or simulation_id is not a UUID, and one naming only a record other than the submission's own may follow a record the simulation service created. Each now raises SimulationServiceAnswerUnreadableError, which the create routes answer with 502. A readable 503 naming the submission's own record keeps its 503 with the record's ID, and one naming no record keeps its plain 503, which now means the service created nothing. The Public API will not choose record IDs, and no route finds a record by what it was submitted with, so the 502s for a lost answer, a proxy's 502 or 504, these 503s and the service's 500 no longer ask the caller to check for the record. They ask the caller to submit again, and say this creates a second record if the first request went through, keeping each case's detail: the transport error, the proxy's status, or the service's status, code and reason. The OpenAPI 502 and 503 entries, the error types' docstrings, the error mapping comment and the changelog fragment say the same. The route test of hand-built errors uses messages the client now gives. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…on only their code (failing) A status the client doesn't expect may follow rows the simulation service wrote, so it should answer 502; only 401, 403, 404, 405, 413 and 429, sent before the service's route runs, keep the plain 503. The messages for the service's 500 and 503 give its status and code but never its reason, and say the service may have created the record and failed. Each message writes the status and code as "503 modal_call_refused". The route tests drive the 500 without a code, the unreadable 500, the 503 without detail and a 503 naming both records through the real client, and the expected messages come from one shared test helper. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…0 or 503 The simulation service's route may write rows before answering a status the client doesn't expect, such as 200, 204 or 409, so each now answers the 502 that asks the caller to submit again. Only 401, 403, 404, 405, 413 and 429, which the gateway, Cloud Run or the service's framework sends before the route runs, keep the plain 503. The messages for the service's 500 and 503 give its status and code but never its detail, which may be an exception's text holding stored data. A 400's or 422's reason, which describes the caller's own request, is still passed on. The 500, the 503s answered 502 and the unexpected statuses say the service "may have created the report, but it failed (500 internal_error)"; a lost answer or a proxy's 502 or 504 still says the service "may have received the request, but its answer was lost". Every message writes the status and code as "503 modal_call_refused". The OpenAPI entries, docstrings and changelog fragment say so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… app The simulation service client's tests imported the route test support, and through it the app factory, only for two message helpers. Both test modules now import them from tests/unit/v2/simulation_service_messages.py. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…er, and that refusals give no status (failing) A 500's or 503's message leaves out a code that isn't lower-case letters, digits and underscores starting with a letter, at most 64 characters, such as exception text or an empty code. A 400's or 422's message gives the simulation service's code and reason as the household calculation client does, without the service's status. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…fusals the code and reason A 500's, 503's or other status's message now gives the simulation service's code only when it is lower-case letters, digits and underscores, starting with a letter, at most 64 characters, so a code holding exception text or an empty code reads like a missing one: "(503)", never "(503 )". Only the message is checked; a 400 or 422 with such a code still takes its own path. A readable 400's or 422's message is again "The simulation service cannot run this request (code): reason" for the caller's error and "The simulation service refused the report (code): reason" (or "simulation") for the service's state, as the household calculation client writes its refusals, without the simulation service's status inside the Public API's 422 or 503. The docstrings, the 422 and 503 OpenAPI descriptions and the changelog fragment say so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eadable-answer error covers The 503 answer naming a created record gives the simulation service's status and code, not its reason. The unavailable error says a readable 400's reason describes the service's state, not the caller's request. SimulationServiceAnswerUnreadableError keeps its name but now says it also covers readable answers that leave open whether the record exists: a 503 naming only another record and any status the Public API does not expect. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Nov 11, 2026
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #4281 +/- ##
==========================================
+ Coverage 92.69% 92.95% +0.26%
==========================================
Files 329 356 +27
Lines 18783 20097 +1314
Branches 3114 3222 +108
==========================================
+ Hits 17410 18682 +1272
- Misses 818 844 +26
- Partials 555 571 +16
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…ions-and-reports Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ey as not found (failing) The saved-report and saved-simulation inserts and updates should turn PostgreSQL's foreign-key refusal (SQLSTATE 23503) on the record's own key into the not-found error, and raise any other integrity error unchanged. The four not-found cases fail: the write raises the IntegrityError. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The saved-report and saved-simulation services imported SQLAlchemy to catch the foreign-key refusal of a write, which the rule api.v2.user-services-no-sql forbids. The insert and update connectors now turn SQLSTATE 23503 on the report's or simulation's own key into the not-found error, and raise any other integrity error unchanged. The services keep the check with a SELECT in the write's own transaction. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The report and simulation create routes' error responses that name the created record subclass V2ErrorResponse, so ruff must treat their annotations as read at runtime. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ulations, reports and simulations The household calculation client and the report and simulation client each kept their own copy of the answer and refusal models, the refusal's reason, the status and code a message gives, the proxy statuses, the transport-error sorting and the HTTP client's construction. They now share policyengine_api.libs.simulation_service_answers, and both answer every status as before. The simulation service's error types now live once, in services/v2/simulation_service/types.py, which both features import. SimulationServiceRefusalError becomes SimulationServiceInvalidRequestError, the household calculations' name for the same 422. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ointed is not found (failing) On PostgreSQL, repointing a saved report or simulation to a record deleted after the service's check raises InvalidRequestError: the update connectors read the row's attributes after the failed flush, which SQLAlchemy has expired. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…efore the flush A failed flush expires the rows the session holds, so the update connectors' reads of the row after it raised InvalidRequestError, which answered 503, in place of the not-found error. The connectors now read the report or simulation and the country before the flush, and the inserts name the input's values. Also say in the shared invalid-request error's docstring that a report's or simulation's message gives the service's code, and in the shared answer module's that each client keeps its own refusal messages. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…fused repoint keeps the saved simulation Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Alembic's env.py calls logging.config.fileConfig, which disables every logger that already exists. When an Alembic test runs before these two tests in the same process, the report and simulation routes' logger is disabled and caplog records nothing, so both tests failed in shard 5. A fixture now re-enables that logger for each of the two tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Nov 12, 2026
Draft
Brings in #4283 (admin user profiles from Auth0). The one conflict, in fastapi_routes/dependencies.py, keeps both imports: PackageVersions for the report and simulation services and UserProfileSource for the user type service. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in the release commit after #4283 (version, changelog and uv.lock). No conflicts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch was successfully 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 #4280
Summary
Adds
/v2/reports(economy kind) and/v2/simulations. Reads come from the detailed-save tables, and creates go through the simulation service. Replaces #4108, whose reads through the simulation service, lookup by v1 report number and first-read runs no longer fit the design.Reads need
country_idand no user credential. They never call the simulation service or start a run.GET /v2/reports/{report_id}returns the report within itsreports.country, with its newest run: the one created last, with the larger ID winning a tie, whatever its status and versions. This deliberately differs from picking the newest succeeded run on the deployed versions, which would hide the out-of-date run a client needs to see.options),status(not_runor the run's),run(status, trigger, both package versions, simulation run IDs, error, times),current_versionsandresult.resultis filled only once the newest run has succeeded.GET /v2/reports?report_id=…lists 1 to 25 reports in one SQL statement, in the order named, each once, without results. Unknown reports, other countries' reports and reports of other kinds are left out.GET /v2/simulations/{simulation_id}returns the simulation and its newest run, withcurrent_versions. Its country comes from its catalog version's model.The result rebuild (
services/v2/stored_reports/economy_results/) reverses the simulation service's writer part by part intoSingleYearMacroOutput:SPMComparisonProvenance) and the data and model versions.The UK local-area breakdown is left out until its storage is decided. A golden fixture,
tests/fixtures/economy_result_rows_golden.json, was produced by running the simulation service's real writer (scripts/generate_economy_result_golden.py, at the simulation service's commitcb1264a5). It covers the US, the UK, labour supply deciles −1 and 0, at-large districts and a negative budget, and a test rebuilds it exactly.Creates.
POST /v2/reportsandPOST /v2/simulationstakecountry_idin the query and the body, refuse bodies over 16 KiB, and answer 201 with the record read back from the tables.kind: "economy",label,description,year,region_code, an optionaldataset_name,baselineandreform(each an optionalpolicy_idanddynamic_id),include_cliffsand an optionalspm.kind,year,region_code, an optionaldataset_name,policy_idanddynamic_id.Answers from the simulation service. No answer that invites a resubmission follows a record the simulation service may have created, unless it says so.
report_idorsimulation_id, saying to read it rather than submit again.unknown_region,unknown_datasetorunknown_dynamicFor sim's 500 and 503, only the status and sim's
codereach the caller, never sim's error text.Saved records. Saved reports and saved simulations accept a detailed-save
report_idorsimulation_id, one reference per request. A request naming both a v1 number and a detailed-save ID gets 400, as does an update that would leave a row holding both. The reference is checked with aSELECTin the write's own transaction. A foreign-key violation (SQLSTATE 23503) on those keys, from a record deleted after that check, answers 404; the insert and update connectors turn it into the not-found error, so the services import no SQLAlchemy. The shared report answer now includesreport_id.The call to the simulation service sits behind the
SimulationServiceClientprotocol. The adapter,policyengine_api/libs/simulation_reports.py, reusesSimulationEntrypointClient's HTTP client, so the gateway credentials and the request and observability ID headers come from there. Wiring the released generated client is one change: addpolicyengine-api-simulation-client>=<the release with these routes>, relock, and replacepost_reportandpost_simulationwith the generatedcreate_report.sync_detailed(...)andcreate_simulation.sync_detailed(...), as their docstrings show.Shared with household calculations (#4274). The adapter and the household calculation client now share
policyengine_api/libs/simulation_service_answers.py, so neither keeps its own copy of these:The
SimulationService*Errortypes are defined once, inservices/v2/simulation_service/types.py. The 422 error isSimulationServiceInvalidRequestError, the household calculations' name. Both routes answer exactly as before.Merge order
The simulation service's
POST /reportsandPOST /simulationsdon't exist yet; that work is separate. Until they are deployed, both creates answer 503, while the reads work as soon as this merges. The adapter,policyengine_api/libs/simulation_reports.py, defines the request and answer shapes those routes must use, including the three 400 codes inCALLER_ROW_REFUSAL_CODES.Tests
Each test was run individually, by node ID, and seen failing for the expected reason before its change.
Unit tests: the result rebuild, the golden fixture, newest-run selection with equal creation times, the list query, country checks, the create services, saved-record checks and the route answers. Every simulation-service answer in the table goes through the real HTTP client over a mock transport to the route's JSON.
Integration tests on PostgreSQL 17, as the runtime role: US and UK rows in all 11 result tables, including UK disclosure fields and their nulls, and the saved-record foreign keys. They run in two existing
v2-integration-check.ymlsteps.Checks:
alembic -c alembic-v2.ini check(no operations),lint-imports, the rules registry, the quality guards, the migration contract export (regenerated), ruff, ruff format and mypy.Review: three rounds, and a follow-up of the fixes.
masterwith Calculate stored households through /v2/household-calculations #4274, one round on the shared code and two CI fixes. A saved record repointed to a record deleted after the check answered 503: the update connector read its row after the failed flush had expired it.All are fixed test-first.
Environment variables and secrets
No new ones.
SIMULATION_ENTRYPOINT_URLstagingandproductionenv:inpush.yml, thendeploy_cloud_run_candidate.sh, then the Cloud Run revision. Unchanged.GATEWAY_AUTH_ISSUER,GATEWAY_AUTH_AUDIENCE,GATEWAY_AUTH_CLIENT_ID,GATEWAY_AUTH_CLIENT_SECRET_RESOURCEenv:, then the deploy script and Cloud Run. Unchanged.V2_RUNTIME_DATABASE_URL_SECRET_RESOURCENot verified:
SIMULATION_ENTRYPOINT_URLnames a simulation service with the new routes;🤖 Generated with Claude Code