Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
ffd224a
Import test modules with from-imports instead of aliases
anth-volk Oct 14, 2026
62bb899
Group test helper parameters into composite fixtures and option objects
anth-volk Oct 14, 2026
08f6028
Pin contract and persistence validation behaviour before refactoring
anth-volk Oct 14, 2026
8e5dd80
Split the comparison report record's state validation into report and…
anth-volk Oct 14, 2026
e8f7025
Name the contract's URI, version and name-length limits
anth-volk Oct 14, 2026
25c671e
Split the live column type check into enum and plain type checks
anth-volk Oct 14, 2026
6a171a4
Keep the observability initializer's keyword-only identity arguments
anth-volk Oct 14, 2026
774e6b9
Pin the order and messages of entry settings validation
anth-volk Oct 14, 2026
357986b
Split entry settings validation into caller auth, old gateway and Sta…
anth-volk Oct 14, 2026
c18a646
Pin the Stage 12 adapter's complete report input and check order
anth-volk Oct 14, 2026
c8c0e7c
Split the Stage 12 annual-comparison adapter into eligibility checks …
anth-volk Oct 14, 2026
d531e6d
Pin the Stage 12 dispatch calls, acknowledged parent and skip event
anth-volk Oct 14, 2026
84809fb
Group Stage 12 dispatch inputs into a prepared report, origin and wor…
anth-volk Oct 14, 2026
b2e5dde
Pin entry app telemetry, Stage 12 route failures and backend readiness
anth-volk Oct 14, 2026
53f5168
Split the entry app factory into a shared runtime and route registrat…
anth-volk Oct 14, 2026
97ba5ee
Compare the old gateway's health status with HTTPStatus.OK
anth-volk Oct 14, 2026
82e2001
Pin gateway route resolution from the active routing state
anth-volk Oct 14, 2026
0958bc5
Split gateway active-state route resolution into pinned, latest and r…
anth-volk Oct 14, 2026
a0d3c0c
Pass the resolved region to _build_simulation instead of its code and…
anth-volk Oct 14, 2026
d28c9f9
Pass artifact key inputs to the dataset and baseline key builders as …
anth-volk Oct 14, 2026
31333bd
Pass baseline identity facts as one request and split out the artifac…
anth-volk Oct 14, 2026
8a90b5a
Pass the precompute's Modal function handles to run_precompute as one…
anth-volk Oct 14, 2026
a6b8c13
Pin every input the national reduce passes to the output builder
anth-volk Oct 14, 2026
9d95f2c
Pass the national reduce's run facts to build_national_output as one …
anth-volk Oct 14, 2026
567ee5f
Pass fan-out poll timing to the batch and segmented runners as one ba…
anth-volk Oct 14, 2026
03dd8df
Pin the Stage 12 bundle schema version check
anth-volk Oct 14, 2026
aad67ff
Name the supported Stage 12 bundle schema version
anth-volk Oct 14, 2026
4a2e6d5
Pin every Stage 12 parity mismatch code, path and comparison order
anth-volk Oct 14, 2026
4fad39c
Split Stage 12 parity comparisons into entity, column and report-valu…
anth-volk Oct 14, 2026
309b85a
Pin parity qualification receipts, check order and default runner lif…
anth-volk Oct 14, 2026
2a1979a
Split parity qualification into existing-run, provenance, calculation…
anth-volk Oct 14, 2026
f376382
Pin Stage 12 coordination validation, claims, failures, stages and ag…
anth-volk Oct 14, 2026
6f39550
Pass each role's retained frames and descriptor to the aggregate buil…
anth-volk Oct 14, 2026
036744a
Split Stage 12 report coordination into claim, plan, dispatch, wait a…
anth-volk Oct 14, 2026
229943d
Pin how the Modal child invoker looks up, spawns and restores children
anth-volk Oct 14, 2026
3fdcd71
Pass the child invoker one spawn request instead of six keywords
anth-volk Oct 14, 2026
223ef7f
Pin result comparison logs, spans and the failed state-update path
anth-volk Oct 14, 2026
4022055
Pass the result comparison its coordinator services as one group
anth-volk Oct 14, 2026
968fc47
Pin the Stage 12 worker's reuse path and calculator timing
anth-volk Oct 14, 2026
682d1b4
Pass the single-simulation worker its seams as one dependencies group
anth-volk Oct 14, 2026
8074205
Pin Stage 12 worker validation results, order and failures
anth-volk Oct 14, 2026
a2b430e
Pass Stage 12 worker validation its environment and checks as one group
anth-volk Oct 14, 2026
a8f7197
Pin routing state validation messages and check order
anth-volk Oct 14, 2026
44c1405
Group a deployed app's versions in the routing publisher and split it…
anth-volk Oct 14, 2026
4c7e3d5
Pin the artifact fetch layer's receipt, credential and download edge …
anth-volk Oct 14, 2026
72b6691
Split the image artifact fetch into freshness, credential and downloa…
anth-volk Oct 14, 2026
b8784ca
Pin the release sentinel's polling, retry statuses and problem order
anth-volk Oct 14, 2026
44cb493
Split the release sentinel's deployment check into submit, poll and c…
anth-volk Oct 14, 2026
9ff57b9
Pin the routing publisher CLI's mode order, outputs and argument errors
anth-volk Oct 14, 2026
174fc35
Split the routing publisher CLI into one command per mode
anth-volk Oct 14, 2026
5c80589
Pin the package update helper's bundle-versions and verify-dispatch o…
anth-volk Oct 14, 2026
41b6abf
Split the package update helper's bundle and dispatch checks into hel…
anth-volk Oct 14, 2026
2f67b0f
Pin the Stage 12 connection retry's delay sequence and budget cut
anth-volk Oct 14, 2026
743ff46
Pass the Stage 12 connection retry its clock, sleep and jitter as one…
anth-volk Oct 14, 2026
58b73ef
Pin the workflow expression evaluator and routing command discovery
anth-volk Oct 14, 2026
4d176d0
Split the workflow expression evaluator into a tokenizer and a parser
anth-volk Oct 14, 2026
b15cf32
Split routing command discovery into script, array and per-command ch…
anth-volk Oct 14, 2026
2faa017
Group the routing and package-update test parameters into cases
anth-volk Oct 14, 2026
385367b
Take the Public API's group 8b rules tool refactor
anth-volk Oct 14, 2026
ede3b89
Select every ruff rule (ALL)
anth-volk Oct 14, 2026
9be0327
Pin the poll, deadline and budget timing the segmented runner #795 ex…
anth-volk Oct 15, 2026
c14453f
Pass the segmented runner the poll and deadline timing #795 added as …
anth-volk Oct 15, 2026
0215687
Set the fake child call's cancel failures #795 added after construction
anth-volk Oct 15, 2026
3988ce5
Pin how the tracked_stage #791 added scopes and records each stage
anth-volk Oct 15, 2026
b9ff16a
Pass the tracked_stage #791 added its stage plan, progress and runtim…
anth-volk Oct 15, 2026
9741890
Compare the word count in the redaction helper #791 added without a m…
anth-volk Oct 15, 2026
c3f50d1
Parametrize the permission tests #758 added by one route case
anth-volk Oct 15, 2026
a16d681
Pin the runtime and job progress the segmented runner #781 reports to
anth-volk Oct 15, 2026
8e88dd1
Pass the segmented runner its runtime and the job progress #781 added…
anth-volk Oct 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
203 changes: 122 additions & 81 deletions .github/scripts/update_policyengine_package.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import tempfile
from collections.abc import Mapping
from pathlib import Path
from typing import Any
from typing import Any, NamedTuple

