Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2.6 KiB
earned_time
Penalty-then-reward for gate apps: one table of what every gate earns in gaming time and shutdown time, read by both consumers.
| consumer | applies | via |
|---|---|---|
| steam-backlog-enforcer | Resolution.gaming_minutes |
_budget_resolve.resolve_budget |
| screen-locker | Resolution.shutdown_minutes |
_shutdown_base.reset_to_base_if_new_day + live pass |
A gate (leetcode-guard, book-guard, ...) never touches either. It publishes a
fact — HMAC-signed credit rows in its ledger — and is registered here once.
Adding a gate
-
The gate writes
{"entries": [...]}to its ledger. Each row haskind: "credit", adetaildict, and anhmacsigned with/etc/workout-locker/hmac.key(earned_time.entry_signature, the same as gatelock'slog_integrity). -
Add an
Earnertoearned_time/_policy.pyand toEARNERS:ANKI = Earner( name="anki", # also screen-locker's "anki_bonus_date" stamp label="Anki", gaming_minutes=30, shutdown_minutes=30, penalty_from=date(2026, 10, 6), # base drops by the same 30/30 from then on ledger=".local/share/anki_guard/ledger.json", match=_anki_match, # which verified credit rows count today missing_ledger_is_no=True, ) -
Bump the version, tag
earned-time-vX.Y.Z, and bump the pin in both consumers. Neither needs code for a ledger-backed flat earner.
penalty_from is the gate's start date, so the base is never cut before the
reward that pays it back exists. The best case stays the same, because the
base drops by exactly what the new earner adds.
Rules the consumers rely on
- Minutes everywhere. Shutdown is minutes after local midnight. screen-locker still stores whole hours and refuses (raises) a remainder rather than flooring it.
- Paths are passed in.
done_today(earner, ledger, key_file)never resolves a real path itself; consumers' test suites redirect their own constants. Noneis "could not check", never "no". It earns nothing (fail closed), but consumers log it. A missing answer inresolve()is logged as well; an unknown earner name raises.- Stdlib only. The gaming daemon imports this as root, headless.
Install
pip install --user "earned-time @ git+https://github.com/kuhyx/utils@earned-time-v0.1.0#subdirectory=earned_time"
The root steam-backlog-enforcer.service sets HOME=/home/kuhy, so it imports
from the same user site-packages.
Test
scripts/run_subproject_tests.sh earned_time