The install finished and then could not download a 230 KB file

With the version ceiling and the widened numpy pin in place, setup succeeded
on the Mac that found them - Python 3.14 chosen and accepted, numpy 2.5.3,
onnxruntime 1.30, faiss 1.15.1, the engine itself - and died on the last step,
fetching the YuNet model:

  ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED]
  certificate verify failed: unable to get local issuer certificate

A python.org macOS build ships its own OpenSSL with NO trust store, and
populates one only when somebody double-clicks Install Certificates.command in
the Python folder. Nobody installing face-recognition software has a reason to
know that exists, and the failure is forty lines of traceback about _ssl.c at
the end of a ten-minute install.

_urlopen tries the default context first and retries with certifi's bundle on
a verification failure. The order is the design:

- Default first, because on Windows and on a system or Homebrew Python the
  default context reads the machine's own certificate store, which is what
  makes a corporate proxy with its own root CA work. Replacing it
  unconditionally would break every site that has one to fix a different
  platform.
- certifi second, because it is already installed: requests is a hard
  dependency and brings it.
- URLError is re-raised untouched. "No route to host" and "no trust store" are
  different problems, and retrying the first with a different CA list only
  delays the real message.

urlretrieve had to go, since it offers no way to pass a context - exactly the
kind of rewrite that silently drops something. The `download: <label> <n>%`
lines are a contract: supervisor.go's progressRe parses them to put first-run
progress in the tray, because the API is not up yet and a shop PC showing a
stopped engine for five minutes looks broken. A test asserts them, and the
rewritten fetch was checked against the real URL: 232,589 bytes, sha256
identical to the model already on disk.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KGcjxF1cNLcuwc3DAPcnfj
This commit is contained in:
2026-09-30 17:21:41 +05:30
parent 248025cdf9
commit 3cddd9c2e1
3 changed files with 211 additions and 2 deletions

View File

@@ -3559,3 +3559,43 @@ one, and why installing into it would not survive a restart. Fixed by dragging
the app to Applications; saying nothing leaves somebody re-running a setup tool
that cannot win. The product is unsigned, so this is the *normal* first-run
state on every Mac, not an edge case.
### And then it could not download a 230 KB file
With all of the above fixed the install succeeded on that Mac - Python 3.14
chosen and accepted, numpy 2.5.3, onnxruntime 1.30, faiss 1.15.1, the engine
itself - and setup died on the last step, fetching the YuNet model:
```
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED]
certificate verify failed: unable to get local issuer certificate
```
A python.org macOS build ships its **own OpenSSL with no trust store**, and
populates one only when somebody double-clicks `Install Certificates.command`
in the Python folder. Nobody installing face-recognition software has any
reason to know that exists, and the failure is forty lines of traceback about
`_ssl.c` at the end of a ten-minute install.
`_urlopen` tries the default context first and retries with **certifi's**
bundle on a verification failure. The order is the whole design:
- Default first, because on Windows and on a system or Homebrew Python the
default context reads the machine's own certificate store - which is what
makes a corporate proxy with its own root CA work. Replacing it
unconditionally would break every site that has one in order to fix a
different platform.
- certifi second, because it is already installed: `requests` is a hard
dependency and brings it.
- A `URLError` is re-raised untouched. "No route to host" and "no trust store"
are different problems, and retrying the first with a different CA list only
delays the real message.
`urlretrieve` had to go, since it offers no way to pass a context - and that is
exactly the kind of rewrite that silently drops something. The
`download: <label> <n>%` lines are a **contract**: `supervisor.go`'s
`progressRe` parses them to put first-run progress in the tray, because the API
is not up yet and a shop PC showing a stopped engine for five minutes after
install looks broken. `tests/test_model_download.py` asserts them, and the
rewritten fetch was checked against the real URL: 232,589 bytes, sha256
identical to the model already on disk.