You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
The notary preflight exits 1 ("Apple refused the credentials") when its --env-file cannot be read, instead of reporting "could not measure" #1886
scripts/check-notary-credentials.py documents two ways to name the three notarization credentials, and the second one hands the tool a path: DEVELOPMENT.md recommends --env-file ~/.emrg/notary.env so the secrets stay out of the shell history. A path is the half a host gets wrong — a typo, or the file not created yet — and today that mistake is answered with a traceback and exit 1.
1 is a verdict in this tool's exit table, and it is the code the table gives Apple refused the credentials. Its documented remedy sends the host hunting an expired or revoked app-specific password, an Apple ID / team ID mismatch, or an unaccepted Developer Program agreement — over a mistyped path. That is the false diagnosis the preflight exists to remove, one code over.
Measured on this host, 2026-10-07, with ~/.emrg/notary.env absent (none of APPLE_ID, MACOS_NOTARY_APP_PASSWORD, MACOS_NOTARY_TEAM_ID set):
The failure this preflight is a host-side counterpart for is a blocked release: on v0.3.8 (run 36956685533) the Windows and both Linux builds succeeded and verify-tag succeeded, but build (macos-15, arm64) failed at step 18 "Notarize pkg (macOS only)" in 3 seconds — a refused submission — so the release job was skipped and nothing was published at all. A host in exactly that situation runs this command to find out why, and a mistyped --env-file answers 1, then sends them to re-check a credential set that was never even read.
What the fix looks like
An --env-file that cannot be read (missing, a directory, undecodable) answers 2 — "no verdict was reached" — with the reason and the path in the message, and never a traceback.
The environment is not read instead: falling back to whatever else happens to be exported would measure a credential set the caller did not name, which is the same false confidence as a pass. The message says so out loud.
The tool gains the family's _entry() wrapper (scripts/check-notary-credentials.py answers 1 as a verdict, so an uncaught exception was read as Apple's refusal) and joins tests/test_a_crash_is_a_measurement_error.py's list.
The tests hold both halves: unreadable → 2 with the path named and no verdict printed, and a readable file still carries its values into the call (so "never read the file" cannot satisfy the first half).
DEVELOPMENT.md's exit-code line names the new 2 case.
Acceptance
uv run pytest tests/ -v passes.
--env-file <missing path> exits 2, names the path, prints no traceback, and prints no credential verdict — with the three variables both unset and set in the environment.
--env-file <a readable file> still exits 0 on accepted credentials.
Also in this PR: the arm that failed on Windows is now impossible to add silently
The Windows leg's red came from the fix's own control: it asserted the exit code the exchange decides, so it read the stand-in's absence rather than the file. The gate _posix_only already existed in that module for exactly this; nothing could see that one arm was missing it, because on a POSIX host the arm was green and the file's suite said nothing about the other platform.
So the gate is mechanised on the same head: Path.chmod is neutralised (the stand-in keeps the mode write_text gave it, so the preflight's own subprocess.run refuses it — the branch the Windows leg takes, reproduced on whichever platform runs the suite), and every arm that runs the preflight through the file's _run driver without the gate must pass under that condition. The arms are derived from the module; the class boundary (arms that drive the preflight) is stated and both controls are in the file — an ungated arm that needs the stand-in fails the clause, and an arm that is gated must fail under the condition, so a condition that changes nothing cannot certify anything.
Acceptance for this half:
Removing @_posix_only from an arm that needs the stand-in fails the clause on its own assertion.
Suppressing the chmod neutralisation fails the clause's premise assertion (a condition that was never created is never reported as coverage).
An ungated arm that tolerates the condition (the env-file arms, and the header control) passes, on both platforms.
Activity
how2how2how2-arch commentedon Oct 6, 2026
Handled by #1887
how2how2how2-arch commentedon Oct 7, 2026
Also in this PR: the arm that failed on Windows is now impossible to add silently
The Windows leg's red came from the fix's own control: it asserted the exit code the exchange decides, so it read the stand-in's absence rather than the file. The gate
_posix_onlyalready existed in that module for exactly this; nothing could see that one arm was missing it, because on a POSIX host the arm was green and the file's suite said nothing about the other platform.So the gate is mechanised on the same head:
Path.chmodis neutralised (the stand-in keeps the modewrite_textgave it, so the preflight's ownsubprocess.runrefuses it — the branch the Windows leg takes, reproduced on whichever platform runs the suite), and every arm that runs the preflight through the file's_rundriver without the gate must pass under that condition. The arms are derived from the module; the class boundary (arms that drive the preflight) is stated and both controls are in the file — an ungated arm that needs the stand-in fails the clause, and an arm that is gated must fail under the condition, so a condition that changes nothing cannot certify anything.Acceptance for this half:
@_posix_onlyfrom an arm that needs the stand-in fails the clause on its own assertion.