Ganymede review 2026-05-11 — three issues addressed.
MUST-FIX — prefix fallback broken in both directions:
Old code used common-prefix matching with a 32-char anchor. This admitted
two failure modes:
1. False-positive: stems "X-A" and "X-B" (sharing 50+ char prefix) both
pass the threshold for a request "X-C-something". Loop picks whichever
iterates first — dict iteration = filesystem walk order = non-deterministic
which claim gets served. Two instances with identical data could disagree.
2. False-negative: a 24-char stem proper-prefix of a longer request never
reaches the 32-char anchor. Returns 404 despite the correct match
sitting right there in by_stem.
Fix: require norm_req.startswith(norm_stem). Proper prefix is much stronger
than common prefix — drop the anchor to 16 chars without admitting noise.
Pull to module constant _PREFIX_ANCHOR_MIN.
Verified against real KB collisions (semaglutide pair, liquidity-weighted-price
pair, attractor-digital-feudalism short-stem case):
- Common-prefix XYZZY collision: 200 -> 404 (correct)
- Proper-prefix match: resolves to shorter B stem (correct, deterministic)
- 27-char proper-prefix request: 404 -> 200 (correct)
- All 4 yesterday's long-slug repros: still 200
WARNING — _build_indexes blocks event loop for ~3.3s on cold cache:
Routed through asyncio.to_thread. Warm-cache overhead negligible (dict
access), cold-cache concurrent requests no longer stall.
NITS:
- _split_frontmatter catches yaml.YAMLError specifically, logs WARNING
with file path. Bare Exception was hiding KB integrity drift.
- _INDEX_CACHE_TTL bumped 60s -> 300s to match commit-message intent.
- PREFIX_ANCHOR_MIN pulled to module constant with calibration comment.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>