docs(commons): the cache's sorted store is what blocks commonMain

Correcting yesterday's audit, which said `LargeSoftCache`'s skip-list ordering
was not load-bearing and that quartz's `ConcurrentMap` could substitute. Both
halves were wrong, from reading only the first half of the file.

`LargeSoftCache` implements `CacheOperations` — itself `quartz/jvmAndroid`,
not commonMain — for its ranged `forEach(from, to, …)`, backed by
`ConcurrentSkipListMap.subMap`. All of `LargeSoftCacheAddressExt` is built on
it: `filter(kindStart(kind), kindEnd(kind), …)` walks one kind's slice of the
`Address` key space, and `filterIntoSet` has 28 callers. On a hash map each of
those degrades to a full scan of every addressable.

`quartz/linuxTest/LargeCacheRangeFallbackTest` states the same invariant from
the other side: the native range overloads fall back to full scans, and that
is deemed safe *because* "the range overloads have no callers outside the
JVM-only LargeSoftCache". Promoting the cache makes that false.

So the remaining work is not the okio sink plus a `SortedSet` signature
change. It needs a sorted KMP store first — an expect/actual `LargeSoftCache`
with a hand-written sorted iOS actual, or a KMP sorted-concurrent map that
exists in neither the tree nor the stdlib. Recorded as a question to answer
before the ordered steps, not as one of them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkULS5SVq4GHDdoCzajKi8
This commit is contained in:
Claude
2026-09-21 02:36:59 +00:00
parent 8f38d4a0b9
commit 5139ccef1e
2 changed files with 36 additions and 12 deletions
+10 -6
View File
@@ -259,12 +259,16 @@ These are intentionally *documented*, not silently tolerated. Fix opportunistica
(`WeakReference` + `ConcurrentSkipListMap`), as are the two
`*ListMatchingFilter` observables, `MintDirectoryIndex` and
`NwcPaymentTracker`. Promoting the cache means promoting those first.
Most of it has answers already in the tree — `commons.util.WeakReference`
is an expect/actual, and quartz's `commonMain` has `ConcurrentMap`,
`ConcurrentSet` and `LargeCache` — so the genuinely new work is an okio (or
`expect`) blob sink and getting `java.util.SortedSet` out of
`EventCache.filter`'s signature, which has no common equivalent. See the
audit in `commons/plans/2026-08-30-commons-migration-sweep.md`.
The binding constraint is `LargeSoftCache`'s **sorted** store: it backs the
ranged `forEach(from, to, …)` that all of `LargeSoftCacheAddressExt` uses to
scan one kind's slice of the `Address` key space, and a hash map turns each
of those into a full scan. `quartz/linuxTest/LargeCacheRangeFallbackTest`
documents the matching invariant from the other side — the native range
overloads fall back to full scans, and that is deemed safe precisely
*because* the range callers live in the JVM-only `LargeSoftCache`. Moving
this needs a sorted KMP store first, not just the okio blob sink and the
`java.util.SortedSet` signature change. See the audit in
`commons/plans/2026-08-30-commons-migration-sweep.md`.
---
@@ -1030,11 +1030,15 @@ constraint rather than a hypothetical one.
- quartz `commonMain` already ships `ConcurrentMap`, `ConcurrentSet`,
`ConcurrentHashCache` and `LargeCache` — so every `ConcurrentHashMap` /
`ConcurrentSkipListSet` above is a swap, not a design problem.
- `LargeSoftCache`'s skip-list ordering is **not** load-bearing: its whole
surface is `get`/`put`/`remove`/`size`/`containsKey`/`keys`/`forEach`, with
no ordered read. `ConcurrentSkipListMap` is there for lock-free concurrency,
so quartz's `ConcurrentMap` should substitute — worth confirming nothing
iterates expecting order before relying on that.
- ~~`LargeSoftCache`'s skip-list ordering is not load-bearing~~ — **wrong,
corrected 2026-09-21.** It is load-bearing, and it is the thing that stops
this migration. `LargeSoftCache` implements `CacheOperations` (which is
itself `quartz/jvmAndroid`, not commonMain) for its ranged
`forEach(from, to, …)`, backed by `ConcurrentSkipListMap.subMap`. The whole
of `LargeSoftCacheAddressExt` is built on it: `filter(kindStart(kind),
kindEnd(kind), …)` walks only one kind's slice of the `Address` key space,
and `filterIntoSet` is called 28 times. On a hash map every one of those
becomes a full scan of all addressables.
- `AtomicInteger` → `kotlin.concurrent.atomics`; `BiConsumer` → a function
type; the `dateFormatter` call is one log line and can go.
- `androidx.collection.LruCache` in `AntiSpamFilter` is already KMP —
@@ -1051,7 +1055,23 @@ constraint rather than a hypothetical one.
a signature change rippling into `CacheSearch`, both observables and
`LocalCacheSearchParityTest`.
**Recommended order** (bottom-up; each step is independently shippable):
**The invariant this would break.** `quartz/linuxTest/LargeCacheRangeFallbackTest`
pins the native `LargeCache` range overloads to a deliberate full-scan
fallback, and says why that is acceptable: *"the range overloads have no
callers outside the JVM-only `LargeSoftCache`"*. Promoting `LargeSoftCache` to
`commonMain` makes that statement false. Any move has to either give iOS a
genuinely sorted store or accept — explicitly, not silently — that addressable
lookups there are full scans.
**Revised cost.** This is not the swap described above. It needs either an
`expect`/`actual` `LargeSoftCache` with a hand-written sorted iOS actual (the
`LargeCache` apple actual, for comparison, is 432 lines), or a KMP
sorted-concurrent map that does not exist in the tree or the stdlib — which
would want a `Comparable` bound on `Address` and would benefit quartz too.
Neither is a step; both are projects.
**Recommended order** (bottom-up; each step is independently shippable), *if
the ordering question above is answered first*:
1. `LargeSoftCache` → `commonMain` on the existing `WeakReference` expect plus
quartz's `ConcurrentMap`. This is the keystone — everything else is behind it.