2026-10-02: tests/conftest.py imports the real arq (+ arq.connections, arq.jobs) and redis (+ redis.asyncio) before pytest collects any test module. ~60 test files open with if _mod_name not in sys.modules: sys.modules[_mod_name] = types.ModuleType(_mod_name), which keys on not-yet-imported rather than not-installed; the first stub file collected parked a bare module (no path) under sys.modules[“arq”], and the next from arq.jobs import Job in the session failed with “‘arq’ is not a package” (test_permit_revision_invoice after test_cert_pipeline; alone, reverse order and the full suite passed only because test_cert_permit_revision → portal_cert_invoice_service → supabase_write_service → job_queue loads the real package first). With the preload every per-file guard is a no-op in every ordering; where a package is truly absent a package-shaped stub stands in. Rule for new test files: no arq/redis stub block needed; never register a bare ModuleType for an installed package; fake arq by overriding attributes on the registered module. Full suite unchanged (2366 passed, 1 skipped, known test_ops_agent_loop set only).
2026-10-02 (entirely-api 0a95d32): the 65 per-file arq/redis stub blocks are gone (plus their unused import sys / import types and stale # noqa: E402). conftest.py preloads the real arq/redis (package-shaped stub only where absent) and now carries an autouse _hermetic_redis guard: create_pool in services.job_service and services.job_queue raises RuntimeError at once and the cached _pool is reset per test, so no test can dial REDIS_HOST (with the real arq an un-mocked pool path would sit through RedisSettings’ 5 × 1 s retries). The session-end report lists tests that reach it (18 in 10 files on 10/2, all swallowing the error as they swallowed the old await None TypeError); patch job_service.get_redis_pool / job_queue.get_pool (or enqueue) in a test that needs a pool. Faking arq in a test = monkeypatch the attribute on the module under test (test_scheduler_cron_retry.py binds _StubJob / _JobStatus on scheduler via a scoped autouse fixture), never write into the arq package. Suite after the sweep: 2366 passed / 1 skipped, the same 17 known test_ops_agent_loop entries, no duration change.