Follow-up to review on #8537: turn on ruff's `PLC0415`
(`import-outside-top-level`) so deferred imports in tests stop
accumulating.
Scoped to `checkpoint-postgres` and `checkpoint-sqlite` rather than
repo-wide, because the sweep turns up three different things and only
one of them is a style problem.
### What the rule finds today
```
package tests src files
checkpoint 13 10 11
checkpoint-conformance 0 10 4
checkpoint-postgres 6 0 2
checkpoint-sqlite 9 0 3
langgraph 130 23 32
prebuilt 14 3 7
cli 9 14 10
sdk-py 189 23 38
────────────────────
370 83 107
```
453 violations across 107 files, and ruff has no autofix for this rule.
### Three categories, not one
**Style — hoist.** `checkpoint-sqlite/tests/test_store.py` deferred
`math`, `random`, `time`, `Counter` and `defaultdict` inside methods for
no reason.
**Deliberate — keep, annotate.**
`checkpoint-postgres/tests/test_async.py` defers behind
`pytest.importorskip("langgraph.channels.delta")` because langgraph core
is *not* a test dependency of that package. Hoisting would break the
skip. Those get `# noqa: PLC0415` and a comment.
**Redundant guard — hoist.**
`checkpoint-sqlite/tests/test_conformance_delta.py` deferred imports
only to get past its own `importorskip`. Imports move up; the
`aiosqlite` guard stays, since that dependency genuinely can be absent.
The second category is why I did not enable this everywhere in one go.
Most of the 83 source-level violations look like the same pattern —
optional-dependency handling and circular-import avoidance in
`jsonplus.py`, `embed.py`, `encrypted.py` and friends. Blanket-enabling
would mean `# noqa` on a lot of correct code, and each one wants an
owner's eye rather than a mechanical pass.
These two packages are clean to enforce today because both have **zero**
source-level violations.
### Suggested rollout for the rest
Either extend package by package as owners confirm which deferrals are
intentional, or enable everywhere at once with `per-file-ignores`
grandfathering the current 107 files so new code is blocked immediately
and the debt burns down. Happy to do either — the second is a smaller
diff but leaves a long ignore list.
### Verified
`checkpoint-sqlite` 118 passed, `checkpoint-postgres` 264 passed on PG
15 and 16, `make lint` clean in both.
One overlap worth flagging:
`checkpoint-sqlite/tests/test_conformance_delta.py` is also touched by
#8537. The change is identical in both, so it should merge cleanly
either way.
## Summary
Add a user-facing design doc and `get_delta_channel_keepset` helper for
third-party `BaseCheckpointSaver` authors who need to support graphs
using `DeltaChannel`.
**Deliverables:**
1. ~~**`docs/delta-channel-checkpointer-guide.md`** — comprehensive
guide covering~~:
moved to docs repo
2. **`BaseCheckpointSaver.get_delta_channel_keepset` /
`aget_delta_channel_keepset`** — returns the minimum set of ancestor
`checkpoint_id`s that must survive deletion for a given head's
`DeltaChannel` reconstruction to remain intact. Enables safe `prune`
implementations without silently corrupting delta history.
3. **Docstring warnings** on `prune`, `aprune`, `delete_for_runs`,
`adelete_for_runs`, `copy_thread`, `acopy_thread` explaining the
DeltaChannel pitfall (silent data loss if ancestor writes/snapshots are
deleted).
4. **Three new conformance capabilities** in
`libs/checkpoint-conformance`:
- `delta_channel_history` — validates the `aget_delta_channel_history`
walk contract
- `delta_channel_keepset` — validates the keep-set contract
- `delta_channel_reconstruction` — end-to-end round-trip (aput +
aput_writes + history + reconstruct)
## Test plan
- [x] `make format lint` passes in `libs/checkpoint`,
`libs/checkpoint-conformance`
- [x] All three new conformance capabilities pass against
`InMemorySaver`
- [x] Run conformance against SQLite saver
- [x] Run conformance against Postgres saver
---------
Co-authored-by: Cursor <cursoragent@cursor.com>