Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
04adc47
Import test modules with from-imports instead of aliases
anth-volk Oct 14, 2026
18dcc95
Group test helper parameters into composite fixtures and option objects
anth-volk Oct 14, 2026
acb07f9
Pin contract and persistence validation behaviour before refactoring
anth-volk Oct 14, 2026
a3b2eb5
Split the comparison report record's state validation into report and…
anth-volk Oct 14, 2026
d773104
Name the contract's URI, version and name-length limits
anth-volk Oct 14, 2026
f4c78cb
Split the live column type check into enum and plain type checks
anth-volk Oct 14, 2026
50046b3
Keep the observability initializer's keyword-only identity arguments
anth-volk Oct 14, 2026
bd49389
Pin the order and messages of entry settings validation
anth-volk Oct 14, 2026
056e8e6
Split entry settings validation into caller auth, old gateway and Sta…
anth-volk Oct 14, 2026
64c761b
Pin the Stage 12 adapter's complete report input and check order
anth-volk Oct 14, 2026
1cb7ef4
Split the Stage 12 annual-comparison adapter into eligibility checks …
anth-volk Oct 14, 2026
cd9ad62
Pin the Stage 12 dispatch calls, acknowledged parent and skip event
anth-volk Oct 14, 2026
a10fc21
Group Stage 12 dispatch inputs into a prepared report, origin and wor…
anth-volk Oct 14, 2026
38fea67
Pin entry app telemetry, Stage 12 route failures and backend readiness
anth-volk Oct 14, 2026
c330425
Split the entry app factory into a shared runtime and route registrat…
anth-volk Oct 14, 2026
79af8b8
Compare the old gateway's health status with HTTPStatus.OK
anth-volk Oct 14, 2026
b808a13
Pin gateway route resolution from the active routing state
anth-volk Oct 14, 2026
9ff3af3
Split gateway active-state route resolution into pinned, latest and r…
anth-volk Oct 14, 2026
ff1176f
Pass the resolved region to _build_simulation instead of its code and…
anth-volk Oct 14, 2026
b81ae32
Pass artifact key inputs to the dataset and baseline key builders as …
anth-volk Oct 14, 2026
03c0110
Pass baseline identity facts as one request and split out the artifac…
anth-volk Oct 14, 2026
aaebaa1
Pass the precompute's Modal function handles to run_precompute as one…
anth-volk Oct 14, 2026
af88de4
Pin every input the national reduce passes to the output builder
anth-volk Oct 14, 2026
d344fb7
Pass the national reduce's run facts to build_national_output as one …
anth-volk Oct 14, 2026
0a6b16d
Pass fan-out poll timing to the batch and segmented runners as one ba…
anth-volk Oct 14, 2026
a07f26f
Pin the Stage 12 bundle schema version check
anth-volk Oct 14, 2026
22122ef
Name the supported Stage 12 bundle schema version
anth-volk Oct 14, 2026
ee4ab7f
Pin every Stage 12 parity mismatch code, path and comparison order
anth-volk Oct 14, 2026
4b3d612
Split Stage 12 parity comparisons into entity, column and report-valu…
anth-volk Oct 14, 2026
a0f240a
Pin parity qualification receipts, check order and default runner lif…
anth-volk Oct 14, 2026
a6202eb
Split parity qualification into existing-run, provenance, calculation…
anth-volk Oct 14, 2026
06de693
Pin Stage 12 coordination validation, claims, failures, stages and ag…
anth-volk Oct 14, 2026
81b1f73
Pass each role's retained frames and descriptor to the aggregate buil…
anth-volk Oct 14, 2026
a34d0e2
Split Stage 12 report coordination into claim, plan, dispatch, wait a…
anth-volk Oct 14, 2026
a5284a0
Pin how the Modal child invoker looks up, spawns and restores children
anth-volk Oct 14, 2026
ad4c7be
Pass the child invoker one spawn request instead of six keywords
anth-volk Oct 14, 2026
c6eceee
Pin result comparison logs, spans and the failed state-update path
anth-volk Oct 14, 2026
70a4959
Pass the result comparison its coordinator services as one group
anth-volk Oct 14, 2026
fd27660
Pin the Stage 12 worker's reuse path and calculator timing
anth-volk Oct 14, 2026
2eae84e
Pass the single-simulation worker its seams as one dependencies group
anth-volk Oct 14, 2026
55128c3
Pin Stage 12 worker validation results, order and failures
anth-volk Oct 14, 2026
d729afa
Pass Stage 12 worker validation its environment and checks as one group
anth-volk Oct 14, 2026
c2ab9a1
Pin routing state validation messages and check order
anth-volk Oct 14, 2026
635dc59
Group a deployed app's versions in the routing publisher and split it…
anth-volk Oct 14, 2026
49831c1
Pin the artifact fetch layer's receipt, credential and download edge …
anth-volk Oct 14, 2026
db44e17
Split the image artifact fetch into freshness, credential and downloa…
anth-volk Oct 14, 2026
1a307bb
Pin the release sentinel's polling, retry statuses and problem order
anth-volk Oct 14, 2026
4cb46aa
Split the release sentinel's deployment check into submit, poll and c…
anth-volk Oct 14, 2026
9266d70
Pin the routing publisher CLI's mode order, outputs and argument errors
anth-volk Oct 14, 2026
c9f8d86
Split the routing publisher CLI into one command per mode
anth-volk Oct 14, 2026
124a2e4
Pin the package update helper's bundle-versions and verify-dispatch o…
anth-volk Oct 14, 2026
3aa70db
Split the package update helper's bundle and dispatch checks into hel…
anth-volk Oct 14, 2026
07009d9
Pin the Stage 12 connection retry's delay sequence and budget cut
anth-volk Oct 14, 2026
a04a692
Pass the Stage 12 connection retry its clock, sleep and jitter as one…
anth-volk Oct 14, 2026
e048d60
Pin the workflow expression evaluator and routing command discovery
anth-volk Oct 14, 2026
a2dca95
Split the workflow expression evaluator into a tokenizer and a parser
anth-volk Oct 14, 2026
d591597
Split routing command discovery into script, array and per-command ch…
anth-volk Oct 14, 2026
97d33ad
Group the routing and package-update test parameters into cases
anth-volk Oct 14, 2026
f12d98d
Take the Public API's group 8b rules tool refactor
anth-volk Oct 14, 2026
257e32d
Select every ruff rule (ALL)
anth-volk Oct 14, 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 ( # noqa: PLC0415 # older policyengine wrappers lack the receipt helper, so the ImportError is handled here
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 ( # noqa: PLC0415 # only bundle-versions runs under uv with policyengine installed; the other commands use the runner's python3
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 ( # noqa: PLC0415 # older policyengine wrappers lack the receipt helper, so the ImportError is handled here
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 @@ -139,7 +139,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, 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.tests-pass`: Each project's unit tests pass in its own locked environment. (blocking: CI job `test`)
- `sim.quality.tests-ruff-exemptions`: Test files may use pytest's bare assert, need no docstrings, annotations or named constants for magic numbers, and may reach into private members, live outside packages and import inside a test (S101, D1, ANN, PLR2004, SLF001, INP001, PLC0415); every other ruff rule applies to tests. (blocking: ruff per-file ignore of ANN, D1, INP001, PLC0415, PLR2004, S101, SLF001 for `**/tests/**`)
- `sim.quality.unittest-ruff-exemptions`: The shared rules tool's tests use unittest, because the tool needs only the standard library, so ruff's pytest-style rules (PT) don't apply to them. (blocking: ruff per-file ignore of PT for `scripts/rules/tests/**`)
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
- 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
- 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
Expand Down
9 changes: 6 additions & 3 deletions docs/engineering/skills/rules-registry.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,12 @@ 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` or blanket per-file ignores.
- **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 per-file
ignores. When a ruff upgrade adds rules, the upgrade PR fixes every new
violation. Don't use `--add-noqa` or blanket per-file ignores; 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