utils/earned_time/README.md
Krzysztof kuhy Rudnicki 957b4df642
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
earned_time pre-commit / pre-commit (push) Waiting to run
earned_time tests / test (push) Waiting to run
file length / file-length (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
palette drift / palette (push) Waiting to run
repo-contract / repo contract (push) Waiting to run
shared gate tests / gate unit tests (push) Waiting to run
earned_time 0.2.0: register the anki earner (30/30, penalty from 2026-10-06)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-05 13:51:44 +02:00

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

  1. The gate writes {"entries": [...]} to its ledger. Each row has kind: "credit", a detail dict, and an hmac signed with /etc/workout-locker/hmac.key (earned_time.entry_signature, the same as gatelock's log_integrity).

  2. Add an Earner to earned_time/_policy.py and to EARNERS:

    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,
    )
    
  3. 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.
  • None is "could not check", never "no". It earns nothing (fail closed), but consumers log it. A missing answer in resolve() 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