import tomllib

Expand Down Expand Up @@ -201,6 +201,56 @@ def latest_version(package: str) -> None:
_write_result(version)


def _package_version(packages: Mapping[str, Any], name: str) -> str:
version = _mapping(packages.get(name)).get("version")
if not isinstance(version, str) or not version:
msg = f"Bundle has no version for {name}"
raise SystemExit(msg)
return version


def _data_release_version(release: Mapping[str, Any], country: str) -> str:
version = release.get("version") or _mapping(release.get("data_package")).get(
"version"
)
if not isinstance(version, str) or not version:
msg = f"Bundle has no data release version for {country}"
raise SystemExit(msg)
return version


def _data_package_version(release: Mapping[str, Any]) -> str:
version = _mapping(release.get("data_package")).get("version")
return version if isinstance(version, str) else ""


def _dataset_sha256(release: Mapping[str, Any], country: str) -> str:
"""Return the default dataset's digest, preferring the certified artifact's."""
default = release.get("default_dataset")
certified = _mapping(release.get("certified_data_artifact"))
if certified.get("dataset") == default and certified.get("sha256"):
value = certified["sha256"]
else:
reference = _mapping(_mapping(release.get("datasets")).get(default))
value = reference.get("sha256") or ""
digest = str(value).strip().lower()
if digest and not re.fullmatch(r"[0-9a-f]{64}", digest):
msg = f"Bundle records a malformed {country} dataset SHA-256"
raise SystemExit(msg)
return digest


