mirror of
https://github.com/kuhyx/utils.git
synced 2026-11-15 04:50:56 +01:00
001670fcbc
5 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| 7096c102cd |
Re-point paths for the ~ reorganisation (home-tidy)
Some checks are pending
crdt_sync_dart tests / test (push) Waiting to run
crdt_sync_flutter tests / test (push) Waiting to run
crdt-sync pre-commit / pre-commit (push) Waiting to run
crdt-sync tests / test (push) Waiting to run
dependency freshness / dependencies on newest stable (push) Waiting to run
design_system tests / test (push) Waiting to run
file length / file-length (push) Waiting to run
freedays pre-commit / pre-commit (push) Waiting to run
freedays tests / test (push) Waiting to run
gatelock pre-commit / pre-commit (push) Waiting to run
gatelock tests / test (push) Waiting to run
github_device_auth tests / test (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
music_theory tests / test (push) Waiting to run
palette drift / palette (push) Waiting to run
shared gate tests / gate unit tests (push) Waiting to run
sync_settings_ui tests / test (push) Waiting to run
web_ui tests / test (push) Waiting to run
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AwtowrowDXx6ccmWVPkCaF |
|||
| 7445768ec8 |
freedays: point at the phone picker
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019pBgaXSJKyY3DarYpDatDF |
|||
| bb0f773b7d |
Make sync_quietly's "never fail" contract true by construction
Some checks failed
crdt-sync pre-commit / pre-commit (push) Has been cancelled
crdt-sync tests / test (push) Has been cancelled
dependency freshness / dependencies on newest stable (push) Has been cancelled
file length / file-length (push) Has been cancelled
freedays pre-commit / pre-commit (push) Has been cancelled
freedays tests / test (push) Has been cancelled
md-naming / markdown naming convention (push) Has been cancelled
palette drift / palette (push) Has been cancelled
shared gate tests / gate unit tests (push) Has been cancelled
sync_quietly exists so a background timer stays green when the pool cannot be synced. It caught only SyncUnavailableError, which meant the contract held only because of an upstream class hierarchy: today FirebaseAuthError happens to subclass RemoteSyncError, and requests' JSONDecodeError happens to subclass RequestException, so an expired refresh token or a malformed payload does get wrapped and caught. That is a coincidence, not a promise to this package -- and it is exactly the kind that goes quiet the day upstream reorganises its exceptions. The symptom would be a unit going red every 15 minutes, which is the outcome this function's own docstring says the design exists to prevent. It now also catches anything unexpected and logs it with a traceback, so a real bug is loud in the journal while the timer stays green. The narrow handler stays first and stays at debug level: offline is normal on a laptop and must not fill the journal. Two tests, neither of which the old suite could have failed on -- the existing one monkeypatched get_client to raise the one exception already known to be handled. One raises FirebaseAuthError *mid-sync* (a token expiring during the tick rather than at client construction, which is the realistic case per seed_session.py's own docstring), the other raises a RuntimeError that no hierarchy covers. 111 tests, 100% branch coverage. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|||
| aa032c95e4 |
Drop the gatelock dependency, and actually sync the pool
Two things the pool needed to be real rather than merely correct.
**freedays no longer depends on gatelock.** It imported
gatelock.log_integrity for ~15 lines of HMAC, but importing gatelock runs
its __init__, which imports tkinter for the lock-window machinery. So a
headless policy library had acquired a hard dependency on a GUI toolkit,
and `import freedays` failed anywhere without Tk:
ImportError: libtk8.6.so: cannot open shared object file
A vmbox run caught it. The host masked it completely -- Tk is installed
here for the lockers -- and so did CI, whose own comment claimed "no
display needed" while the job would have needed libtk. freedays/_hmac.py
now does the signing with hashlib and hmac.
The format is deliberately unchanged (HMAC-SHA256 over compact
sorted-key JSON), so nothing has to be re-signed: all 8 entries already
in the real audit trail, written by gatelock's implementation, verify
against the new one. That was checked, not assumed.
This also dissolves the version diamond that shaped the app rollout:
freedays now pins only crdt-sync, so it can never conflict with whatever
gatelock tag an app carries.
**The pool now syncs on a schedule.** A `sync-quiet` subcommand plus a
systemd user timer every 15 minutes, because "it syncs" was otherwise
only true if you typed `freedays sync` yourself -- a day marked on the
phone would sit there. sync-quiet exits 0 when there is no network or no
credential, on purpose: a unit that goes red every time the laptop is
offline is a unit you learn to ignore, and a real bug still reaches the
journal through the library's logging.
install.sh gives freedays its own venv rather than borrowing an app's --
the pool is shared by five apps, so binding its CLI to one of them would
make "is freedays installed?" depend on something unrelated, and
uninstalling that app would take the command with it.
Verified in vmbox first (systemd), then on this host: the timer is
enabled and scheduled, and the service *finishes* rather than failing
when there is no remote.
seed_session.py gains freedays, without which the one command that can
give the pool a Firebase session would reject it outright (--app is
choices-validated) and the pool would stay local-only with nothing
saying why. That file was already exactly at the 250-line cap, so the
app list and its rationale move to tool/_seeded_apps.py.
freedays: 109 tests, 100% branch coverage. crdt-sync: 433 pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
|||
| ef69619597 |
Add freedays: one shared pool of days when every gate stands down
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
file length / file-length (push) Waiting to run
freedays pre-commit / pre-commit (push) Waiting to run
freedays tests / test (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
palette drift / palette (push) Waiting to run
shared gate tests / gate unit tests (push) Waiting to run
Five apps (diet-guard, wake-alarm, screen-locker, leetcode-guard, home-guard) each enforce something daily, and none of them had a way to say "not today". screen-locker was the only one with any notion of a day off, and it had two overlapping ones: a budgeted sick day designed to be hard to take, and an undocumented scheduled_skips.json that no other app could see. This is the second thing generalised, not the first. Free days are pre-declared, carry no justification, allow consecutive runs, and are capped only by an annual budget -- 35 per calendar year, resetting Jan 1. Sick days stay in screen-locker exactly as they are: their rolling windows (1/7d, 3/30d, 10/90d) exist precisely to forbid the consecutive runs a holiday is made of, so merging the two policies would have had to ruin one of them. One global pool: a free day is free for every app at once, so there is nothing per-app to mark and nothing per-app to forget. The integration is one call, `freedays.is_free_day()`, which reads a single local JSON file and never the network -- a gate fires from a systemd timer and a hung sync must not be able to hold one open or shut. It returns False for anything it cannot parse, so an unreadable pool means the normal rules apply rather than everything switching off. Two design points worth keeping: - Un-marking flips a `state` field rather than tombstoning the record. crdt-sync deletes are monotonic, so a tombstoned date could never be marked free again. - `consumed` is a sticky flag. Releasing a day that has not arrived refunds it; releasing today does not, because those hours were already unguarded. It is OR-ed against the stored value on every write so two devices either side of midnight cannot hand a spent day back. Timezone is local, decided in _day.today() and nowhere else. The apps disagreed about this before: screen-locker's lock chain computes the day in UTC while add_scheduled_skip.py uses local time, so a skip added near midnight could name a date the chain never matched. 94 tests, 100% branch coverage, no suppressions. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |