Repository navigation
Compare the v2 manifest's /versions answers on staging deploys, and answer /ping in the entrypoint - #934
Draft
anth-volk wants to merge 7 commits into
Draft
Compare the v2 manifest's /versions answers on staging deploys, and answer /ping in the entrypoint#934anth-volk wants to merge 7 commits into
anth-volk wants to merge 7 commits into
Conversation
The old gateway's /ping returns the request's value plus one. The entrypoint now gives that same answer itself, with the same request validation, status code and response body, so the route no longer depends on the old gateway being reachable. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add a v2-side module that builds the GET /versions and
GET /versions/{kind} answers, in the old gateway's response models, from
the v2 version manifest instead of v1's routing state. It shares no code
with the routes that forward to the old gateway.
The manifest names v2 worker applications, routes a model version that
several bundles contain to the newest of them, takes latest from its
default version, and records no SPM capability, so spm_capabilities is
empty. The tests pin each of these and check the answer against the
shape of the gateway's recorded answer.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GET /internal/v2/versions and GET /internal/v2/versions/{kind} give the
answers /versions would give from the v2 version manifest, read from the
Modal Dict the deploy publishes it to. /versions itself still forwards
to the old gateway, so a staging deploy can compare the two before the
public routes change.
The routes are public like /versions and left out of OpenAPI. An
unknown kind is a 404 with the old gateway's detail; a manifest that is
not configured, cannot be read or takes over 5 seconds gives a 503.
Reads run on the source's own thread, so a stalled read does not hold
threads of the default executor the Stage 12 path uses.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The staging deploy now runs .github/scripts/compare_versions_sources.py
against its candidate revision after the smoke check. It reads
/versions, /versions/{kind} and an unknown kind from both the old
gateway (through the entrypoint's forwarding routes) and the v2
manifest (through /internal/v2/versions), and writes every difference
to the job summary.
Routes are compared by the PolicyEngine version each application
serves, because the gateway names v1 applications and the manifest v2
ones. The report is for information only and exits 0 whatever it finds,
since the two sources are known to differ until the manifest records
everything the gateway reports; only a missing base URL is an error.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The route tests imported their manifest builders from another test module. They now come from tests/v2_versions_support.py, which both test modules use, as the testing guidance asks for shared helpers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The per-kind comparison read any body that was not a JSON object as an empty route map, so two such answers counted as a match. Every answer the comparison reads must now be HTTP 200 with a JSON object; any other body is listed as a body difference and is not compared further. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
create_app chose the source with "or", so an injected source that evaluated as false was silently replaced by the one built from the settings. It now builds a source from the settings only when none is injected. 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 #931
Same-Repository Draft PR Check
PolicyEngine/policyengine-sim-api, not a personal fork.Summary
This is the comparison step only. The public
GET /versionsandGET /versions/{kind}routes still forward to the old gateway, and their answers do not change. Switching those routes to the v2 version manifest is a separate, later change, made after the Public API's v1 economy and budget-window routes are removed (see "Why/versionsdoes not switch here").POST /pingis answered by the Simulation Entrypoint itself: the request'svalueplus one asincremented, with the same request validation and status codes as before. The old gateway is no longer called for it, so/pinganswers even when the old gateway is unreachable.policyengine_simulation_entry.v2_versionsbuilds the/versionsand/versions/{kind}answers from the v2 version manifest, in the old gateway's response models (VersionsResponse,VersionMap,PolicyEngineBundle). It shares no code with the forwarding routes.latestis the manifest's default version; each country'slatestis that bundle's model version.spm_capabilitiesis empty, because the manifest records no SPM capability.GET /internal/v2/versionsandGET /internal/v2/versions/{kind}serve those answers. They are public like/versions, left out of the OpenAPI document, and deployed to both environments.Unknown version kind: …).Retry-After: 10..github/scripts/compare_versions_sources.pyagainst its candidate revision, right after the smoke check. It reads/versions, each/versions/{kind}and an unknown kind from both sources, and writes every difference to the job summary.Why
/versionsdoes not switch hereThe Public API reads
/versionswhile it serves requests, not only in its release checks./versions/{country}or/versions/policyengine. They put that name into the key they store results under, and they recalculate a stored result whenever the name differs from theresolved_app_namethe old gateway reports. The manifest names v2 applications (policyengine-simulation-v2-py…) while the old gateway reports v1 ones (policyengine-simulation-py…), so switching while those routes exist would recalculate on every read. The switch therefore comes in a later change, after those routes are removed.spm_capabilitiesto validate US SPM selections, and its release check (--check-installed-spm) requires a capability for the bundle it ships. The v2 manifest records none today. It will record them as part of the later switch, not in this PR./versionsanswers for them is decided with the later switch, once request logs show whether anything still asks for them.The same comparison, run locally on answers recorded from both environments, found:
Choices still open for review
These choices were made in this PR and are open for a reviewer to change:
/pingis answered by the entrypoint without a staging comparison. Its answer does not come from the manifest. Unit tests and the existing deploy smoke check (incremented == 2) show it gives the old gateway's answer.Before this PR may merge
Environment variables
No new variables. The internal routes use values the entrypoint revision already receives whenever
deploy_stage12_v2is true, which it is for bothbetaandprod:STAGE12_V2_MANIFEST_NAMEsimulation-api-v2-version-manifestindeploy-cloud-run-simulation-entry.sh--env-vars-fileforgcloud run deploy, so it is a runtime environment variable on the revisionSTAGE12_V2_MANIFEST_ENVIRONMENTmodal_environment(stagingormain)env:MODAL_ENVIRONMENTindeploy_entrypoint, then the deploy script's--env-vars-fileMODAL_TOKEN_ID,MODAL_TOKEN_SECRETbeta/prodGitHub environment variablesMODAL_TOKEN_ID_SECRET_NAMEandMODAL_TOKEN_SECRET_SECRET_NAMEenv:indeploy_entrypoint, then the deploy script pins each secret's newest enabled version and mounts it with--set-secrets; the Modal client reads both from the environmentCANDIDATE_URL(comparison step)deploy_entrypoint.outputs.candidate_urlenv:in theintegrationjob, passed to the script as its argumentGITHUB_STEP_SUMMARY(comparison step)Not verified: that the runtime's Modal token can read the manifest Dict in each environment. The Stage 12 code in the same revision already reads that Dict with the same token, but the read was not tested for this PR.
Testing
Run locally on every commit; all passed:
uv tool run ruff@0.15.22 format --checkanduv tool run ruff@0.15.22 check .python scripts/run_quality_guards.pypython -m scripts.rules pyright --sync projects/policyengine-simulation-entryuv run --frozen pytest tests/inprojects/policyengine-simulation-entry: 809 passed on the branch headtest_modal_scripts.py,test_update_v2_version_manifest.py,test_update_version_registry.py): 347 passedNew tests:
test_v2_versions.py: the manifest's answers, including a check against the shape of the gateway's recorded answer.test_v2_versions_routes.py: the internal routes and the manifest source.test_compare_versions_sources.py: the comparison logic, whole runs against a local HTTP server, and the workflow step.test_app.py:/ping.The comparison was also run end to end against the app on 127.0.0.1, serving answers recorded from staging and production.
🤖 Generated with Claude Code