def _certificate_sha256(release: object) -> str:
# Receipt-v1 certificate digest; older wrappers lack the helper.
try:
from policyengine.provenance.receipt import (
certificate_sha256 as digest,
)
except ImportError:
return ""
return digest(release) if isinstance(release, Mapping) else ""


def bundle_versions() -> None:
"""Print the package and data versions selected by PolicyEngine.py."""
from policyengine.bundle import (
Expand All @@ -210,67 +260,20 @@ def bundle_versions() -> None:
bundle = _mapping(get_current_bundle())
packages = _mapping(bundle.get("packages"))
data_releases = _mapping(bundle.get("data_releases"))

def package_version(name: str) -> str:
version = _mapping(packages.get(name)).get("version")
if not isinstance(version, str) or not version:
msg = f"Bundle has no version for {name}"
raise SystemExit(msg)
return version

def data_release_version(country: str) -> str:
release = _mapping(data_releases.get(country))
version = release.get("version") or _mapping(release.get("data_package")).get(
"version"
)
if not isinstance(version, str) or not version:
msg = f"Bundle has no data release version for {country}"
raise SystemExit(msg)
return version

def data_package_version(country: str) -> str:
release = _mapping(data_releases.get(country))
version = _mapping(release.get("data_package")).get("version")
return version if isinstance(version, str) else ""

def dataset_sha256(country: str) -> str:
release = _mapping(data_releases.get(country))
default = release.get("default_dataset")
certified = _mapping(release.get("certified_data_artifact"))
if certified.get("dataset") == default and certified.get("sha256"):
value = certified["sha256"]
else:
reference = _mapping(_mapping(release.get("datasets")).get(default))
value = reference.get("sha256") or ""
digest = str(value).strip().lower()
if digest and not re.fullmatch(r"[0-9a-f]{64}", digest):
msg = f"Bundle records a malformed {country} dataset SHA-256"
raise SystemExit(msg)
return digest

def certificate_sha256(country: str) -> str:
# Receipt-v1 certificate digest; older wrappers lack the helper.
try:
from policyengine.provenance.receipt import (
certificate_sha256 as digest,
)
except ImportError:
return ""
release = data_releases.get(country)
return digest(release) if isinstance(release, Mapping) else ""

us_release = _mapping(data_releases.get("us"))
uk_release = _mapping(data_releases.get("uk"))
outputs = {
"policyengine_version": package_version("policyengine"),
"policyengine_core_version": package_version("policyengine-core"),
"spm_calculator_version": package_version("spm-calculator"),
"us_version": package_version("policyengine-us"),
"us_data_version": data_release_version("us"),
"uk_version": package_version("policyengine-uk"),
"uk_data_version": data_release_version("uk"),
"uk_data_package_version": data_package_version("uk"),
"us_dataset_sha256": dataset_sha256("us"),
"uk_dataset_sha256": dataset_sha256("uk"),
"uk_certificate_sha256": certificate_sha256("uk"),
"policyengine_version": _package_version(packages, "policyengine"),
"policyengine_core_version": _package_version(packages, "policyengine-core"),
"spm_calculator_version": _package_version(packages, "spm-calculator"),
"us_version": _package_version(packages, "policyengine-us"),
"us_data_version": _data_release_version(us_release, "us"),
"uk_version": _package_version(packages, "policyengine-uk"),
"uk_data_version": _data_release_version(uk_release, "uk"),
"uk_data_package_version": _data_package_version(uk_release),
"us_dataset_sha256": _dataset_sha256(us_release, "us"),
"uk_dataset_sha256": _dataset_sha256(uk_release, "uk"),
"uk_certificate_sha256": _certificate_sha256(data_releases.get("uk")),
}
for name, version in outputs.items():
_write_result(f"{name}={version}")
Expand All @@ -293,25 +296,30 @@ def certificate_sha256(country: str) -> str:
PROMOTABLE_VERDICTS = frozenset({"qualified", "needs_review"})


def verify_dispatch() -> None:
"""Check a policyengine-release payload against the installed bundle.
class _DispatchComparison(NamedTuple):
"""What comparing a dispatch with the installed bundle found.

Reads the dispatch ``client_payload`` JSON from ``RELEASE_PAYLOAD`` and
``bundle-versions`` output from stdin. A payload without ``bundle.uk``
(older releases, manual runs) is accepted. A field the payload names
must equal the installed bundle's, and an installed bundle that cannot
report it is a mismatch, except for the certificate digest, which older
wrappers cannot compute. A qualification that says the pair was not
promotable is refused. ``dispatch_verified`` is true only when at least
one field was compared. Prints digests for the PR body.
``mismatches`` lists the fields that disagree; ``compared`` counts the
fields both the dispatch and the installed bundle stated.
"""
raw = os.environ.get("RELEASE_PAYLOAD", "").strip()
payload = json.loads(raw) if raw and raw != "null" else {}

mismatches: list[str]
compared: int


def _installed_fields(bundle_output: str) -> dict[str, str]:
"""Read ``bundle-versions`` output: one ``key=value`` line per field."""
installed = {}
for line in sys.stdin.read().splitlines():
for line in bundle_output.splitlines():
key, _, value = line.partition("=")
if key:
installed[key.strip()] = value.strip()
return installed


def _require_dispatched_wrapper(
payload: Mapping[str, Any], installed: Mapping[str, str]
) -> None:
if payload.get("version") and payload["version"] != installed.get(
"policyengine_version"
):
Expand All @@ -320,7 +328,12 @@ def verify_dispatch() -> None:
f"{installed.get('policyengine_version')} is installed."
)
raise SystemExit(msg)
expected = _mapping(_mapping(payload.get("bundle")).get("uk"))


def _compare_dispatch_bundle(
expected: Mapping[str, Any], installed: Mapping[str, str]
) -> _DispatchComparison:
"""Compare each field the dispatch names with the installed bundle's value."""
mismatches = []
compared = 0
for payload_key, installed_key in DISPATCH_CHECKS:
Expand All @@ -341,11 +354,11 @@ def verify_dispatch() -> None:
claimed_text = claimed_text.lower()
if claimed_text != actual:
mismatches.append(f"{payload_key}: dispatch {claimed}, installed {actual}")
if mismatches:
raise SystemExit(
"The release dispatch names a different UK bundle from the installed "
"wheel: " + "; ".join(mismatches)
)
return _DispatchComparison(mismatches, compared)


def _promotable_qualification(payload: Mapping[str, Any]) -> tuple[str, str]:
"""Return the UK verdict and receipt digest, refusing malformed or rejected ones."""
qualification = _mapping(_mapping(payload.get("qualification")).get("uk"))
verdict = qualification.get("verdict") or ""
receipt_sha256 = qualification.get("receipt_sha256") or ""
Expand All @@ -362,7 +375,35 @@ def verify_dispatch() -> None:
):
msg = f"The release dispatch says the UK pair was not promotable ({verdict})"
raise SystemExit(msg)
_write_result(f"dispatch_verified={'true' if compared else 'false'}")
return verdict, receipt_sha256


