Release scope
August 27, 2026 · View on GitHub
Grounded in git diff v4.0.0..<candidate> -- src/ (41 files, +1130/−146) and the commit log, not a
recollection — the 4.0.0 Nachtrag lesson: a scope list that omits what actually ships is the defect it
exists to prevent. Honest limit: the categories below are read from the commit subjects + the file-level
diff; re-confirm the src diff against the final merged HEAD before the tag.
In
Headline feature — reported-version status (the MAJOR-carrying user-facing change)
- Every provenance field carrying a harness-reported version (
harness_version,task_version,promptfoo_version) now also carries<field>_status∈ {reported,not_reported,not_bound} with a<field>_status_reasonmandatory when the status is notreported. The absence of a version field stops meaning two things at once (66e72f4). Backwards compatible: the version field itself is unchanged, the status is never derived,not_reportednever folds to PASS. Verifierversion_status_issues()rejects an unknown literal / missing mandatory reason / status-field contradiction; conformance vectorsconformance/provenance/version-status-*. Touchesoutcome.py,verification_summary.py, the provenance verifier, and the harness adapters (promptfoobindsharness_version— the class, not the instance,529cd20;inspect_hook.py/pytest_plugin.py/hf_evals.py).
Never-raise / fail-closed hardening across the verify surfaces (the makellose-500 work)
A sweep closing "a public verify surface raises a raw exception on a malformed producer argument instead of ruling" as a CLASS, not per-instance — each fix is fail-closed (typed refusal, never a false accept), landed with property/mutation tests:
renewal.py(+233, the largest): non-int.time/ giant-int magnitude / malformed signed fields reach a fail-closed refusal instead of a raw crash; a_never_raise_verdictdecorator makes verify-sequence never-raise by construction (6aef259, iter9 L2; earlier38a672a/880ca55).merkle.py(+43):verify_consistencybindssecond_size(sn==0notfn==0,681bfe6).kbjwt.py(+41),sdjwt.py/sdjwt_issue.py/sdjwt_vc.py,tlogproof.py(+27),statuslist.py,relation.py/relation_statement.py,persample.py(+39),policy_profiles.py,run_ledger.py,public_transparency.py: the same never-raise / type-confusion class closed on each (26a42besdjwt-vc P0 false-accept + rp_headers TypeError;f403360/58403ef/2da2644anchorsisinstance(Mapping)proves no usable.get;7581bd7the three surviving never-raise findings).- Membership guard (
_membership.py): 27 membership tests hashed attacker data — fixed as the class, not the 27 lines (fd84e1d); and the guard whose contract is "never raises" could raise — closed (8ef363f). - Wire-form (
d478882): a signed receipt has again EXACTLY one accepted wire form.
Pre-tag ceremony + release infrastructure shipped in this tag (not library API — recorded for completeness)
- Pre-tag receipt ceremony option C:
subject_tree_digestbinds the HEAD top-level tree minusaudit_artifacts/— binds src + pyproject (deps) + scripts, keeps the receipt committable, closes a post-signing dependency-injection supply-chain hole (deep-gate refuted the src-only option B). - Two-half keyless receipt mode (
pre_tag_receipt.py --emit-payload/--assemble) for the Mac key-custody handshake — the private release key never reaches the build host. - P1-A: the pre-tag gate no longer crashes on
import proofbundlewhen run bare (the CI condition), fail-closed on any verify exception. - Gate-qualification harness hardening (cc-classes + AST-count meta-tests; option-C dep-injection regression).
audit_candidate_matrixversion_pin: re-baselined to 5.0.0 at the vor-tag (P3-3). The deep-gate L6-01 fix is the BINDING (version_pin_bindingdetects a pin/package drift and withholds readiness with a named reason + nonzero exit) — and because that binding is now in place, pulling the literal to the shipping version is the correct release step, not the recreated defect the docstring warned about (that warning was against bumping the literal without the binding). The drift the binding catches is now exercised SYNTHETICALLY (test_drift_withholds_readinessmonkeypatches an older pin) instead of relying on the live 3.6.0-vs-5.0.0 mismatch; a 5.0.0release_evidence_slotsentry is added. C12.1 (the signed pre-tag receipt) stays FAIL until the Mac key-custody handshake at tag time — expected, not a defect.
Out (explicitly deferred, no semantic change withheld from a caller who needs it)
- Zenodo academic Technical-Note deposit — a separate scholarly artifact with its own DOI track
(
10.5281/zenodo.21230466), owner-gated, NOT part of this software release (Owner G1 = A, 2026-08-27). The software concept DOI (10.5281/zenodo.21110642) IS minted automatically with the release, as for every release of this repo.
Why MAJOR (the SemVer question, answered) — TWO independent triggers, both recorded
- Exit-code contract change. An input class that previously exited 2 (malformed/usage) now
exits 1 (crypto failure/verdict).
SPEC.mdis normative (RFC 2119); a declared exit-code contract is public API → SemVer MAJOR (Owner decision 2026-08-18). - A threshold is now required for a verdict. The Inspect lifecycle hook and the pytest plugin
previously defaulted
PROOFBUNDLE_THRESHOLDto"0"(passed for any non-negative score). Both now require it and skip emission when unset — a caller that relied on getting a receipt gets none. Migration:export PROOFBUNDLE_THRESHOLD=0.
No verdict flips: nothing that verified before stops verifying, nothing that failed starts passing. The
never-raise sweep only turns raw crashes into typed refusals — no input that verified now fails. The 4.x
line ends here (no maintenance branch, per SECURITY.md).