RUL-020 — Async/sync contract — an async def must always be awaited
Ruling
"If a function is async def, every call to it must be awaited (or scheduled via asyncio.create_task/gather/ensure_future). Calling it bare discards the coroutine — the work never happens and no exception is raised." (docs/sync-vs-async.md TL;DR). This is the correctness rule the seed list's "async-by-default" label refers to; no separate verbatim ruling text for a stylistic "prefer async for new code" preference was found in the accessible memory export — stated plainly rather than assumed.
Evidence
docs/sync-vs-async.md: written after session 163/166 audited real un-awaited-coroutine bugs that slipped past 1038 green test modules because the buggy line sat in optional-dependency branches no test executed.- Project memory
project-async-correctness-gate: a runtime gate (PytestUnraisableExceptionWarning) was tried and rejected as flaky under the free-threaded batched runner; the static Pyright gate replaced it.
Enforcement
- gate scripts/check_async_await.py reportUnusedCoroutine
Verification
scripts/check_async_await.py runs Pyright over src/ + tests/ and fails on reportUnusedCoroutine (a bare async_fn() whose result is discarded) plus operations on a bare CoroutineType, deliberately excluding the redis-py Awaitable[T] | T stub-noise false positives on sync clients.