def verify_dispatch() -> None:
"""Check a policyengine-release payload against the installed bundle.

Reads the dispatch ``client_payload`` JSON from ``RELEASE_PAYLOAD`` and
``bundle-versions`` output from stdin. A payload without ``bundle.uk``
(older releases, manual runs) is accepted. A field the payload names
must equal the installed bundle's, and an installed bundle that cannot
report it is a mismatch, except for the certificate digest, which older
wrappers cannot compute. A qualification that says the pair was not
promotable is refused. ``dispatch_verified`` is true only when at least
one field was compared. Prints digests for the PR body.
"""
raw = os.environ.get("RELEASE_PAYLOAD", "").strip()
payload = json.loads(raw) if raw and raw != "null" else {}
installed = _installed_fields(sys.stdin.read())
_require_dispatched_wrapper(payload, installed)
comparison = _compare_dispatch_bundle(
_mapping(_mapping(payload.get("bundle")).get("uk")), installed
)
if comparison.mismatches:
raise SystemExit(
"The release dispatch names a different UK bundle from the installed "
"wheel: " + "; ".join(comparison.mismatches)
)
verdict, receipt_sha256 = _promotable_qualification(payload)
_write_result(f"dispatch_verified={'true' if comparison.compared else 'false'}")
_write_result(f"uk_qualification_verdict={verdict}")
_write_result(f"uk_qualification_receipt_sha256={receipt_sha256}")

Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ To add or change a rule, read `docs/engineering/skills/rules-registry.md`.
- `sim.quality.every-project-tested`: Every project and library with unit tests runs them in the PR test matrix. (doc-only, not checked: follow it yourself)
- `sim.quality.ruff-format-src`: Source code under projects/*/src and libs/*/src is formatted with ruff format. (blocking: ruff format)
- `sim.quality.ruff-format-tests`: Test code is formatted with ruff format too; CI currently checks only src directories. (doc-only, not checked: follow it yourself)
- `sim.quality.ruff-lint`: All Python code passes ruff check with every rule family selected in ruff.toml, with no ratchet; families are added one themed group at a time until the selection is ALL. (blocking: ruff check (A, ANN, ARG, ASYNC, B, BLE, C4, D, DTZ, E, E4, E7, E9, EM, ERA, EXE, F, FAST, FBT, FIX, FLY, FURB, G, I, INP, LOG, N, PD, PERF, PGH, PIE, PLC, PLW, PT, PTH, PYI, RET, RSE, RUF, S, SIM, SLF, T20, TC, TD, TID253, TRY, UP, W))
- `sim.quality.ruff-lint`: All Python code passes ruff check with every ruff rule selected (ALL), with no ratchet, except the ignores in ruff.toml: COM812 and ISC001, which conflict with ruff format, and D203 and D213, which conflict with the kept D211 and D212. (blocking: ruff check (ALL))
- `sim.quality.runtime-evaluated-models-resolve`: Every Pydantic model is runtime-evaluated for ruff: one of its bases, read through the imports of the model's own module, is listed in ruff.toml's runtime-evaluated-base-classes or is a runtime-evaluated class in that same module; and every Pydantic model resolves its annotations once its package is imported. (blocking: pytest `projects/policyengine-simulation-entry/tests/test_runtime_evaluated_annotations.py`, `projects/policyengine-simulation-executor/tests/test_runtime_evaluated_annotations.py`)
- `sim.quality.src-programs-ruff-exemptions`: Programs under src (the Modal deployment utilities, and the OpenAPI and Stage 12 infrastructure commands) may print their output (T201), open URLs from their arguments (S310) and, for the Modal utilities run with python -m, live in a directory that isn't a package (INP001); every other ruff rule applies to them. (blocking: ruff per-file ignore of INP001, S310, T201 for `projects/*/src/{modal/utils/*,policyengine_simulation_entry/generate_openapi,policyengine_simulation_entry/stage12_infrastructure,policyengine_simulation_gateway/generate_openapi}.py`)
- `sim.quality.tests-pass`: Each project's unit tests pass in its own locked environment. (blocking: CI job `test`)
Expand Down
1 change: 1 addition & 0 deletions changelog_entry.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
- Made CLAUDE.md and the Copilot instructions thin pointers to AGENTS.md, required workflow logic longer than one line to live in tested and shellchecked scripts, and registered the tracking-issue rule
- Added a rules registry (rules/registry.toml) with a standard-library tool that validates it, checks every rule is enforced where it claims, and renders docs/generated/rules.md and the Rules block in AGENTS.md
changed:
- Enabled every ruff rule (select ALL), ignoring only COM812 and ISC001, which conflict with ruff format, and the D203 and D213 halves of the mutually exclusive docstring pairs; complex functions, long parameter lists and magic numbers were split into smaller helpers and grouped parameters, each behind new characterization tests, across the contract and persistence validation, entry settings, app factory and Stage 12 adapter, gateway route resolution, Stage 12 coordination, workers, parity and qualification, the precompute and national reduce, the routing publisher and its command line, the release sentinel, the package update helper and the shared rules tool; Modal function, FastAPI route, Pydantic validator and command-line signatures and every OpenAPI document are unchanged, and three library APIs that other projects import keep a reasoned PLR0913 suppression
- Enabled ruff's argument, idiom and pytest-style rules (ARG, ASYNC, B, C4, FBT, PD, PERF, PGH, PLW, PT, PTH, RUF, SIM); unused arguments are removed, underscored or explained, FastAPI auth dependencies use Annotated, re-raised errors keep their cause, and boolean options of in-repo helpers are keyword-only; OpenAPI documents are unchanged, and the shared rules tool's unittest tests are exempt from pytest-style rules; tests take the unused-argument rules (ARG001, ARG002, ARG005) as file-level exceptions, since their fakes and stubs mirror real signatures; best-effort telemetry and cleanup calls that swallow any error use contextlib.suppress, with the reason as a comment
- Enabled ruff's structure and import rules (INP, PLC, SLF, N, A, FAST, E); long lines are wrapped, deferred imports that Modal images, test seams, import cycles or heavy dependencies need carry a reason, script directories are declared to ruff as namespace packages, four internal exceptions gained an Error suffix, and published field names, OpenAPI documents and logged error types are unchanged; tests are also exempt from SLF001, INP001 and PLC0415; heavy or optional packages that load only inside functions are listed in ruff's banned-module-level-imports, which keeps them out of module-level imports (TID253) and lets their deferred imports go without a suppression, and the Modal utility programs take INP001 file-wide
- Enabled ruff's security, logging and debugging rules (S, T20, LOG, G, DTZ, ERA, TD, FIX, EXE); asserts outside tests became explicit checks, command-line scripts keep their printed output, test subprocess calls go through helpers, and every deliberate exception carries a reasoned suppression; the gateway's auth misconfiguration log no longer repeats its message, and the deployed-service tests share the unit-test ruff exemptions; tests, CI/CD scripts and command-line scripts take their deliberate exceptions as registered file-level ruff ignores instead of inline suppressions, and the rules guard rejects file-level ignores anywhere else
Expand Down
11 changes: 7 additions & 4 deletions docs/engineering/skills/rules-registry.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,13 @@ fail the guard yet.

## Baselines

- **Ruff is never ratcheted.** Adopting a ruff rule family is one standalone PR
that changes the select list and fixes every resulting violation. Don't use
`--add-noqa`. A finding that is kept gets an inline `# noqa: CODE # reason`,
except in the files below.
- **Ruff is never ratcheted.** `ruff.toml` selects `ALL`, so every ruff rule
applies except the four listed in `ignore` (two formatter conflicts and one
of each mutually exclusive docstring pair) and the registered
[file-level exceptions](#file-level-exceptions). When a ruff upgrade adds
rules, the upgrade PR fixes every new violation. Don't use `--add-noqa`.
Outside the files that allow file-level exceptions, a justified exception is
an inline `# noqa: CODE # reason`.
- Other enforcers (mypy, Pyright, Import Linter, custom guards) may start
`ratcheted` with a baseline file of tolerated violations. Baselines may only
shrink: the guard compares counts against the base branch.
Expand Down
Loading
Loading