Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
5174fd6
Add candidate qualification contracts and the scorecard replay interface
MaxGhenis Oct 11, 2026
2722a71
Qualify UK candidates: gates, ledger, ordered promotion and release d…
MaxGhenis Oct 11, 2026
d810066
Verify wheels through the shared provenance.wheels module
MaxGhenis Oct 11, 2026
d3bde21
Use the reform contract runner's default sample size
MaxGhenis Oct 11, 2026
a087276
Re-pin the scorecard replay schemas at 91f46c1
MaxGhenis Oct 11, 2026
ca3840d
Gate on the whole reform contract report, bound to the candidate
MaxGhenis Oct 11, 2026
da267e2
Test the contract gate on W1's real full all-pass report
MaxGhenis Oct 11, 2026
def8b8d
Follow the scorecard's tightened replay interface (dc040de)
MaxGhenis Oct 11, 2026
28c90ea
Fail a candidate whose standard reforms do not move the balance
MaxGhenis Oct 11, 2026
4da37e4
Read the metrics tool's own responsiveness checks
MaxGhenis Oct 11, 2026
68820f2
Follow the metrics tool at c7a6c9639: no-worsening by scope, skipped …
MaxGhenis Oct 11, 2026
3ea0b19
Pin the metrics tool with failing scopes; test on a real report
MaxGhenis Oct 11, 2026
b424ffb
Test scope-based no-worsening on the tool's real failing scopes
MaxGhenis Oct 11, 2026
ee78f5e
Close gate holes found by review: partial reports and self-contradict…
MaxGhenis Oct 12, 2026
abffac1
Close certification holes found by review
MaxGhenis Oct 12, 2026
a17f801
Close promotion and ledger holes found by review
MaxGhenis Oct 12, 2026
0d5cc12
Pin W3's metrics tool at 9b33fe19f
MaxGhenis Oct 12, 2026
23698f4
Enforce the qualified core before online compatibility acceptance
MaxGhenis Oct 12, 2026
537c0c1
Require exact replay wheel evidence and record baseline wheel identity
MaxGhenis Oct 12, 2026
3d19e3c
Prepare exact UK candidate sidecars and keep staged receipts ordered …
MaxGhenis Oct 12, 2026
b87a776
Require full contract coverage and bind finalized gate evidence to th…
MaxGhenis Oct 12, 2026
f58b11a
Attach the deferred-import lint suppression to the import statement
MaxGhenis Oct 12, 2026
ca93076
Merge remote-tracking branch 'origin/main' into w4/qualification-resp…
MaxGhenis Oct 12, 2026
d30e3f0
Gate W1's full run on released policyengine-uk 2.126.12 against the r…
MaxGhenis Oct 12, 2026
b44fa88
Generate replay requests under country-import suppression; draft a re…
MaxGhenis Oct 13, 2026
968272c
Hold exact-pair certificates to their qualified model and build
MaxGhenis Oct 13, 2026
aa8d17b
Accept only the runner's declared exemptions and require its run-leve…
MaxGhenis Oct 13, 2026
72d263d
Give each contract assertion an id and declare full-run population co…
MaxGhenis Oct 14, 2026
c19eee4
Fix round-4 review findings on UK candidate qualification
MaxGhenis Oct 14, 2026
b182e95
Check the UC allowance response on the award before deductions
MaxGhenis Oct 14, 2026
c9ecb38
Bound UC deduction rises by each unit's own rate
MaxGhenis Oct 14, 2026
e13dc0a
Merge remote-tracking branch 'origin/w4/runner-uc-award-check' into w…
MaxGhenis Oct 14, 2026
2de1572
Merge remote-tracking branch 'origin/main' into w4/qualification-resp…
MaxGhenis Oct 15, 2026
d3b526e
Merge origin/main (receipt schema v1, #571) into candidate qualification
Oct 15, 2026
769d5ef
Require the current baseline before promotion; recover forced attempt…
Oct 15, 2026
00b4cfe
Never let a concurrent ledger publish merge two attempts' receipts
Oct 15, 2026
2d05150
Run every qualification step in the environment its job installed
MaxGhenis Oct 15, 2026
09910b1
Merge remote-tracking branch 'origin/main' into w4/qualification-resp…
MaxGhenis Oct 15, 2026
61155b5
Declare a historical report's checks as its own runner did
MaxGhenis Oct 15, 2026
a416230
Recognise contract 1.1.0's run-level row; gate a real 1.1.0 full run
MaxGhenis Oct 16, 2026
892d8da
Replace the 1.58.0 metrics fixture with W3's suppressed 1.58.1 report
MaxGhenis Oct 16, 2026
71a872d
Read the metrics tool's small-cell format; pin it at 228a05a3e
MaxGhenis Oct 16, 2026
3fa3d74
Line up additive totals with exclude_signs; close supp-r1's findings
MaxGhenis Oct 16, 2026
1bb9b3a
Suppress small counts in published contract evidence; close round 10
MaxGhenis Oct 16, 2026
464bf31
Withhold every value from an unsafe metrics report; compare signed ex…
MaxGhenis Oct 16, 2026
a2efd36
Withhold population totals over fewer than 10 records from the ledger
MaxGhenis Oct 16, 2026
d2aa8d4
Publish only true/false findings from population checks' details
MaxGhenis Oct 16, 2026
574e262
Publish only population rows' fixed fields and top-level findings
MaxGhenis Oct 16, 2026
2ade4a0
Runbook: rerun a population check on the attempt's own commit and data
MaxGhenis Oct 16, 2026
5736f99
Withhold every population row field outside the runner's fixed set
MaxGhenis Oct 16, 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
49 changes: 36 additions & 13 deletions .github/dispatch-policyengine-release.sh
Original file line number Diff line number Diff line change
@@ -1,28 +1,51 @@
#!/usr/bin/env bash
#
# Notify consumer repositories that a policyengine release is available on
# PyPI by sending a policyengine-release repository dispatch, which triggers
# their update workflows to open single-version bump PRs.
# PyPI by sending a repository dispatch, which triggers their update
# workflows.
#
# Environment:
# VERSION Exact policyengine version that was released.
# GH_TOKEN PolicyEngine GitHub App token with Contents write access to the
# consumer repositories.
# VERSION Exact policyengine version that was released.
# GH_TOKEN PolicyEngine GitHub App token with Contents write access to
# the consumer repositories.
# CONSUMER_REPOS Space-separated repositories to notify (default:
# policyengine-api policyengine-sim-api).
# PAYLOAD_FILE Optional JSON file holding the whole dispatch body
# ({"event_type": ..., "client_payload": {...}}), as written
# by scripts/release_consumers.py. Without it, a
# policyengine-release event carrying only the version is sent.
set -euo pipefail

if [[ -z "${VERSION:-}" ]]; then
echo "ERROR: VERSION must be set." >&2
exit 1
fi

CONSUMER_REPOS=(
policyengine-api
policyengine-sim-api
read -r -a REPOS <<< "${CONSUMER_REPOS:-policyengine-api policyengine-sim-api}"

if [[ -n "${PAYLOAD_FILE:-}" ]]; then
payload_version=$(python - "$PAYLOAD_FILE" <<'PY'
import json
import sys

payload = json.load(open(sys.argv[1]))["client_payload"]
request = payload.get("request")
print(payload.get("version") or request["candidate"]["policyengine"]["version"])
PY
)
if [[ "$payload_version" != "$VERSION" ]]; then
echo "ERROR: ${PAYLOAD_FILE} is for ${payload_version}, not ${VERSION}." >&2
exit 1
fi
fi

for repo in "${CONSUMER_REPOS[@]}"; do
gh api "repos/PolicyEngine/${repo}/dispatches" \
-f event_type=policyengine-release \
-f "client_payload[version]=${VERSION}"
echo "Dispatched policyengine-release ${VERSION} to PolicyEngine/${repo}."
for repo in "${REPOS[@]}"; do
if [[ -n "${PAYLOAD_FILE:-}" ]]; then
gh api "repos/PolicyEngine/${repo}/dispatches" --method POST --input "$PAYLOAD_FILE"
else
gh api "repos/PolicyEngine/${repo}/dispatches" \
-f event_type=policyengine-release \
-f "client_payload[version]=${VERSION}"
fi
echo "Dispatched policyengine ${VERSION} to PolicyEngine/${repo}."
done
116 changes: 116 additions & 0 deletions .github/qualification-ledger.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#!/usr/bin/env bash
#
# Check out, or publish to, the qualification ledger branch.
#
# .github/qualification-ledger.sh checkout <dir>
# .github/qualification-ledger.sh publish <dir> "<commit message>"
#
# The ledger is an orphan branch that only grows. Each attempt writes its own
# directory (uk/<pair_id>/attempts/<key>/), which concurrent publishes never
# share. A rejected push is retried by replaying the single new commit on the
# latest tip; the new commit may be the branch's first (two runs can both find
# no ledger yet), so it is replayed with cherry-pick, which needs no parent.
#
# Two attempts of one pair do share that pair's published files
# (qualification_receipt.json, latest_attempt.json and pair.json). Git must
# never merge those line by line: a textual merge of two receipts can produce
# a valid-looking receipt that is neither (one attempt's harness with another's
# replay). They are replayed as binary, so a concurrent change to them
# conflicts. When those are the only conflicts and LEDGER_REDERIVE is set, the
# tip's copies are kept and "$LEDGER_REDERIVE <dir>" recomputes every pair's
# published files from its immutable attempts; otherwise the publish fails.
set -euo pipefail

BRANCH="${LEDGER_BRANCH:-uk-qualification-ledger}"
command="${1:?usage: qualification-ledger.sh checkout|publish <dir> [message]}"
dir="${2:?ledger directory required}"

case "$command" in
checkout)
if git ls-remote --exit-code --heads origin "$BRANCH" >/dev/null 2>&1; then
git fetch --quiet origin "$BRANCH"
git worktree add --quiet --force -B "$BRANCH" "$dir" FETCH_HEAD
else
git worktree add --quiet --detach "$dir"
(
cd "$dir"
git switch --quiet --orphan "$BRANCH"
cat > README.md <<'README'
# UK candidate qualification ledger

Written by `.github/workflows/certify-uk-release.yaml` in PolicyEngine/policyengine.py.
Each `uk/<pair_id>/` directory holds a candidate pair, every qualification
attempt's gate evidence, the replay request and receipt, and the latest
qualification receipt. Nothing is deleted.
README
)
fi
;;
publish)
message="${3:?commit message required}"
cd "$dir"
git add -A
if git diff --cached --quiet; then
echo "Ledger unchanged."
exit 0
fi
identity=(-c user.name="policyengine-qualification[bot]"
-c user.email="qualification@policyengine.org")
git "${identity[@]}" commit --quiet -m "$message"
new_commit="$(git rev-parse HEAD)"
delay="${LEDGER_RETRY_DELAY:-5}"
attributes="$(mktemp)"
trap 'rm -f "$attributes"' EXIT
printf '%s\n' \
'qualification_receipt.json merge=binary' \
'latest_attempt.json merge=binary' \
'pair.json merge=binary' > "$attributes"
for attempt in 1 2 3 4 5 6; do
if git push --quiet origin "HEAD:refs/heads/$BRANCH"; then
echo "Published ledger: $message"
exit 0
fi
# Someone else published first: replay this one commit on their tip.
if ! git fetch --quiet origin "$BRANCH"; then
sleep $((attempt * delay))
continue
fi
git reset --quiet --hard FETCH_HEAD
if ! git "${identity[@]}" -c core.attributesFile="$attributes" \
cherry-pick --allow-empty "$new_commit" >/dev/null 2>&1; then
conflicted="$(git diff --name-only --diff-filter=U)"
shared_only=true
while IFS= read -r path; do
[ -z "$path" ] && continue
case "$path" in
uk/*/qualification_receipt.json|uk/*/latest_attempt.json|uk/*/pair.json)
# Only a pair's published files, never an attempt's own.
case "$path" in uk/*/*/*) shared_only=false ;; esac ;;
*) shared_only=false ;;
esac
done <<< "$conflicted"
if [ -z "$conflicted" ] || [ "$shared_only" != true ] \
|| [ -z "${LEDGER_REDERIVE:-}" ]; then
git cherry-pick --abort || true
echo "::error::The ledger commit conflicts with a concurrent publish."
exit 1
fi
while IFS= read -r path; do
git checkout --ours -- "$path"
done <<< "$conflicted"
# Recompute published files from the attempts, the tip's and this one's.
$LEDGER_REDERIVE "$PWD"
git add -A
# Completes the cherry-pick, keeping its message, even if empty.
git "${identity[@]}" commit --quiet --allow-empty --no-edit
fi
sleep $((attempt * delay))
done
echo "::error::Could not publish the qualification ledger after 6 attempts."
exit 1
;;
*)
echo "Unknown command: $command" >&2
exit 2
;;
esac
Loading
Loading