Skip to content

v0.4.3 — Wrapper stamp validity by pyproject content hash, not mtime

Choose a tag to compare

@myurasov myurasov released this 10 May 02:58
· 14 commits to main since this release

Fixes the wrapper-UX wart that surfaced during today's testing on myspark1-local.

What was broken

The v0.3.8 wrapper fast-path skipped require_uv when .venv looked healthy, but the staleness check was an mtime comparison:

[ ! "pyproject.toml" -nt "$INSTALL_STAMP" ]

rsync bumps the receiving file's mtime on every push, even when the content is byte-identical. So every

laptop$ ./spectator rsync --target HOST
laptop$ ssh HOST 'cd ~/.spectator/Spectator && ./spectator <verb>'

re-entered bootstrap on the remote → fired require_uv → died with "uv is required" on hosts where uv had been deleted post-deploy or where ~/.local/bin wasn't on the non-interactive SSH PATH. Even though the deps were unchanged and .venv was perfectly fine.

Fix

Stamp file now stores the SHA256 of pyproject.toml's content. The wrapper's bootstrap() compares that hash against the current pyproject.toml's hash:

State Action
Hash matches Fast path (no uv needed)
Hash differs Sync (uv required — deps actually changed)
Stamp empty (legacy v0.3.9-v0.4.2 touch'd stamp) Fast path + write the hash for next time (self-healing migration)

Hashing tries shasum -a 256 first (in Perl's macOS default distribution), then sha256sum (GNU coreutils on Linux), then falls back to touch (the legacy-empty-stamp path will self-heal on next bootstrap).

deploy.py writes the hash directly during the remote uv-sync instead of just touching, so freshly deploy'd targets skip the legacy-empty-stamp branch entirely.

Tests

60/60 (was 57, +3). All three new tests sabotage PATH (no uv available) and run ./spectator install (which calls bootstrap for real, unlike help which short-circuits before bootstrap):

  • Hash matches + post-rsync mtime → fast path → success.
  • Hash mismatch → bootstrap correctly demands uv → fails with the right error.
  • Empty stamp (legacy) → self-heals → success + writes the new hash.

Migration

None. Existing v0.3.9-v0.4.2 installs upgrade in place: their empty stamp triggers the self-healing path on first invocation, which writes the hash.