Task: P3.9. Constraint index: A6. Model: Sonnet.
⚠ RESOLVED 2026-07-13 — read this first. Everything below is the P3.9 investigation exactly as it was run, against a build whose Python had almost every optional C-extension deselected. Its two blocking recommendations were applied in the same commit that added this document (
a549bd0):configs/fragments/de10nano-image.fragmentnow setsBR2_PACKAGE_PYTHON3_{SSL,ZLIB,BZIP2,XZ,PYEXPAT,READLINE,CURSES}=y, andscripts/ci-tests.sh's "P3.9 — Python & Downloader ABI gate" imports all seven underqemu-armon every run. The image no longer ships a Python that cannotimport ssl.What remains open from this report:
downloader.sh's hardcoded/usr/bin/python3.9fast-path gate (§3.6), andsqlite3/_decimal, still deliberately off (stock's 3.9 shipped neither). The 3.14.5 evidence below is left exactly as measured — the pin is now 3.14.6, and retyping it would assert an experiment nobody re-ran.
Bottom line up front: the on-device python3 as built at the time of this
investigation was broken for Downloader_MiSTer and for "Update All" (the most
widely used community updater) — not because of a 3.9→3.14 language/stdlib-removal
issue, but because the Buildroot python3 package had almost every optional
C-extension submodule deselected, including ssl. Both tools do import ssl on their core HTTP path and both were actually run
(not just statically inspected) under the real target ARM Python 3.14.5 via
qemu-arm, and both crash identically: ModuleNotFoundError: No module named '_ssl'.
Once that (and one more: zlib) is fixed, the evidence gathered here is that
Downloader's own source is otherwise clean for 3.14 — no removed-stdlib imports, and
100% of its .py files byte-compile without error under the real 3.14 compiler.
Honesty note up front: Downloader_MiSTer's own automated test suite
(src/automated_tests.sh → test/unit, test/integration, test/system/quick,
test/system/slow) is not available to us. src/test/ is explicitly gitignored in
the public repo, and .github/workflows/request_tests.yml shows the real suite lives in
a private repo, run via a repository_dispatch to a ${{ secrets.TESTS_REPO }} we
have no access to. See §1. This is a hard scoping fact discovered during this task, not
an assumption — I looked for a public mirror and found none. In its place, §3 actually
executes Downloader's and Update All's real, unmodified entry points against the real
target interpreter, which is a stronger signal than the unit tests would have given for
this specific question (they run on host CPython 3.9 in CI, not under qemu-user on the
target ABI) but does not substitute for their assertions on internal logic.
- Local clone:
work/Downloader_MiSTer,HEAD=9153156(915315668b9460b0fcdfc728be8254fe698c479f, branchmain), matching the commit pinned indocs/downloader-contract.md. Working tree clean. work/Downloader_MiSTer/src/automated_tests.shrunspython3 -m unittest discover -s test/unitetc. — butsrc/test/does not exist in the checkout.work/Downloader_MiSTer/.gitignoreline 10:src/test/— the test tree is deliberately excluded from the public repo, not merely absent from this clone.work/Downloader_MiSTer/.github/workflows/request_tests.yml— on every push tomain, it does not run tests in this repo. It fires an authenticatedrepository_dispatch(event_type: test-sha) to${{ secrets.TESTS_REPO }}using${{ secrets.TESTS_TOKEN }}, i.e. the real suite runs in a separate, private repository we have no credentials for.- Confirmed no public repo fills this role:
curl https://api.github.com/orgs/MiSTer-devel/repos?per_page=100lists no downloader/test-shaped repo, andcurl https://api.github.com/search/repositories?q=org:MiSTer-devel+downloader+testreturns"total_count": 0. - Searched all of
work/for any cached copy oftest/unitetc. — none found.
Conclusion: "Downloader suite green on-target-Python" (the task's literal Done-when clause) cannot be produced. This is reported, not worked around by inventing a substitute test suite and calling it the same thing. §3 below is what was actually run instead, and is clearly labeled as such throughout.
No rebuild was performed. Everything below runs against the existing
output/target/usr/bin/python3.14 (ELF32 ARM, EABI5, hard-float, armv7l) from the
current build.
sudo/root is not available in this environment (no passwordless sudo), so a real
chroot(2) was not possible. Per the task's own fallback instruction ("invoke qemu-arm … python3.14 directly with the right library path"), and consistent with the precedent
already in this repo (scripts/inventory/gen-busybox.sh, docs/abi-contract.md §2.4 use
exactly this pattern for the stock MiSTer binary), I used QEMU user-mode's -L
sysroot-prefix instead of a real chroot:
$ qemu-arm -L output/target output/target/usr/bin/python3.14 --version
Python 3.14.5-L makes qemu-arm resolve the ELF interpreter (/lib/ld-linux-armhf.so.3) and all
absolute-path opens the guest makes (library search, /usr/lib/python3.14/... stdlib
imports, /etc, /tmp etc.) against output/target first. This is not a real
chroot — no mount namespace, no root — but it is sufficient to run the actual target
ARM binary against its actual target library/stdlib tree, which is what matters for an
ABI-compatibility question. (binfmt_misc on this host also has qemu-arm registered
with the F "fix binary" flag, so a real chroot — if root were available — would not
even need qemu-arm-static copied into output/target/usr/bin/ first; noted for anyone
re-running this with root.)
$ qemu-arm -L output/target output/target/usr/bin/python3.14 -c \
"import sys,platform; print(sys.version); print(platform.machine()); print(platform.architecture())"
3.14.5 (main, Jun 16 2026, 21:01:40) [GCC 14.3.0]
armv7l
('32bit', 'ELF')
$ python3 --version # host, for contrast
Python 3.14.4Different patch version (3.14.5 target vs 3.14.4 host at time of writing) and a 32-bit
armv7l platform tag — this is unambiguously the emulated target binary, not the host
interpreter answering by mistake.
One environment-hygiene note: without -S/-I/PYTHONNOUSERSITE, sys.path leaked in
/home/mcf/.local/lib/python3.14/site-packages (a host path) because HOME wasn't
overridden. This never affected any result below (nothing relevant to stdlib C
extensions lives there), but all commands in §3 onward were run with PYTHONNOUSERSITE=1
(entry-point tests, to preserve normal site.py behavior like the exit/quit
builtins) or -S (bare import/compile checks) to keep the target run honest.
output/.config (the config that produced the current output/ build):
BR2_PACKAGE_PYTHON3=y
BR2_PACKAGE_PYTHON3_PYC_ONLY=y
# BR2_PACKAGE_PYTHON3_2TO3 is not set
# BR2_PACKAGE_PYTHON3_BERKELEYDB is not set
# BR2_PACKAGE_PYTHON3_BZIP2 is not set
# BR2_PACKAGE_PYTHON3_CODECSCJK is not set
# BR2_PACKAGE_PYTHON3_CURSES is not set
# BR2_PACKAGE_PYTHON3_DECIMAL is not set
# BR2_PACKAGE_PYTHON3_READLINE is not set
# BR2_PACKAGE_PYTHON3_SSL is not set
BR2_PACKAGE_PYTHON3_UNICODEDATA=y
# BR2_PACKAGE_PYTHON3_SQLITE is not set
# BR2_PACKAGE_PYTHON3_PYEXPAT is not set
# BR2_PACKAGE_PYTHON3_XZ is not set
# BR2_PACKAGE_PYTHON3_ZLIB is not set
# BR2_PACKAGE_PYTHON3_ZSTD is not set
# BR2_PACKAGE_PYTHON3_OSSAUDIODEV is not set
Every optional submodule is off except UNICODEDATA. This reads like the Buildroot
python3 package's stock defaults were taken as-is rather than chosen against what the
target actually needs to run. output/target/usr/lib/python3.14/lib-dynload/ (the
actually-built .so extension modules) confirms it — 41 files, and _ssl, zlib,
_bz2, _lzma, _sqlite3, _curses, readline, pyexpat, _decimal are all
absent.
work/Downloader_MiSTer/src/downloader/http_gateway.py:20: import ssl
work/Downloader_MiSTer/src/downloader/ssl_context.py:19: import ssl
http_gateway.py builds HTTPSConnection objects directly from http.client — this is
Downloader's entire network layer, not an optional feature. downloader/config.py
imports HttpConfig from http_gateway.py at module scope
(downloader/config.py:28), and nearly every other module imports config.py
transitively — so the ssl dependency is not confined to networking code, it poisons
almost the whole package import graph (quantified in §3.4).
$ cd work/Downloader_MiSTer/src
$ PYTHONNOUSERSITE=1 qemu-arm -L ../../../output/target \
../../../output/target/usr/bin/python3.14 __main__.py
No module named '_ssl'
Warning! Your OS version seems to be older than September 2021!
Please upgrade your OS before running Downloader
More info at https://github.com/MiSTer-devel/mr-fusion
$ echo $?
10Exit code 10 is EXIT_ERROR_WRONG_SETUP (work/Downloader_MiSTer/src/downloader/constants.py:239).
__main__.py's own except (ImportError, SyntaxError) handler
(src/__main__.py:25-34) catches the ModuleNotFoundError from from downloader.main import main and prints a misleading diagnostic — it blames OS age ("older than
September 2021"), which is backwards: our OS/kernel is from 2026, five years newer than
that message implies is required; the real cause is a missing Python C extension, not an
old kernel. Anyone hitting this on a real device would be pointed at the wrong problem.
3.4 Blast radius: 70 of 88 Downloader submodules fail to import standalone, all at the same choke point
$ for f in downloader/*.py downloader/jobs/*.py downloader/migrations/*.py; do
mod=$(echo "$f" | sed 's#/#.#g; s#\.py$##')
qemu-arm -L ../../../output/target ../../../output/target/usr/bin/python3.14 -S -c "import $mod"
done70 / 88 fail identically:
File "/usr/lib/python3.14/ssl.py", line 100, in <module>
import _ssl
ModuleNotFoundError: No module named '_ssl'
(via downloader.config → downloader.http_gateway → ssl, for anything that imports
config, which is almost everything).
18 / 88 import cleanly standalone (pure data/scaffolding modules with no config
dependency): constants, db_entity, db_options, error,
external_store_fingerprints, fail_policy, job_system, os_utils, path_package,
waiter, jobs.copy_data_job, jobs.errors, jobs.fetch_data_job,
jobs.fetch_file_job, jobs.index, jobs.transfer_job, plus both package __init__.py
files. This is only useful as a lower bound on "code that is at least syntactically and
import-time clean" — it is not "code that works," since none of it is exercised beyond
import.
The task brief specifically names update_all as a script to smoke-test. update_all.sh
(fetched live from theypsilon/Update_All_MiSTer — this is not vendored in work/)
turns out to be a thin bash launcher that downloads and directly executes a
self-extracting payload; unpacking it (tail -n +8 | xzcat -d) shows it is a Python
zipapp (PEP 441) with shebang #!/usr/bin/env python3 — i.e. it runs on whatever
python3 is on $PATH, exactly like Downloader:
$ curl -fsSL https://raw.githubusercontent.com/theypsilon/Update_All_MiSTer/master/dont_download2.sh -o /tmp/dont_download2.sh
$ tail -n +8 /tmp/dont_download2.sh | xzcat -d > /tmp/update_all.pyz
$ head -c 40 /tmp/update_all.pyz
#!/usr/bin/env python3
PK... # zip magic follows the shebang lineUnzipped and run against the real target interpreter the same way:
$ unzip -q /tmp/update_all.pyz -d /tmp/dd2_zip && cd /tmp/dd2_zip
$ PYTHONNOUSERSITE=1 qemu-arm -L .../output/target .../output/target/usr/bin/python3.14 __main__.py
No module named '_ssl'
Warning! Your OS version seems to be older than September 2021!
Please upgrade your OS before running Update All
More info at https://github.com/theypsilon/ms-fusion
$ echo $?
1Same root cause (update_all/analogue_pocket/http_gateway.py:2: import ssl), same
failure mode, different exit code (1, Update All's own top-level handler). Both of
the two Python tools a real user is most likely to run on this image are non-functional
as shipped.
Downloader ships two execution forms: a Nuitka-compiled standalone binary
(downloader_bin, bundles its own runtime) and the pure-Python zipapp fallback tested
above. downloader.sh (the on-device launcher, byte-identical between
work/Downloader_MiSTer/downloader.sh and the live main branch — diffed, zero
differences) decides which one to use with a literal, hardcoded path check:
work/Downloader_MiSTer/downloader.sh:161:
if [[ -s "${LATEST_BIN_PATH}" && -x /usr/bin/python3.9 ]] ; then
...use the compiled downloader_bin fast path...
else
...fetch and run dont_download.sh, the ssl-broken zipapp from §3.3...
fi
This doesn't check "is there a python3", it checks for that exact literal path. Confirmed directly against the built image:
$ test -x output/target/usr/bin/python3.9 && echo EXISTS || echo "DOES NOT EXIST"
DOES NOT EXIST
$ ls -la output/target/usr/bin/python*
lrwxrwxrwx python -> python3
lrwxrwxrwx python3 -> python3.14
-rwxr-xr-x python3.14So on this build, downloader.sh always falls through to the broken zipapp path —
the compiled-binary fast path is never even attempted, regardless of whether that
compiled binary would itself have worked. (Whether downloader_bin — a Nuitka-frozen
binary that bundles its own CPython/OpenSSL — would run fine independent of the system
Python was not tested: no ARM build of it was fetched or available locally, and
producing/verifying one is out of scope for this task. It is very plausible it doesn't
share this bug, since Nuitka standalone builds are self-contained; this is a plausibility
argument, not a verified fact, and doesn't change that the currently-shipped launcher
logic won't reach it on this image.)
downloader/file_system.py:27 imports zipfile; load_json_from_zip()
(file_system.py:673) and unzip_contents() (file_system.py:583/590) read .zip
files, and DISTRIBUTION_MISTER_DB_URL (downloader/constants.py:31) —
the default database Downloader points at — is
https://raw.githubusercontent.com/MiSTer-devel/Distribution_MiSTer/main/db.json.zip, a
DEFLATE-compressed zip (compression is the entire point of shipping .json.zip instead
of .json). Verified directly, not inferred:
$ python3 -c "
import zipfile
with zipfile.ZipFile('/tmp/deflate_test.zip','w',zipfile.ZIP_DEFLATED) as zf:
zf.writestr('db.json', '...')" # built on host, simulating a real deflate zip
$ qemu-arm -L output/target output/target/usr/bin/python3.14 -I -c "
import zipfile
with zipfile.ZipFile('/tmp/deflate_test.zip') as zf:
zf.read('db.json')"
RuntimeError: Compression requires the (missing) zlib modulePlain zipfile.ZIP_STORED (uncompressed) archives read/write fine without zlib — but
that's not what any real-world .json.zip on GitHub is. This is a second, independent
hard blocker, distinct from _ssl, that would surface immediately after _ssl is
fixed.
From work/buildroot/package/python3/Config.in:
config BR2_PACKAGE_PYTHON3_SSL
bool "ssl"
select BR2_PACKAGE_OPENSSL
select BR2_PACKAGE_OPENSSL_FORCE_LIBOPENSSL
select BR2_PACKAGE_LIBOPENSSL_ENABLE_BLAKE2
help
_ssl module for Python3 (required for https in urllib etc).
config BR2_PACKAGE_PYTHON3_ZLIB
bool "zlib module"
select BR2_PACKAGE_ZLIB
help
zlib support in Python3
Both underlying target libraries already exist in the current build — this isn't pulling in a new dependency, just wiring Python to what's already there:
$ grep '^BR2_PACKAGE_OPENSSL=y\|^BR2_PACKAGE_LIBOPENSSL=y\|^BR2_PACKAGE_ZLIB=y' output/.config
BR2_PACKAGE_OPENSSL=y
BR2_PACKAGE_LIBOPENSSL=y
BR2_PACKAGE_ZLIB=y
$ find output/target -iname 'libssl.so*' -o -iname 'libcrypto.so*' -o -iname 'libz.so*'
output/target/usr/lib/libssl.so.3
output/target/usr/lib/libcrypto.so.3
output/target/usr/lib/libz.so.1Recommend enabling, MUST (verified blockers):
BR2_PACKAGE_PYTHON3_SSL=yBR2_PACKAGE_PYTHON3_ZLIB=y
Recommend considering, SHOULD (not proven to block Downloader specifically, but cheap, and A6 explicitly names "many community scripts" as in scope — none of these were observed missing in the two tools actually tested, so treat as defensive parity, not urgent):
BR2_PACKAGE_PYTHON3_BZIP2—bz2, used by some third-party archive-handling scripts.BR2_PACKAGE_PYTHON3_SQLITE—sqlite3, some community tools cache local state in it.BR2_PACKAGE_PYTHON3_XZ—lzma; not needed by Downloader itself (it shells out to the pinned7zabinary for.7z, perdocs/downloader-contract.md, not Python'slzma), but general-purpose scripts sometimes usetarfilewith xz.BR2_PACKAGE_PYTHON3_PYEXPAT— only if some script parses XML; not used by Downloader or the sampled community scripts.BR2_PACKAGE_PYTHON3_READLINE— quality-of-life for anyone using the Python REPL interactively over serial/SSH; irrelevant to any script run non-interactively.BR2_PACKAGE_PYTHON3_DECIMAL— the pure-Pythondecimalfallback already works (confirmed, §4.2); this only buys speed via the C accelerator.BR2_PACKAGE_PYTHON3_CURSES— no observed consumer; stock MiSTer scripts don't seem to build TUIs in Python.
I did not make any of these changes — per the task constraints, this is a report for the orchestrator to apply and rebuild.
Applied 2026-07-13 (a549bd0). Both MUSTs plus BZIP2/XZ/PYEXPAT/READLINE/
CURSES are live at configs/fragments/de10nano-image.fragment (a python3-dirclean
was required to force the rebuild). SQLITE and DECIMAL were deliberately declined —
stock's Python 3.9 shipped neither.
This is the part of A6 that is good news, and it was checked by executing the real 3.14 compiler/interpreter, not by reading the diffs between Python release notes.
$ grep -rnE '^\s*(import|from) (distutils|imp|cgi|cgitb|asynchat|asyncore|smtpd|telnetlib|nntplib|uu|xdrlib|sndhdr|ossaudiodev|spwd|nis|crypt|formatter|binhex|mailcap|imghdr|audioop|aifc|chunk|msilib|pipes)\b' \
downloader/*.py downloader/**/*.py *.py
(no matches)
$ grep -rnE 'collections\.(Mapping|MutableMapping|Sequence|Iterable|Callable|Set|MutableSet)\b' downloader/*.py downloader/**/*.py *.py
(no matches)None of distutils (removed 3.12), imp (removed 3.12), cgi/cgitb (removed 3.13),
asynchat/asyncore/smtpd (removed 3.12), or the pre-3.10 collections.<ABC>
shortcuts appear anywhere in the codebase.
$ qemu-arm -L output/target output/target/usr/bin/python3.14 -S -m compileall -q -f \
downloader __main__.py pc_launcher.py debug.py
$ echo $?
091 files (88 in downloader/ + __main__.py, pc_launcher.py, debug.py), zero
syntax/compile errors, using the actual target 3.14.5 parser/compiler — not a version
assumption.
$ qemu-arm -L output/target output/target/usr/bin/python3.14 -I -c "import decimal; print(decimal.Decimal)"
<class 'decimal.Decimal'>Not a blocker even though BR2_PACKAGE_PYTHON3_DECIMAL is off — just slower (no _decimal C
accelerator). Downloader doesn't appear to use decimal regardless (not in the grep list
above); noted only because it's a widely-used module elsewhere.
4.4 Full inventory of stdlib import attempts run against the target binary (PRE-FIX build — see the RESOLVED banner at the top)
| Module | Result | Downloader depends on it? |
|---|---|---|
ssl |
FAIL — No module named '_ssl' |
Yes, unconditionally (§3.2) — blocker |
zlib |
FAIL — No module named 'zlib' |
Yes, via zipfile DEFLATE (§3.7) — blocker |
sqlite3 |
FAIL — No module named 'sqlite3' |
No (grepped, not imported) |
bz2 |
FAIL — No module named '_bz2' |
No |
lzma |
FAIL — No module named '_lzma' |
No (uses external 7za instead) |
pyexpat |
FAIL — No module named 'pyexpat' |
No |
curses |
FAIL — No module named 'curses' |
No |
readline |
FAIL — No module named 'readline' |
No (non-interactive) |
decimal |
OK (pure-Python fallback) | No (not imported) |
hashlib (md5) |
OK — _md5 extension present |
Yes — used for file hash verification, works |
socket |
OK | Yes, works |
json |
OK | Yes, works |
zipfile (ZIP_STORED only) |
OK | Yes — but real-world zips are deflate (§3.7) |
Post-fix (2026-07-13): ssl, zlib, bz2, lzma, pyexpat, curses and
readline all import on the shipped image; scripts/ci-tests.sh's "P3.9 — Python &
Downloader ABI gate" asserts every one of them under qemu-arm on each run. sqlite3
and _decimal remain off by choice. The FAIL cells above are the recorded pre-fix
experiment and are deliberately not edited.
MiSTer-devel/Scripts_MiSTer (the repo the on-device Scripts menu actually surfaces,
per docs/package-manifest.md's scope) was fetched via the GitHub API — every file in
it is a .sh script; zero .py files. This substantially narrows A6's real exposure:
the two concrete Python consumers on a stock-shaped MiSTer are Downloader_MiSTer itself
and community add-on tools like Update All / MiSTer_SAM, not the curated Scripts menu.
Fetched live (not vendored in work/) — 4 Python files from .MiSTer_SAM/:
MiSTer_SAM_MCP.py (1067 lines, the main daemon), MiSTer_SAM_joy.py,
MiSTer_SAM_keyboard.py, MiSTer_SAM_mouse.py.
-
Grep for removed stdlib modules: none found.
-
compileall -q -f .under target 3.14: 0 errors, all 4 files. -
Actually executed under
qemu-arm -L output/target … python3.14:Script Result MiSTer_SAM_keyboard.pyRan to completion, printed Keyboard disconnected(graceful — no keyboard device present in this environment, as expected off-hardware). Exit 0.MiSTer_SAM_mouse.pySame pattern: Mouse disconnected, exit 0.MiSTer_SAM_joy.pyRan past all imports and startup logic, then FileNotFoundErroron a config file (sam_controllers.json) not present in the ad hoc test directory — an environment artifact of my test setup, not a Python-compat failure; it proves the script imports and executes real logic cleanly on 3.14.MiSTer_SAM_MCP.pyThe long-running daemon; ran silently for the full 5s timeout with no crash, no traceback (consistent with an event loop polling for hardware that isn't present in this environment). Inconclusive on functionality, but no compatibility failure observed.
No ssl/zlib-style blocker in this sample — these scripts use asyncio, configparser,
struct, subprocess, signal, glob, json, os, sys, threading, time, none
of which are affected by the gaps in §3.
This is a time-boxed sample (2 tools, ~92 files total examined), not an exhaustive census of the community-script ecosystem the task brief gestures at ("many community scripts"). Anything beyond Downloader_MiSTer, Update_All_MiSTer, and MiSTer_SAM's 4 Python files was not fetched or tested.
| # | Finding | Severity | Verified how | Upstream-fixable or blocker |
|---|---|---|---|---|
| 1 | _ssl not built (BR2_PACKAGE_PYTHON3_SSL unset) |
Blocker — FIXED a549bd0 |
Real execution: Downloader __main__.py and Update All's .pyz, both under real qemu-user target 3.14.5 |
Our Buildroot config — not a Downloader bug. BR2_PACKAGE_PYTHON3_SSL=y (defconfig:705), CI-gated |
| 2 | zlib not built (BR2_PACKAGE_PYTHON3_ZLIB unset) |
Blocker (independent of #1) — FIXED a549bd0 |
Real execution: read of a real DEFLATE zip fails under target 3.14.5 | Our Buildroot config — not a Downloader bug. BR2_PACKAGE_PYTHON3_ZLIB=y (defconfig:706), CI-gated |
| 3 | downloader.sh hardcodes -x /usr/bin/python3.9 to gate the compiled-binary fast path |
High — masks/compounds #1, means the broken fallback is always taken on this image | Read of pinned + live downloader.sh; confirmed /usr/bin/python3.9 absent on built rootfs |
Downloader's own script; worth an upstream issue, but our python3 version choice (3.14 vs stock's 3.9) is what trips it |
| 4 | Downloader source: no removed-stdlib imports, no deprecated-ABC imports, 100% of files compile clean under real 3.14 compiler | Informational (good news) | Real execution of compileall + grep |
N/A |
| 5 | bz2/sqlite3/lzma/pyexpat/curses/readline/_decimal also not built |
Low — not used by Downloader or the sampled community scripts. PARTLY FIXED a549bd0 |
Real execution: each import attempted individually against target | bz2/lzma/pyexpat/curses/readline are now built (defconfig:707-711); sqlite3/_decimal declined on purpose — stock's 3.9 shipped neither |
| 6 | Downloader's own automated test suite is inaccessible (private repo) | Scope limitation, not a code finding | Read of .gitignore + request_tests.yml + GitHub org search |
N/A — cannot be fixed from here |
| 7 | Official Scripts_MiSTer has zero Python files |
Informational — narrows A6's blast radius | GitHub API listing | N/A |
| 8 | MiSTer_SAM (4 files) — no compat issues found | Informational (good news) | Real execution + compileall | N/A |
# from repo root, branch phase3-parity, existing output/ build, no rebuild
ROOTFS=output/target
PY=$ROOTFS/usr/bin/python3.14
# 1. Prove target identity
qemu-arm -L "$ROOTFS" "$PY" -c "import sys,platform; print(sys.version); print(platform.machine())"
# 2. Reproduce the ssl blocker directly
qemu-arm -L "$ROOTFS" "$PY" -I -c "import ssl"
# 3. Reproduce the zlib blocker directly
qemu-arm -L "$ROOTFS" "$PY" -I -c "import zlib"
# 4. Run Downloader_MiSTer's real entry point (work/Downloader_MiSTer must be present)
cd work/Downloader_MiSTer/src
PYTHONNOUSERSITE=1 qemu-arm -L ../../../$ROOTFS ../../../$PY __main__.py; echo "exit=$?"
cd ../../..
# 5. Compile-check all of Downloader's source under the real 3.14 compiler
qemu-arm -L "$ROOTFS" "$PY" -S -m compileall -q -f \
work/Downloader_MiSTer/src/downloader \
work/Downloader_MiSTer/src/__main__.py \
work/Downloader_MiSTer/src/pc_launcher.py \
work/Downloader_MiSTer/src/debug.py
echo "exit=$?"
# 6. Fetch + run Update All's real fallback zipapp (network required; not vendored)
curl -fsSL https://raw.githubusercontent.com/theypsilon/Update_All_MiSTer/master/dont_download2.sh -o /tmp/dd2.sh
tail -n +8 /tmp/dd2.sh | xzcat -d > /tmp/update_all.pyz
mkdir -p /tmp/dd2_zip && unzip -oq /tmp/update_all.pyz -d /tmp/dd2_zip
cd /tmp/dd2_zip
PYTHONNOUSERSITE=1 qemu-arm -L "$OLDPWD/$ROOTFS" "$OLDPWD/$PY" __main__.py; echo "exit=$?"
cd "$OLDPWD"
# 7. Confirm /usr/bin/python3.9 is absent on the built rootfs (downloader.sh's gate)
test -x "$ROOTFS/usr/bin/python3.9" && echo EXISTS || echo "DOES NOT EXIST"Environment used: host Ubuntu, qemu-arm version 10.2.1 (Debian 1:10.2.1+ds-1ubuntu3.1),
no root/sudo available (hence -L sysroot-prefix rather than a real chroot; see §2.1
for why this is equivalent for this question). binfmt_misc has qemu-arm registered
with the F flag, so a real chroot (with root) would not require copying
qemu-arm-static into output/target first.
- Did Downloader's suite run green under target 3.14.5? No suite was run — it
could not be, because it isn't accessible to us (§1: private repo, gitignored
locally). What was run, actually executed under the real target 3.14.5 via
qemu-user: Downloader's real
__main__.pyentry point (fails, exit 10,_sslmissing) and acompileallpass over all 91 of its source files (100% clean, 0 errors). Both are real, reproducible results — neither is a substitute for the hidden test suite's assertions. - Any hard blockers? Yes, two, both config gaps not code bugs:
_sslmissing (§3, breaks 100% of network operations, verified by actually running Downloader and Update All) andzlibmissing (§3.7, breaks reading the defaultdb.json.zip, verified against a real DEFLATE zip). Compounding factor, not itself a blocker to fix here:downloader.shhardcodes a check for/usr/bin/python3.9(§3.6) that will never be true on any build shipping Python 3.14, so the compiled-binary fast path is never attempted and the broken fallback always runs. - Buildroot options to enable?
BR2_PACKAGE_PYTHON3_SSL=yandBR2_PACKAGE_PYTHON3_ZLIB=y(both MUST).BR2_PACKAGE_PYTHON3_BZIP2,_SQLITE,_XZ,_PYEXPAT,_READLINE,_DECIMALare SHOULD-consider for general community-script parity but were not proven necessary by anything actually tested. Underlying libs (openssl,zlib) are already built into the target (§3.8), so this should be a low-risk, localized change. No changes were made — this is a report only. - Actually run vs static analysis: Actually run under real qemu-user emulation of
the target 3.14.5 ARM binary: §2 (interpreter identity), §3.3–§3.5 (Downloader's and
Update All's real entry points), §3.4 (88 individual module-import attempts), §3.7
(zipfile DEFLATE read against a real fixture), §4.2 (compileall over 91 files), §5.2
(4 MiSTer_SAM scripts). Static-analysis-only: §4.1 (grep for removed-module imports —
no execution needed since the result is "absent," and grep is the right tool for an
absence claim). Explicitly unverified / out of scope: the real Downloader test suite
(inaccessible, §1); whether the Nuitka-compiled
downloader_binbinary works independent of system Python (plausible, not tested, §3.6); anything beyond the 2 tools + 4 scripts sampled in §5; actual network I/O against a real HTTPS endpoint withsslhypothetically enabled (would require a rebuild, which was out of scope here). - Exact commands: §7, copy-pasteable from repo root on this same checkout state.