The single ci-mirror manual hook (clean venv + pre-commit --all-files +
serial pytest) ran past the 60 s per-command cap, so the tree could not be
stamped for a push. It is now nine gate steps, each under 55 s at load ~40:
- ci-mirror-venv / -precommit / -pytest: scripts/ci_mirror.sh parts;
pytest runs on 4 xdist workers, pytest-cov still enforces 100 %.
- ci-mirror-pylint-{pkg,tests-1,tests-2,tests-3}: per-file checks over a
partition of the pylint hook's targets; -dup and -cycles: the two
cross-file checks on the whole tree. pylint is now a local hook so all of
them share one env (&pylint_env, pylint==4.0.9).
- scripts/pylint_symilar_cache.py: pylint plugin that hashes each file once
for duplicate-code instead of once per file pair (same R0801 output).
Tests: an autouse guard refuses non-loopback DNS (several tests made live
HLTB requests), two web fixtures poll at 0.01 s, and the rate-limit test
uses a fake clock. CLAUDE.md and AGENTS.md get the push line.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WzxdWWjUkRCFGNMnnXZrSP
kuhy 2026-11-11, multi-day research without re-setup.
W1: once a window was used in a lock, the block's purge spares
~/.local/share/Steam (Librarian's install and local saves) for the
rest of the lock; ~/steam, ~/.steam and the rest still go. The
package stays removed, hosts/iptables are re-applied, and since the
kept install could otherwise be launched without the package, every
Steam game is killed on sight. A record that cannot be read also
keeps the folder (an irreversible delete never rests on unknown
state); the block itself stays fully applied.
W2: the 48 h cap is gone; --until is bounded by the lock's end. An
open window may be extended ONCE, to the lock end at most, never
shortened, never reopened after it ended. The extension is a
separate extended_until field beside until, so a daemon still on
the previous code reads the original window instead of rejecting the
record as corrupt (which would have purged the folder).
Failure modes unchanged: missing/corrupt record = no window, and a
corrupt one refuses a new window or extension.
Checked: 3325 tests at 100% coverage; pylint 10/10 in parts; vmbox:
extension accepted once, refused beyond the lock end, earlier or
equal, second time and non-root; extended end honoured past the
original end; at close the Steam dir and a save survive, ~/steam and
~/.steam go, a game started after close is killed; extension to
exactly the lock end accepted. Not verified on the host.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WzxdWWzuCEg1m87jpKyRrw
Pulls ~/src/steam-backlog-enforcer, syncs units only on difference,
restarts the web unit every run and the daemon only when it predates
the last package commit, then runs four health checks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Under load pacman -Qi / iptables exceed their 10 s timeouts inside the
research-window pass; the uncaught TimeoutExpired crashed the daemon
(systemd restart loop). The pass now logs and retries next tick.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kuhy 2026-11-11: game-design research needs Librarian (app 4197610)
actually played during the 6-day total block. New root command
total-block-research --until T opens ONE window per lock: <= 48 h,
never past the lock end, a rerun never extends it.
The command only validates and records (/var/lib/steam-backlog-
enforcer, root 0700; lock id + deadline + two latches). The daemon
opens and closes everything: Steam hosts names, the store block, the
total-block chain (IP cache kept) and the steam package, reinstalled
with only guard-lib's package-block hook masked for that one pacman
call (--hookdir, proved in a guest). Lock file, hook and end date are
never touched. Browser-game sites stay null-routed.
Only Librarian may run: loop-local allowed set, strict mode (no
whitelist exceptions, no zero-achievement games), launchers and
counted games killed; state.json is never read or written.
Expiry latches the record, then the ordinary tick re-applies hosts
(now reconciled each tick), chain, uninstall and purge; the normal
store block is restored once, unconditionally.
Failure modes: missing/corrupt/unparseable record = no window and a
corrupt one also refuses a new window (fail closed); a clock set
back cannot reopen a latched window.
Checked: 3290 tests at 100% coverage; whole-tree pylint 10/10 run in
three parts (the hook exceeds the 60 s cap, so SKIP=pylint here); vmbox
guest with real guardctl package-block and the real unit, window
opened, Librarian-only enforced, clock moved past the deadline and the
full block re-applied. Not verified on the host.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WzxdWWzuCEg1m87jpKyRrw
kuhy 2026-11-11: itch.io unblocked, only it. A named
TOTAL_BLOCK_EXEMPT_DOMAINS drops itch from the hosts section and
its IPs from iptables (the exception wins on shared Cloudflare
IPs). New root command total-block-refresh applies the list to a
live block: hosts first, then per-rule iptables deletes, no flush.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WzxdWWzuCEg1m87jpKyRrw
web/ has only pnpm-lock.yaml, so setup-node's npm cache step failed
the job before any test ran.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tests are now linted by the 10.00 gate (ignore-paths dropped), with no
per-test disables. CtlServer/DaemonControl members the tests drive are
public (renamed, behaviour unchanged); SteamAPIClient._get takes its
params explicitly, as every caller already did.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sidebar paints a glyph and digit per section, the palette button a
magnifier and ⌃K; page titles, subtitles and the Actions heading are
spoken only. web/scripts/visible_words.mjs lists chrome words in the
built pages (146 -> 4, the command-button labels).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Partial textgate rework: job state pills, notices, empty and
loading states, copy button and timeline legend paint icons; the
old wording is spoken only. Guard test covers these pieces.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Raise the pylint hook to --fail-under=10.0 and make it lint the whole
package on every run (pass_filenames: false): over the staged subset a
9.99 tree passed at commit time.
Ports de2aea3 (9b9342ba-1) onto main, minus its web/ and requirements.txt
bumps and the iptables dedupe f1822f1 already landed; _hltb_confidence and
_hltb_search keep main's 429 handling. _search_batch splits out
_acquire_auth / _search_headers, and _stats.py is back to 250 lines.
Drops the min-similarity-lines = 8 floor (0 duplicate-code hits at the
default) and sets max-attributes = 15 for the flat persisted records, as
kuhy decided. Tests stay outside pylint, as at every site already at 10.
Todo: fda71ed1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Set [tool.pylint.similarities] min-similarity-lines = 8 (as diet-guard)
and refactor the remaining R0801 hits: the store and total-block
iptables modules now share create_fresh_chain / add_drop_rule /
drop_chain in _store_tools. No duplicate-code disable or exclude.
Run pylint serially (pyproject jobs = 1, hook --jobs=1): parallel mode
pickles PyLinter with dill, which crashes on Python 3.15 (co_lnotab).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tests isolate FOCUS_PROJECT_DATA so a live project never leaks in. Also
bumps the freshness gate's stale dev pins and websockets.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WzxdWYcmik6APnHfc9gEVy
Owned games Steam confirms have no achievements can never be assigned or
completed, so the enforcer no longer uninstalls, hides or kills them as
unauthorized. The gaming-time budget kill still applies to them.
Fail-closed: unknown status (no cache, other account, unreadable config
or snapshot, request error) keeps a game enforced. The set comes from
no_achievements_cache.json minus snapshot.json, so a game that gains
achievements is enforced again after the next scan.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WzxdWWAkVShjD2Bo6hM88B
pnpm-lock.yaml had missed the previous bump (react-query 5.104.5,
typescript-eslint 8.72.0, vite 8.3.5); both lockfiles now match package.json.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GYmCezqhUjUmMisF5YtM4F
The BG3 fast install of 2026-10-23 downloaded 160 GB, was refused by the
installer, and the web job still ended on a full bar, "Done!" and Succeeded
while Steam restarted the download from 0%.
- A failed run's reason is read from the installer's own run log (its ERROR
lines). A waiting job prints "Failed: fast install of <game>: <reason>",
which fails the job; `install` no longer says "Done!" after it.
- Daemon-started failures stay on the Dashboard's Fast install card (and in
ctl status, `error`) until a new run starts or the game is installed.
- install_game writes Steam's "needs downloading" manifest before the
installer runs: it only finishes a manifest it finds, so fresh installs
were refused for want of one.
- Command building moved to _fast_install_cmd (250-line cap).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GYmCezqhUjUmMisF5YtM4F