mirror of
https://github.com/kuhyx/utils.git
synced 2026-11-11 19:53:59 +01:00
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
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
69 lines
2.6 KiB
Markdown
69 lines
2.6 KiB
Markdown
# 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`:
|
|
|
|
```python
|
|
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
|
|
|
|
```console
|
|
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
|
|
|
|
```console
|
|
scripts/run_subproject_tests.sh earned_time
|
|
```
|