Repository navigation
Unblock 1.7.10: opt-in signed release qualification, atomic installer write, faster CI - #283
Merged
Coding-Dev-Tools merged 5 commits intoOct 25, 2026
Conversation
Since #217 every PyPI and GitHub release write required an Ed25519-signed envelope covering all 11 RELEASE_GATES, including a multi-week human pilot and a first-time-developer usability study. The four secrets it reads have never been configured, so v1.7.6, v1.7.8 and v1.7.9 could only ship through hard-coded per-release waiver inputs, and v1.7.10 has been blocked since 2026-10-18. The four "Require signed full-product qualification" steps now run only when the repository or environment variable REQUIRE_SIGNED_QUALIFICATION is 'true'. Unset, a release still needs every build gate (tests on 3.9-3.14, evals, pyright, CodeQL, audits, browser/Pi/container gates, installed artifact smokes, reproducibility) and approval of the protected release-qualification environment. The waive_v176/v178/v179_qualification inputs are removed, together with the waiver guard, the pre-PyPI waiver disclosure step and the waiver branches of the GitHub repair step. Repair keeps its retained-run artifact and evidence hash checks. When it creates a release that does not exist yet, it still refuses to displace a newer Latest. The release-contract tests now assert the new contract: each qualification step is present, reads only protected secrets, precedes every publication write and is guarded by the variable, and no waiver inputs remain. docs/RELEASE_QUALIFICATION.md documents the variable. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0111byzLrKKTCJ23uTT6PyZq
_write_settings() already staged settings.json in a temp file, fsynced it and moved it into place with os.replace(), so a crash can no longer leave a truncated file. Two gaps remained: - json.dumps(..., sort_keys=True) re-sorted every key in the user's hand-maintained settings file on each install and uninstall. Keys now keep their original order, so an install-then-uninstall round trip is byte-identical. - The directory entry was never synced after the replace, so a power loss could still roll the rename back. The parent directory is now fsynced on POSIX. This is best effort: a filesystem that refuses a directory fsync does not turn a completed install into a reported failure. The script stays stdlib-only, so it mirrors engraphis.private_state.atomic_private_text instead of importing it. That helper also defaults to 0o600 and treats a concurrent edit as a conflict. Everything else behaves as before. New tests cover key-order preservation through install and uninstall, an interrupted replace leaving the previous file intact with no temp files, fsync ordering (file, then directory), and a refused directory fsync. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0111byzLrKKTCJ23uTT6PyZq
CI ran the whole ~10.7k-test suite in one process, and the coverage gate only failed below 60% while measured line coverage is 86.7%. That floor would let roughly a quarter of the tested code lose its tests before CI noticed. - Add pytest-xdist>=3.6 to the test extra. ci.yml now runs the full-stack test matrix and the coverage job with `-n auto`. Each worker gets its own subdirectory under --basetemp, which stays inside ENGRAPHIS_INDEX_ROOTS. Locally on two cores the suite with coverage went from 1266s serial to 657s, with identical results. - Raise --cov-fail-under from 60 to 80, a few points below the measured 86.74% (41232 statements, 5467 missed). - Fix the one test that was not xdist-safe: test_unrecognized_marker_fails_closed_without_changing_checkpoint_files put os.getpid() in its parametrize id, so xdist workers collected different test ids and aborted. The pid case now has a fixed id, and the payload is unchanged. These stay serial on purpose: core-py39 (a numpy+pytest-only floor job), the single-file encryption job, and release.yml, whose public release evidence records the exact pytest command. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0111byzLrKKTCJ23uTT6PyZq
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Coding-Dev-Tools
merged commit Oct 25, 2026
aade8c6
into
codex/release-1-7-10-reconciled
29 checks passed
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.
Why
Nothing has reached PyPI since 1.7.9 on 2026-10-01, and #270 (1.7.10) has been green but blocked since 2026-10-18.
release.ymlwrite to PyPI or GitHub has had to passscripts.verify_release_qualification. That step checks an Ed25519-signed envelope covering all 11RELEASE_GATES, including a multi-week human pilot (five developers for a clean week, then twenty repositories for two more weeks) and a usability gate (at least 11 of 12 first-time developers reach useful recall unaided). The gate definitions are indocs/RELEASE_READINESS.md.ENGRAPHIS_RELEASE_QUALIFICATION,ENGRAPHIS_RELEASE_VERIFY_KEY,ENGRAPHIS_RELEASE_CANDIDATE_ID,ENGRAPHIS_RELEASE_LEDGER_SHA256) have never been configured. A tag push therefore always fails at the publisher.waive_v176/v178/v179_qualificationrepair inputs. Every patch release needed a new workflow edit just to waive the gate again.For a patch release from a solo maintainer, the existing build gates plus the protected-environment approval are the right bar. The signed 11-gate qualification is still worth having for a major release or a public claim, so this PR keeps it as an opt-in switch rather than deleting it.
This is a stacked PR on
codex/release-1-7-10-reconciled(#270's head,79b0502). That branch rewritesrelease.yml,install_cc_hook.pyandpyproject.tomlheavily, so basing this PR onmainwould conflict.What changed (one commit per theme)
1.
ci(release): make signed full-product qualification opt-inRequire signed full-product qualification …steps (publish,github-release, and both writes ingithub-release-repair) now run only whenvars.REQUIRE_SIGNED_QUALIFICATION == 'true'. The two repair steps' old!inputs.waive_*conditions are replaced by the same guard.waive_v17x_qualificationdispatch inputs, the waiver-guard step, the pre-PyPI waiver-disclosure step, and the waiver branches, env and notes text inRepair GitHub Release.workflow_dispatchnow takes onlyrelease_tag.main, full pytest on 3.9–3.14, evals, pyright, CodeQL, pip/npm audits, browser/Pi/container gates, installed-artifact smokes), the two-builder reproducibility check, both SBOMs and the grype gate, trusted publishing, the post-publish PyPI file-set check, the retained-run hash verification in repair, andenvironment: release-qualificationon all three writer jobs.docs/RELEASE_QUALIFICATION.mdnow opens with "What a release requires": green release gates plusrelease-qualificationapproval for a patch release. It explains settingREQUIRE_SIGNED_QUALIFICATION=true(repo or environment variable) to bring back the signed 11-gate qualification for major releases or public claims. It also records that the 1.7.6–1.7.9 waivers were removed and that skipping qualification is never recorded as a pass. The signed-envelope contract is kept as-is.docs/RELEASE_READINESS.mdanddocs/REWORK_EXECUTION.mdare updated so they no longer say every release needs the signed approval.tests/test_release_qualification.pychecks that each writer job is still inrelease-qualification, and that each qualification step exists, is guarded by exactlyvars.REQUIRE_SIGNED_QUALIFICATION == 'true', reads onlysecrets.*(nevervars.*) and precedes every PyPI or GitHub write. It also checks that dispatch inputs are exactly{release_tag}, that nowaivetext remains, and that repair writes only verified assets and generated notes.tests/test_release_infrastructure.pyasserts the guard text inside the publish and GitHub-release qualification steps.if:makes the contract test fail.2.
fix(install_cc_hook): keep the operator's key order and sync the rename_write_settings()was already atomic: temp file in the same dir, fsync,os.replace, mode preserved. The zero-filled-file crash risk in the audit applies tomain's version, which Prepare 1.7.10: preserve historical memory and repair hosted client flows #270 already replaces.sort_keys=True, so the user's key order is preserved and an install→uninstall round trip is byte-identical.engraphis.private_state.atomic_private_textrather than importing it. That helper also defaults to0o600and raises on concurrent edits, which isn't the semantics this installer needs.3.
ci: run the suite with pytest-xdist and raise the coverage floorpytest-xdist>=3.6is added to thetestextra.ci.yml's full-stacktestmatrix and thecoveragejob now run-n auto. Each worker gets its own--basetempsubdir, which stays insideENGRAPHIS_INDEX_ROOTS.--cov-fail-undergoes from 60 to 80.core-py39: numpy+pytest-only floor job; I'm not adding deps there.encryption: one file, and kept short-lived because of SQLCipher.release.yml:scripts/release_evidence.pyrecords the exact pytest command in the public evidence, and the release path should favour fidelity over speed.tests/test_external_checkpoints.py::test_unrecognized_marker_fails_closed_without_changing_checkpoint_filesputos.getpid()in its parametrize id, so the workers collected different test ids and xdist aborted ("Different tests were collected between gw0 and gw1"). The fix gives that case a fixedid="pid"; the payload is unchanged. No other test needed serialising.Test results (local, Python 3.13, 2 cores, venv with
pip install -e ".[test]")codex/release-1-7-10-reconciled@79b0502, serial + coverage (what CI does today)-n auto(2 workers) +--cov-fail-under=80(the new CI coverage job)ruff check .on this branchencryptionextra (it has its own CI job), plus Windows/macOS-only tests, a release-only CycloneDX test, and a Docker-image test.How to ship 1.7.10 after this
codex/release-1-7-10-reconciled.main.main:git tag v1.7.10 <merge-sha> && git push origin v1.7.10. The tag must be onmainand matchpyproject.toml(1.7.10).Publish to PyPIand thenPublish GitHub Releasepause, approve therelease-qualificationenvironment for each.REQUIRE_SIGNED_QUALIFICATIONunset for this patch release.release-qualificationhas you as a required reviewer. This PR assumes that protection exists; I couldn't read environment settings from this session.Notes / follow-ups (not done here)
AGENTS.md§1 still says--cov-fail-under=60. I was asked not to touchAGENTS.md, so it needs a one-word follow-up edit.CHANGELOG.mdis unchanged. Add a line under 1.7.10 if you want the release notes to mention that the qualification is now opt-in.Generated by Claude Code