Zimi 1.9.1 - #62
Closed
epheterson wants to merge 9 commits into
Closed
Conversation
1.9.0 was tagged twice and Desktop Release failed both times. The second
tag had a gate added specifically to prevent that, and it did not, because
it was an approximation: one job on ubuntu-latest, standing in for a four
runner matrix.
Three failures it could not see:
* Linux. The release's Linux leg is pinned to ubuntu-22.04 for the snap
base (issue #55). Jammy calls the package libgirepository1.0-dev; the
workflow still asked for the 24.04 name, so apt exited 100 and no test
ran. Broken since the pin moved. Both workflows now ask for either.
* Windows and macOS. Their suites have not run since 1.8.2, because
Desktop Release fires only on a tag. 296 commits landed over them.
* macOS Intel specifically. test_a_job_that_keeps_reporting_is_left_alone
proves a negative — the watchdog leaves a talking job alone — with five
ticks of margin between a 0.02s heartbeat and a 0.1s stall window. One
late sleep on a loaded Intel runner and the watchdog was right. The
heartbeat stays; the window goes to 2s, a hundredfold margin, same 0.4s
test.
The matrix here is now the release's matrix, and test_ci_contract.py fails
if the two lists ever drift apart again. -v rather than -q because the
Windows leg died at 30% of the suite with no failure summary at all: names
have to stream before the test that crashes the interpreter runs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y97WKqARyZKB3uDfALncC8
…not build on jammy Two more dominoes behind the ones the matrix already found. os.kill(os.getpid(), SIGINT) does not deliver a signal on Windows. It calls TerminateProcess with the number as an exit code, so the crawler's interrupt test terminated pytest itself at 32% of the suite: no summary, no traceback, just exit 1. signal.raise_signal runs the handler the test is actually about and does it on every platform. PyGObject 3.52 requires girepository-2.0, which 22.04 does not ship under any package name. The Linux leg is pinned to 22.04 for the snap base, so an unpinned install resolves to a version that cannot build there. --tb=short so the eighteen Windows failures behind the crash come back with reasons and not only names. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y97WKqARyZKB3uDfALncC8
The Windows suite has never run. Not since 1.8.2 — never: until this
release made CI run the whole suite, the release workflow ran
`python tests/test_unit.py`, which imports the file and executes none of
its tests, plus one file by name. Every Windows build ever shipped was
gated by a step that could not fail. First real run: 56 failed, 2521
passed. Six were the product.
* `zimi serve` died at boot. The first-run banner — the setup key box
from this release's own GHSA-5mw2-53vv-9pw6 fix — is drawn in box
characters, and Windows encodes redirected stdout as cp1252, which has
none of them. The very first run, the one where no password is set
yet, crashed before READY. Stdio is resilient now and the banner
degrades to ASCII rather than raising. PYTHONIOENCODING reproduces it
on any platform, so the test fails everywhere if this regresses.
* Deleting a ZIM answered 500 every time: the route unlinked the file
and released the pooled libzim Archive after. Windows will not remove
an open file. Release first, which POSIX does not notice.
* Auto-update orphaned every superseded edition, same cause, inside an
`except OSError: pass` — so it failed silently and permanently. The
swallow is now a warning.
* The create watchdog could never kill a stalled browser: signal.SIGKILL
does not exist on Windows and naming it raises.
* Every ZIM built on Windows left four libzim index scratch files beside
it. libzim unlinks them as it closes, which needs an OS that permits
unlinking an open file.
* Mimetypes came from HKEY_CLASSES_ROOT, so a ZIM built on Windows could
carry entry types no other machine would produce. All five sites now
share one registry-free guesser.
The rest were tests. Where one assumed something the platform does not
grant — POSIX mode bits, a chmod'd read-only directory — it now asserts
the premise and skips when the OS declines, which also fixes those tests
under root on Linux, where chmod is equally ignored.
Local suite: 2633 passed, 19 skipped.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y97WKqARyZKB3uDfALncC8
These are fixes against 1.8.2 and every release before it, not against unreleased work on this branch, so they belong in Fixed. The cp1252 boot crash deliberately is not listed: the banner it choked on is new in this release and never shipped. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y97WKqARyZKB3uDfALncC8
* The kill-ladder test named signal.SIGKILL itself, so guarding the
product alone was not enough.
* Three more mock.patch.object(os, "getloadavg") sites needed create=True;
I had fixed two of five.
* The unregister tests removed a ZIM with a bare os.remove, which is not
the sequence any caller uses now: the handles come out first, and on
Windows that is not a preference.
* The read-only write-path fixture had a third chmod site that asserted
nothing, so on a platform that ignores the mode it built a writable
directory and then failed the product for not falling back.
* The index scratch sweep took three of four files and left the fulltext
index, still mapped. Collect, then wait it out in bounded steps. The
waits only happen when a file is genuinely still held, so POSIX never
sleeps.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y97WKqARyZKB3uDfALncC8
…a directory os.WNOHANG does not exist on Windows, and the AttributeError from naming it was not caught, so _process_alive raised and the watchdog could never establish that anything had died. What sat underneath was worse: os.kill on Windows sends no signal, it calls TerminateProcess with the number given, so the liveness probe os.kill(pid, 0) would have killed the process it was asking about. OpenProcess + GetExitCodeProcess is the real question. The index scratch survived three rounds of sweeping because a Xapian database is a DIRECTORY and os.remove cannot delete one. I narrowed two 'leaves nothing behind' assertions to accommodate that before finding it; both are reverted, because the guarantee was right and the sweep was wrong. The sweep test now covers the directory case. Unrelated, and found by the same run: the vocab-cache signature test asserted that one inserted row moves a SQLite file's size or mtime. On a filesystem with coarse timestamp granularity it moves neither, which is a flake rather than a signal. It writes enough rows to be observable now. Local suite: 2633 passed, 19 skipped. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y97WKqARyZKB3uDfALncC8
A security fix, a setting 1.9.0 should not have removed, and two bugs a single reporter found in a day. GHSA-5mw2-53vv-9pw6, reopened by a deployment shape. The bootstrap window waives the setup key for the machine running Zimi, and asked the RESOLVED client address whether it was that machine. Behind a reverse proxy on the same host — the standard NAS deployment — _client_ip correctly refuses the forwarded address as a trusted-tier claim and falls back to the hop, which is loopback, so every remote client through that proxy read as the host and skipped the key. It is asked of the socket now, and a forwarded request is never the host whatever the socket says. The bootstrap tests replaced _client_ip wholesale, so nothing had ever run the function that makes that decision: the advisory's own PoC was covered and the proxy path was not. The doubles move the socket too now. lan_admin (#59). The advisory closed a default that deserved closing and removed, with nothing in its place, a way people genuinely run Zimi: one household, one LAN, no password. It is back as something an operator types, and it means a DIRECT private peer — self-review caught that leaving it at _is_private_client would, behind that same proxy, have handed the admin of a passwordless server to anyone on the internet. The moon was upside down for half of every month (#60). The sprite shades from a Sun vector already flipped for a waning moon, and the bright-limb angle carries the same flip, so waning moons were turned a further 180 degrees: lit limb on the wrong side, maria inverted. The four renderers all agreed, and all agreed on the wrong number, which is precisely what a consistency test cannot see. There is now one that checks the sky. zimi import --setup could set up a sidecar the server never reads (#61). It resolves its own data dir from the shell it runs in; run without the service's config it installs into another library's state directory, reports success, and leaves the engine greyed out. The Create page now names this server's directory in the command it offers. 2636 passed, 19 skipped, plus the standalone JS suite. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y97WKqARyZKB3uDfALncC8
main carries the squash of #58, which this branch already holds as its own commits, plus the v1.9.0 appcast update. Keep this tree and take main's appcasts, which are the only content on main that is not already here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y97WKqARyZKB3uDfALncC8
macos-latest failed test_the_line_buffer_is_bounded on the same commit that passed it in the run beside it: "creation job never finished". _wait_done polled 400 times at 10ms, which reads as a four-second ceiling and is not one. Each turn also makes an HTTP request, so what the count actually measures is how fast the runner is that minute. The 550-line buffer test is the slowest of them, so it is the one that ran out of turns first. Both helpers take a wall-clock deadline now, 30 seconds. A passing test still returns the moment the job is done and the suite does not get slower; the only thing that waits is a job that genuinely hung, and reporting that honestly is worth the wait. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y97WKqARyZKB3uDfALncC8
Merged
Owner
Author
|
Superseded by #63: same tree, commit history without session links. |
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.
The first day of 1.9.0, in four fixes.
Security
GHSA-5mw2-53vv-9pw6, reopened by a deployment shape. The bootstrap window waives the setup key for the machine running Zimi, and asked the resolved client address whether it was that machine. Behind a reverse proxy on the same host — the standard NAS deployment —
_client_ipcorrectly refuses the forwarded address as a trusted-tier claim and falls back to the hop, which is loopback. Every remote client through that proxy read as the host and skipped the key. Reproduced (200 password set), fixed, now403 needs_setup_key.Asked of the socket now, and a forwarded request is never the host whatever the socket says.
Why no test caught it:
test_bootstrap_takeover.pyreplaced_client_ipwholesale, so nothing had ever executed the function that makes the decision. The advisory's PoC was covered; the proxy path never was. The doubles move the socket too now.Not closed, and it cannot be while loopback is the proof: a same-host forwarder that sends no header at all (
socat,proxy_passwith noproxy_set_header) still presents a bare loopback peer. Every mainstream proxy sets a forwarded header. Closing the rest means retiring loopback-as-proof and asking even the host for the setup key, which is a product decision rather than a fix.Added
lan_admin(#59). The advisory closed a default that deserved closing, and removed with nothing in its place a way people genuinely run Zimi: one household, one LAN, no password. Back as something an operator types (lan_admin: true/ZIMI_LAN_ADMIN=1), off by default, and only while no password is set.It means a direct private peer. Self-review caught that leaving it at
_is_private_clientwould, behind that same proxy, have handed the admin of a passwordless server to anyone on the internet — the same root cause one layer up.Fixed
The moon was upside down for half of every month (#60). The sprite shades from a Sun vector already flipped for a waning moon, and the bright-limb angle carries that same flip, so waning moons were turned a further 180 degrees: lit limb on the wrong side, maria inverted. Waxing was correct, which is why it read as random.
Moon on the meridian at each quarter, where the answer is known:
The existing test compared the four renderers to each other. They agreed, on the wrong number. The new one checks against the sky.
zimi import --setupcould set up a sidecar the server never reads (#61). It resolves its own data dir from the shell it runs in, so without the service's config it installs into another library's state directory, reports success, and leaves the alive engine greyed out. The Create page now names this server's own directory in the command it offers, shell-quoted with single quotes so a path containing$survives the paste.Verification
🤖 Generated with Claude Code