- Add existsByTurnIdPrefix(prefix) to L3MemoryStore interface; implemented in
InMemoryL3MemoryStore (mutex-guarded) and TurboVecL3MemoryStore (metadata map).
All test fakes (TrackingL3MemoryStore, CapturingStore) updated.
- Introduce RepoMapIndexerPort interface; RepoMapIndexer implements it. Allows
injection of test doubles without framework overhead.
- ProjectMemoryService.indexAndRecord: reads latest WorkspaceStateObservedEvent
from event store (invariant #9 — recorded fact, not re-probe); skips scan+embed
when L3 already holds entries for "repomap:<root>:<stateKey>"; passes stateKey
to RepoMapComputedEvent; embeds each scanned entry after append; per-entry
failures warn-logged (CancellationException rethrown).
- Tests: InMemoryL3MemoryStoreTest +2 cases; new ProjectMemoryServiceReuseTest
covers same-key skip, changed-key re-index, no-observation always-reindex,
and embedding turnId tag verification.
TurboVec persists vectors via the sidecar but kept entry metadata
(sessionId/turnId/text) only in an in-memory map, so restarting with
backend=turbovec silently dropped every query hit (the id→metadata
lookup missed). Rebuild that map from the ChatTurnEvent log at startup.
- RehydratableL3MemoryStore: capability interface for stores whose
vectors persist independently of the JVM. TurboVec implements it;
InMemory deliberately does not (its vectors are volatile too).
- L3MetadataRehydrator replays ChatTurnEvents into the metadata map.
No embedder: the query path never reads entry.vector, so vectors
stay in the sidecar and metadata comes from the event log (the
source of truth, invariant #1). Short-circuits for non-rehydratable
backends before scanning the log.
- Wired into Main before the server accepts queries; no sidecar spawn.
Backfill of never-embedded history (needs the embedder) is a separate
follow-up. The in_memory non-durability warning already exists.
Defines the L3MemoryStore contract in core:router (entry, query, hit,
in-memory test default) and a new infrastructure:router:turbovec module
that adapts turbovec via a Python sidecar over stdin/stdout JSON-Lines.
Adapter manages the subprocess via ProcessBuilder with lazy start,
mutex-serialized requests, and shutdown via withTimeout. Wire-format
metadata that turbovec doesn't store (sessionId, turnId, text) is kept
in an in-memory map for now — durable persistence ships with the
upcoming ChatTurnEvent.
Not yet wired into RouterFacade, InfrastructureModule, or DI. Embedding
generation source is a separate concern.