Skip to content

feat: partition the routing index by LoRA adapter so cache hints can't alias#159

Merged
EdHasNoLife merged 8 commits into
mainfrom
sunxuedward/cac-181-lora-index-partition
Jul 24, 2026
Merged

feat: partition the routing index by LoRA adapter so cache hints can't alias#159
EdHasNoLife merged 8 commits into
mainfrom
sunxuedward/cac-181-lora-index-partition

Conversation

@EdHasNoLife

@EdHasNoLife EdHasNoLife commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Linear: CAC-181

Problem

The content fingerprint (pkg/fingerprint, XXH3-64 / seed 1337) is derived from token IDs alone. Two requests with identical tokens under different LoRA adapters therefore produce the same prefix_hash / block_hashes and collided on a single index entry — a LookupRoute for adapter A could be handed a replica that only holds adapter B's KV for those tokens.

SMG v1.8.0's runtime LoRA load/unload/list (adapters swap under a live engine, no restart) makes that ordinary rather than theoretical. vLLM's BlockStored event has always carried lora_id as its trailing field; the subscriber decoded through block_size and dropped it.

Approach

Partition the index, not the hash. The index key becomes (tenant_id, model_id, hash_scheme, adapter_id, prefix_hash)adapter_id joins the existing partition ahead of the content hash.

pkg/fingerprint and its golden vectors are untouched (make verify-golden-vectors is part of the gate that passed). The hash still answers "what content is this?"; the partition answers "whose KV is it?". Matching inside a partition — exact-match and the block-chain longest-leading-run rule — is byte-for-byte unchanged.

Wire changes (all additive vs main, v1alpha1-compatible)

Message Field
LookupRouteRequest string adapter_id = 11 — partition to look up in
LookupRouteResponse string adapter_id = 5 — echo of the partition consulted
PrefixEntry string adapter_id = 6 — per-entry, because one replica holds KV for several adapters at once (field 5 is CacheTier tier, from the merged #154)
CacheStateUpdate string adapter_id = 8 — per-update default for entries that set none
CacheEvent string adapter_id = 7 — narrows a PREFIX_EVICTED to one partition

This branch merged main (which added CacheTier tier = 5 to PrefixEntry in #154 and the T1/T2 tier-tagging path), so PrefixEntry.adapter_id lands at 6. All fields are additive vs main — no removals. Protos regenerated with make proto-gen (verified idempotent, no drift); make proto-lint clean.

Adapter identity is a stable string, not the engine's integer

vLLM's lora_id is an internal load-order integer, so the same integer can mean different adapters on two replicas whose --lora-modules order differs — and the index is shared across replicas. Partitioning on the raw int would re-create the aliasing across replicas instead of within one.

The subscriber maps it via --lora-adapter-names "1=sql-lora,2=chat-lora" to the same string the gateway sends as adapter_id. An unmapped non-nil lora_id is fail-closed: its blocks are dropped (not cached) rather than indexed under a replica-local lora:<id> that could alias different adapters across replicas — so LoRA caching requires the map. An unmapped adapter gets no routing hint (a cache miss, never a wrong replica) until it is named; the drop is logged once per id. An undecodable lora_id fails the batch rather than decoding to "no adapter": mislabelling an adapter's blocks as base-model would put them in the default partition, which is the exact bug being fixed.

Scope & follow-ups

The --lora-adapter-names map is resolved once at startup, so this PR covers adapters known at startup. Two cases are deliberately out of scope, tracked in CAC-243:

  • Runtime-loaded adapters (SMG v1.8.0 load/unload) that aren't pre-mapped are fail-closed — safe, but uncached until named. Restoring their caching needs dynamic identity resolution (from the engine's adapter registry), not a static list.
  • The CacheBackend-managed (webhook-injected) subscriber can't set --lora-adapter-names on a container it doesn't define, so the flag is self-managed-subscriber-scoped until a managed config surface (a CacheBackend field the webhook forwards) lands.

Neither is a correctness regression — a dropped/unmapped adapter gets no hint (a miss, never a wrong replica). Cross-doc key-sync in docs this PR doesn't touch is tracked in CAC-245.

Backwards compatibility

Empty/unset adapter is the default partition and is exactly the prior behavior — a LoRA-free deployment ingests and looks up under "" and is completely unaffected, with nothing to configure. Older producers and older gateways both land there. (This is not a "single LoRA adapter just works" claim: only a nil lora_id uses ""; any non-nil id must be mapped, or it is fail-closed.)

Producer and consumer must agree on the identifier (a lookup with an adapter will not match entries ingested without one, and vice versa) — the same rule hash_scheme already imposes, and documented as such.

PREFIX_EVICTED without an adapter keeps the original conservative cross-partition sweep. With one it drops only that partition, so a single adapter's GPU eviction no longer discards another adapter's still-valid hint for the identical (token-derived) hash.

Deliberately NOT partitioned: the (tenant, model, hash_scheme) scope

scopeKey does not gain adapter_id. It answers a replica-membership question, and the surfaces reading it — the distinguishing-power denominator, the TENANT_HOT serving check, the AFFINITY_HINT candidate set, and the UNKNOWN_HASH_SCHEME classifier — carry no per-adapter cache-content claim (TENANT_HOT / AFFINITY_HINT ship matched_tokens=0 by contract). Adapters also load and unload under a live engine, so adapter residency is a property of individual KV entries, not of a replica's membership in an engine domain.

Consequence, pinned by a test: a lookup for an unseen adapter in a known engine domain is a novel-prefix miss (NO_HINT / AFFINITY_HINT), not UNKNOWN_HASH_SCHEME — that code keeps meaning "wrong hash_scheme".

Tests

  • pkg/index — different adapters + same tokens do not alias; same adapter still hits (with unchanged matched_tokens); absent adapter is legacy behavior in both directions; per-entry adapter overrides the update-level default; the chain path partitions identically; scoped vs. unscoped eviction; ALL_CLEARED stays adapter-agnostic; the miss classifier is unaffected.
  • pkg/adapters/enginelora_id decodes (present / explicit nil / absent 5-field tuple / undecodable → error); AdapterID returns ok=false for an unmapped id and ParseAdapterNames; Stored stamps the adapter while leaving the fingerprint identical; identical tokens under two adapters share a hash but not a partition; Reporter end-to-end across a mixed-adapter batch (mapped + base forwarded, unmapped fail-closed / dropped), incl. the partition-scoped eviction; a non-LoRA run stays entirely in the default partition.
  • pkg/server — full round trip (engine event carrying lora_id → subscriber → ReportCacheState → index partition → LookupRoute): same adapter PREFIX_MATCH with the response echoing adapter_id, a different adapter and an absent adapter both off the prefix-match path on the identical hash; plus a no-adapter regression test.
  • install-smoke — extended per CONTRIBUTING: seeds an adapter-scoped prefix over gRPC and asserts same-adapter_idPREFIX_MATCH while a different and an absent adapter_id stay off the prefix-match path. No engine traffic needed.

Validation

make ci green (naming, internal-refs, fmt, vet, golangci-lint, Prometheus rules, golden vectors, race tests, build) — also via the pre-push hook. make proto-gen idempotent; make proto-lint clean. Full go test ./... passes after the main merge.

Docs

docs/design/grpc-contract.md gets the new fields plus an "Update — adapter (LoRA) index partition" section, and its standing caveat ("prefixes that differ only by LoRA adapter share a key … tracked as a follow-up") is now resolved. docs/design/kvevent-subscriber-wiring.md documents --lora-adapter-names, the fail-closed behavior, and a "Limitations of the static flag" section (runtime loads + managed injection → CAC-243).

The content fingerprint is derived from token IDs alone, so two requests
with identical tokens under different LoRA adapters produce the same
prefix_hash and collided on one index entry — a LookupRoute for adapter A
could be handed a replica holding only adapter B's KV. Runtime LoRA
load/unload makes that ordinary rather than theoretical.

Partition the index, not the hash. The key becomes
(tenant, model, hash_scheme, adapter, prefix_hash); pkg/fingerprint and
its golden vectors are untouched.

- proto: adapter_id on LookupRouteRequest (11), LookupRouteResponse (5),
  PrefixEntry (5), CacheStateUpdate (8), CacheEvent (7). Additive only.
- subscriber: decode BlockStored.lora_id (it was already on the wire and
  ignored), resolve it to a stable identity via --lora-adapter-names, and
  stamp it per entry; PREFIX_EVICTED names its partition so one adapter's
  eviction no longer drops another's identical hash.
- server: thread adapter through ingest and lookup; echo the partition on
  hint-bearing responses.

Empty/unset adapter is the default partition and is exactly the prior
behavior, so non-LoRA deployments are unaffected. The (tenant, model,
hash_scheme) scope deliberately stays adapter-free: it answers a replica
membership question, so UNKNOWN_HASH_SCHEME keeps its meaning.
@linear-code

linear-code Bot commented Jul 22, 2026

Copy link
Copy Markdown

CAC-181

@github-actions

Copy link
Copy Markdown

Codex review

Files reviewed

  • cmd/kvevent-subscriber/
    • [reviewed] main.go
  • docs/design/
    • [reviewed] grpc-contract.md
    • [reviewed] kvevent-subscriber-wiring.md
  • docs/reference-stack/scripts/
    • [reviewed] default_install_smoke.sh
  • pkg/adapters/engine/
    • [reviewed] config.go
    • [reviewed] events.go
    • [reviewed] forwarder.go
    • [reviewed] lora_adapter_test.go
    • [reviewed] mapper.go
    • [reviewed] mapper_test.go
    • [reviewed] positional.go
    • [reviewed] positional_test.go
  • pkg/index/
    • [reviewed] adapter_partition_test.go
    • [reviewed] index.go
    • [reviewed] lfu_eviction_test.go
  • pkg/server/
    • [reviewed] adapter_partition_test.go
    • [reviewed] inferencecache_service.go
    • [reviewed] lmcache_offload_integration_test.go
  • pkg/server/proto/inferencecache/v1alpha1/
    • [skipped — generated] inferencecache.pb.go
  • proto/inferencecache/v1alpha1/
    • [reviewed] inferencecache.proto

Findings

Blocking

None.

Should-fix

  • cmd/kvevent-subscriber/main.go:53 — The flag help incorrectly says the mapping may be omitted for a single-adapter LoRA deployment. A non-nil LoRA ID is then ingested under fallback partition lora:<id>, so a gateway using the adapter’s stable name will miss unless it is explicitly configured to use that fallback.
  • pkg/adapters/engine/config.go:42 — The AdapterNames documentation similarly groups single-adapter and LoRA-free deployments together, although only nil LoRA IDs enter the default partition. Correct this guidance so single-LoRA deployments configure a mapping or deliberately use lora:<id> end to end.
  • docs/design/grpc-contract.md:270 — The compatibility claim that a “single-adapter” deployment ingests and looks up under "" is false for a LoRA adapter: Config.AdapterID maps every non-nil ID to a configured name or lora:<id>. Limit this claim to non-LoRA/base-model traffic and document the required identifier agreement for even one LoRA adapter.
  • docs/reference-stack/scripts/default_install_smoke.sh:1408 — The two negative adapter probes ignore grpcurl failure and only reject PREFIX_MATCH, so an RPC error or empty response falsely passes the test. Require each RPC to succeed and assert an expected fail-open reason such as AFFINITY_HINT/NO_HINT before accepting the result; the same issue recurs at line 1415.

Nit

None.

Per-category coverage

  1. Vendor-neutral naming: Clean
  2. Contract & spec / docs sync: findings above at: cmd/kvevent-subscriber/main.go:53, pkg/adapters/engine/config.go:42, docs/design/grpc-contract.md:270
  3. gRPC fail-open semantics: Clean
  4. Quality (tests / error wrapping / package placement / generated drift): findings above at: docs/reference-stack/scripts/default_install_smoke.sh:1408

Verdict

changes-requested

Review follow-ups on the LoRA index-partition change.

Only a NIL lora_id (base model) uses the default "" partition; ANY non-nil
lora_id — including a single-adapter deployment — resolves to a configured
name or the lora:<id> fallback, never "". So a single-LoRA deployment does
not "just work": the subscriber's ingest identifier and the gateway's
LookupRoute query identifier must agree end-to-end or every lookup silently
misses. Corrected the wording in three places that conflated "single-adapter
LoRA" with "LoRA-free":
- --lora-adapter-names flag help (cmd/kvevent-subscriber/main.go)
- Config.AdapterNames doc (pkg/adapters/engine/config.go)
- the compat claim in docs/design/grpc-contract.md

Also hardened the two negative adapter probes in the install-smoke script:
they only rejected PREFIX_MATCH, so a grpcurl failure or empty response
falsely passed. They now require the RPC to succeed with a non-empty body
AND assert the expected fail-open reason_code (AFFINITY_HINT) — an RPC error
is now a red, not a green. Dropped the stats from the seed ingest so the
fail-open code is deterministically AFFINITY_HINT rather than possibly
TENANT_HOT.
@github-actions

Copy link
Copy Markdown

Codex review

Files reviewed

  • cmd/
    • [reviewed] cmd/kvevent-subscriber/main.go
  • docs/design/
    • [reviewed] docs/design/grpc-contract.md
    • [reviewed] docs/design/kvevent-subscriber-wiring.md
  • docs/reference-stack/scripts/
    • [reviewed] docs/reference-stack/scripts/default_install_smoke.sh
  • pkg/adapters/engine/
    • [reviewed] pkg/adapters/engine/config.go
    • [reviewed] pkg/adapters/engine/events.go
    • [reviewed] pkg/adapters/engine/forwarder.go
    • [reviewed] pkg/adapters/engine/lora_adapter_test.go
    • [reviewed] pkg/adapters/engine/mapper.go
    • [reviewed] pkg/adapters/engine/mapper_test.go
    • [reviewed] pkg/adapters/engine/positional.go
    • [reviewed] pkg/adapters/engine/positional_test.go
  • pkg/index/
    • [reviewed] pkg/index/adapter_partition_test.go
    • [reviewed] pkg/index/index.go
    • [reviewed] pkg/index/lfu_eviction_test.go
  • pkg/server/
    • [reviewed] pkg/server/adapter_partition_test.go
    • [reviewed] pkg/server/inferencecache_service.go
    • [reviewed] pkg/server/lmcache_offload_integration_test.go
  • pkg/server/proto/inferencecache/v1alpha1/
    • [skipped — generated] pkg/server/proto/inferencecache/v1alpha1/inferencecache.pb.go
  • proto/inferencecache/v1alpha1/
    • [reviewed] proto/inferencecache/v1alpha1/inferencecache.proto

Findings

Blocking

None.

Should-fix

  • proto/inferencecache/v1alpha1/inferencecache.proto:106 — The contract incorrectly says a “single-adapter” deployment uses the empty partition and is unaffected. The subscriber assigns every non-nil LoRA ID a name or lora:<id>, including single-LoRA deployments, so following this guidance causes silent lookup misses and contradicts docs/design/grpc-contract.md:270.
  • pkg/index/index.go:2553 — Tenant-quota eviction’s tie-break remains nondeterministic when two keys share age, prefixHash, and adapter but differ by model or hash scheme. Include all remaining prefixKey fields before returning equality; otherwise victim selection still depends on map iteration despite the new determinism comment.

Nit

None.

Per-category coverage

  1. Vendor-neutral naming: Clean
  2. Contract & spec / docs sync: findings above at: proto/inferencecache/v1alpha1/inferencecache.proto:106
  3. gRPC fail-open semantics: Clean
  4. Quality (tests / error wrapping / package placement / generated drift): findings above at: pkg/index/index.go:2553

Verdict

changes-requested

Two more review follow-ups on the LoRA index-partition change.

1. proto/inferencecache/v1alpha1/inferencecache.proto — the adapter_id (11)
   comment still carried the "single-adapter deployment uses the empty
   partition and is unaffected" claim that was corrected everywhere else,
   contradicting grpc-contract.md/config.go/main.go. Applied the same
   correction: only a nil lora_id uses the "" partition; any LoRA adapter,
   even one, resolves to a name or lora:<id> and needs end-to-end identifier
   agreement. Regenerated the .pb.go (the comment flows into generated code;
   proto-gen stays idempotent) and gave the request/response adapter_id fields
   an inline description in grpc-contract.md to match.

2. pkg/index/index.go — the tenant-quota eviction tie-break added adapter but
   still tied on keys differing by model or hashScheme, leaving the victim
   dependent on map iteration order (contradicting its own comment). It now
   breaks age ties on the full remaining key (model, hashScheme, adapter,
   prefixHash), mirroring the cap-sweep comparator. Added a test that pins
   deterministic victim selection across each of those fields (64 fresh index
   instances per field so an incomplete comparator flakes here).
@github-actions

Copy link
Copy Markdown

Codex review

Files reviewed

  • cmd/
    • [reviewed] cmd/kvevent-subscriber/main.go
  • docs/design/
    • [reviewed] docs/design/grpc-contract.md
    • [reviewed] docs/design/kvevent-subscriber-wiring.md
  • docs/reference-stack/scripts/
    • [reviewed] docs/reference-stack/scripts/default_install_smoke.sh
  • pkg/adapters/engine/
    • [reviewed] pkg/adapters/engine/config.go
    • [reviewed] pkg/adapters/engine/events.go
    • [reviewed] pkg/adapters/engine/forwarder.go
    • [reviewed] pkg/adapters/engine/lora_adapter_test.go
    • [reviewed] pkg/adapters/engine/mapper.go
    • [reviewed] pkg/adapters/engine/mapper_test.go
    • [reviewed] pkg/adapters/engine/positional.go
    • [reviewed] pkg/adapters/engine/positional_test.go
  • pkg/index/
    • [reviewed] pkg/index/adapter_partition_test.go
    • [reviewed] pkg/index/index.go
    • [reviewed] pkg/index/lfu_eviction_test.go
    • [reviewed] pkg/index/tenant_quota_test.go
  • pkg/server/
    • [reviewed] pkg/server/adapter_partition_test.go
    • [reviewed] pkg/server/inferencecache_service.go
    • [reviewed] pkg/server/lmcache_offload_integration_test.go
  • pkg/server/proto/inferencecache/v1alpha1/
    • [skipped — generated] pkg/server/proto/inferencecache/v1alpha1/inferencecache.pb.go
  • proto/inferencecache/v1alpha1/
    • [reviewed] proto/inferencecache/v1alpha1/inferencecache.proto

Findings

Blocking

  • cmd/kvevent-subscriber/main.go:53 — The adapter mapping is a static startup flag, so adapters loaded or reloaded at runtime cannot reliably acquire their stable name; an unknown runtime ID falls back to replica-local lora:<id>, which can alias different adapters across replicas. This leaves the motivating live load/unload scenario unsafe unless the subscriber can resolve the runtime registry dynamically.

  • docs/design/kvevent-subscriber-wiring.md:178 — The managed sidecar injection path does not expose --lora-adapter-names, yet the documentation tells operators to set it directly on an injected container they do not define. Consequently ordinary CacheBackend-managed deployments cannot configure the stable mapping required to prevent cross-replica aliasing.

Should-fix

None.

Nit

  • pkg/index/index.go:272 — The comment says “single-adapter behavior is unchanged,” contradicting the surrounding contract that every non-nil LoRA ID requires an agreed nonempty identifier. Change this to “non-LoRA behavior is unchanged.”

Per-category coverage

  1. Vendor-neutral naming: Clean
  2. Contract & spec / docs sync: findings above at: docs/design/kvevent-subscriber-wiring.md:178, pkg/index/index.go:272
  3. gRPC fail-open semantics: Clean
  4. Quality (tests / error wrapping / package placement / generated drift): findings above at: cmd/kvevent-subscriber/main.go:53

Verdict

changes-requested

Name the two gaps the startup flag can't close — runtime-loaded adapters,
and the managed (webhook-injected) sidecar — as tracked follow-ups, so the
docs stop implying they're solved by "supply the map" / "set it explicitly".
Correct the index.go partition comment: the default partition is the non-LoRA
case, not "single-adapter".
@github-actions

Copy link
Copy Markdown

Codex review

Files reviewed

  • cmd/kvevent-subscriber/
    • main.go [reviewed]
  • docs/design/
    • grpc-contract.md [reviewed]
    • kvevent-subscriber-wiring.md [reviewed]
  • docs/reference-stack/scripts/
    • default_install_smoke.sh [reviewed]
  • pkg/adapters/engine/
    • config.go [reviewed]
    • events.go [reviewed]
    • forwarder.go [reviewed]
    • lora_adapter_test.go [reviewed]
    • mapper.go [reviewed]
    • mapper_test.go [reviewed]
    • positional.go [reviewed]
    • positional_test.go [reviewed]
  • pkg/index/
    • adapter_partition_test.go [reviewed]
    • index.go [reviewed]
    • lfu_eviction_test.go [reviewed]
    • tenant_quota_test.go [reviewed]
  • pkg/server/
    • adapter_partition_test.go [reviewed]
    • inferencecache_service.go [reviewed]
    • lmcache_offload_integration_test.go [reviewed]
  • pkg/server/proto/inferencecache/v1alpha1/
    • inferencecache.pb.go [skipped — generated]
  • proto/inferencecache/v1alpha1/
    • inferencecache.proto [reviewed]

Findings

Blocking

  • docs/design/kvevent-subscriber-wiring.md:181 — The implementation knowingly leaves runtime-loaded adapters and every managed injected subscriber on the unsafe lora:<id> fallback. Because the same numeric ID can identify different adapters on different replicas, those replicas can still alias into one shared partition and return exactly the wrong-cache hint this PR claims to prevent.

Should-fix

  • pkg/index/index.go:897 — Empty adapter_id doubles as both the default/base-model partition and the legacy wildcard, so a base-model eviction from the new subscriber sweeps matching LoRA partitions too. Add presence or an explicit scope discriminator so modern producers can target "" without requesting a cross-partition sweep.

  • docs/design/lookuproute-ranking.md:29 — The ranking design still defines exact and chain keys without adapter_id at several locations, leaving the authoritative algorithm description inconsistent with the implementation.

  • docs/design/cachebackend-api.md:156 — The CacheBackend design still states that the index key is (tenant, model, hash_scheme, prefix_hash). It must document the adapter partition, particularly because this section describes the subscriber integration producing those entries.

  • docs/operations/index-sizing.md:4 — Index sizing and quota documentation still counts distinct keys without adapter_id. Multiple adapters now multiply key cardinality and quota consumption, so the current capacity guidance underestimates memory and eviction pressure.

Nit

None.

Per-category coverage

  1. Vendor-neutral naming: Clean
  2. Contract & spec / docs sync: findings above at: docs/design/lookuproute-ranking.md:29, docs/design/cachebackend-api.md:156, docs/operations/index-sizing.md:4
  3. gRPC fail-open semantics: Clean
  4. Quality (tests / error wrapping / package placement / generated drift): findings above at: docs/design/kvevent-subscriber-wiring.md:181, pkg/index/index.go:897

Verdict

changes-requested

An adapter-aware producer emits adapter="" for a genuine base-model
eviction, so the "" cross-partition sweep can also drop live LoRA-partition
hints for the same prefix hash. Conservative soft state (re-added by
ReportCacheState) and a strict non-regression, but the comment shouldn't
frame "" as only the pre-adapter case. Precise fix (adapter_id presence on
the event) is a tracked follow-up.
@github-actions

Copy link
Copy Markdown

Codex review

Files reviewed

  • cmd/
    • [reviewed] cmd/kvevent-subscriber/main.go
  • docs/design/
    • [reviewed] docs/design/grpc-contract.md
    • [reviewed] docs/design/kvevent-subscriber-wiring.md
  • docs/reference-stack/scripts/
    • [reviewed] docs/reference-stack/scripts/default_install_smoke.sh
  • pkg/adapters/engine/
    • [reviewed] config.go
    • [reviewed] events.go
    • [reviewed] forwarder.go
    • [reviewed] lora_adapter_test.go
    • [reviewed] mapper.go
    • [reviewed] mapper_test.go
    • [reviewed] positional.go
    • [reviewed] positional_test.go
  • pkg/index/
    • [reviewed] adapter_partition_test.go
    • [reviewed] index.go
    • [reviewed] lfu_eviction_test.go
    • [reviewed] tenant_quota_test.go
  • pkg/server/
    • [reviewed] adapter_partition_test.go
    • [reviewed] inferencecache_service.go
    • [reviewed] lmcache_offload_integration_test.go
  • pkg/server/proto/inferencecache/v1alpha1/
    • [skipped — generated] inferencecache.pb.go
  • proto/inferencecache/v1alpha1/
    • [reviewed] inferencecache.proto

Findings

Blocking

pkg/adapters/engine/config.go:68 — The unmapped fallback partitions on the unstable engine load-order integer. Two replicas can assign the same lora_id to different adapters and consequently ingest both under lora:<id>, recreating the exact cross-adapter alias this PR promises to eliminate; this is especially exposed because the documented managed-sidecar path cannot provide the mapping and runtime-loaded adapters cannot update it.

Should-fix

pkg/adapters/engine/lora_adapter_test.go:80 — The fallback test only proves isolation from the base partition within one replica and therefore blesses the unsafe case. Add a heterogeneous two-replica/runtime-load test demonstrating that different adapters can share the same fallback identifier, then require a design that fails closed or resolves a genuinely stable identity.

docs/design/kvevent-subscriber-wiring.md:181 — The documented runtime-load and managed-injection limitations are not merely reduced hit quality: they can map different adapters to the same partition across replicas and return a wrong routing hint. The docs should not characterize this as harmless or “strictly fewer collisions”; the feature needs a safe managed/dynamic identity path before claiming alias prevention.

Nit

None.

Per-category coverage

  1. Vendor-neutral naming: Clean
  2. Contract & spec / docs sync: findings above at: pkg/adapters/engine/config.go:68, docs/design/kvevent-subscriber-wiring.md:181
  3. gRPC fail-open semantics: Clean
  4. Quality (tests / error wrapping / package placement / generated drift): findings above at: pkg/adapters/engine/lora_adapter_test.go:80

Verdict

changes-requested

Reconcile the adapter index partition (this branch) with #154 (T1/T2 tier
tagging from the block lifecycle) and #156 (subscriber stats). Key resolutions:

- proto PrefixEntry: keep both new fields — main's CacheTier tier = 5, and
  move this branch's adapter_id from 5 to the next free number, 6. All other
  adapter_id sites (11/5/8/7) were conflict-free. pb.go regenerated.
- eviction path (positional.go/forwarder.go): each evicted prefix now carries
  BOTH its adapter partition (scope the drop) AND its token count (re-report at
  T2 when a paired L2 tier retains it). The T2 downgrade re-report is stamped
  with the entry's adapter so it lands in the right partition.
- BlockStored entries stamp adapter_id AND Tier T1; subscriber keeps both the
  --ignore-block-removed (now T2-aware) and --lora-adapter-names flags.
- docs (grpc-contract.md): PrefixEntry documents tier=5 + adapter_id=6.

Full build, go test ./..., vet, gofmt, and buf lint all pass.
@github-actions

Copy link
Copy Markdown

Codex review

Files reviewed

  • cmd/
    • kvevent-subscriber/main.go [reviewed]
  • docs/design/
    • grpc-contract.md [reviewed]
    • kvevent-subscriber-wiring.md [reviewed]
  • docs/reference-stack/scripts/
    • default_install_smoke.sh [reviewed]
  • pkg/adapters/engine/
    • config.go [reviewed]
    • events.go [reviewed]
    • forwarder.go [reviewed]
    • lora_adapter_test.go [reviewed]
    • mapper.go [reviewed]
    • mapper_test.go [reviewed]
    • positional.go [reviewed]
    • positional_test.go [reviewed]
  • pkg/index/
    • adapter_partition_test.go [reviewed]
    • index.go [reviewed]
    • lfu_eviction_test.go [reviewed]
    • tenant_quota_test.go [reviewed]
  • pkg/server/
    • adapter_partition_test.go [reviewed]
    • inferencecache_service.go [reviewed]
    • lmcache_offload_integration_test.go [reviewed]
  • pkg/server/proto/inferencecache/v1alpha1/
    • inferencecache.pb.go [skipped — generated]
  • proto/inferencecache/v1alpha1/
    • inferencecache.proto [reviewed]

Findings

Blocking

None.

Should-fix

  • docs/design/grpc-contract.md:267 — The contract says PrefixEntry.adapter_id is field 5, but field 5 is tier and the proto assigns adapter_id = 6. Incorrect wire numbers can produce incompatible client implementations.
  • docs/design/grpc-contract.md:60 — The documented last-write-wins identity omits adapter_id, incorrectly implying that re-reporting the same hash under another adapter moves the original entry’s tier.
  • proto/inferencecache/v1alpha1/inferencecache.proto:208 — The PrefixEntry.tier comment likewise omits adapter_id from the re-report identity, contradicting the implemented partitioned key.
  • docs/design/lookuproute-ranking.md:29 — The primary ranking design still defines the lookup key without adapter_id, so it no longer describes the implemented isolation boundary.
  • docs/design/cachebackend-api.md:190 — The CacheBackend design still states that the index key is (tenant, model, hash_scheme, prefix_hash), omitting the new adapter partition.
  • docs/concepts/cachetenant-identity-and-quota.md:23 — The quota unit omits adapter_id; identical content under two adapters now consumes two distinct quota entries.
  • docs/operations/index-sizing.md:4 — The sizing model omits adapter_id, understating key cardinality and memory usage for multi-adapter workloads.
  • docs/reference/metrics.md:51 — The metric contract omits adapter_id from the distinct-entry unit, so operators may misinterpret increases caused by adapter partitions.
  • docs/reference/reason-codes.md:37PREFIX_MATCH is documented against an adapter-blind identity even though a match now requires the same adapter partition.
  • docs/reference/reason-codes.md:40 — The TENANT_HOT description calls the failed exact-match key adapter-blind; it should distinguish the adapter-scoped content match from the deliberately adapter-blind serving scope.

Nit

  • pkg/index/index.go:584 — The prefixesByTenant comment omits adapter from the distinct key counted by quotas.
  • pkg/index/index.go:1712 — The Aggregate comment also omits adapter, making its documented counting unit stale.

Per-category coverage

  1. Vendor-neutral naming: Clean
  2. Contract & spec / docs sync: findings above at: docs/design/grpc-contract.md:60, docs/design/grpc-contract.md:267, proto/inferencecache/v1alpha1/inferencecache.proto:208, docs/design/lookuproute-ranking.md:29, docs/design/cachebackend-api.md:190, docs/concepts/cachetenant-identity-and-quota.md:23, docs/operations/index-sizing.md:4, docs/reference/metrics.md:51, docs/reference/reason-codes.md:37, docs/reference/reason-codes.md:40
  3. gRPC fail-open semantics: Clean
  4. Quality (tests / error wrapping / package placement / generated drift): findings above at: pkg/index/index.go:584, pkg/index/index.go:1712

Verdict

changes-requested

…files)

Post-merge consistency, all in files this PR already changes:
- grpc-contract.md: the adapter-partition section said PrefixEntry.adapter_id
  is field 5; the merge moved it to 6 (5 is now tier). Correct the number.
- grpc-contract.md + proto PrefixEntry.tier comment: the tier last-write-wins
  identity now includes adapter_id (partitioned key), not (…, prefix_hash) alone.
- index.go: prefixesByTenant + Aggregate comments count a distinct key that now
  includes adapter.

Cross-doc key drift in files this PR does not touch stays a tracked follow-up.
pb.go regenerated (comment mirror only, no wire change); go test ./..., vet,
gofmt, buf lint pass.
@github-actions

Copy link
Copy Markdown

Codex review

Files reviewed

  • cmd/
    • [reviewed] cmd/kvevent-subscriber/main.go
  • docs/design/
    • [reviewed] docs/design/grpc-contract.md
    • [reviewed] docs/design/kvevent-subscriber-wiring.md
  • docs/reference-stack/scripts/
    • [reviewed] docs/reference-stack/scripts/default_install_smoke.sh
  • pkg/adapters/engine/
    • [reviewed] pkg/adapters/engine/config.go
    • [reviewed] pkg/adapters/engine/events.go
    • [reviewed] pkg/adapters/engine/forwarder.go
    • [reviewed] pkg/adapters/engine/lora_adapter_test.go
    • [reviewed] pkg/adapters/engine/mapper.go
    • [reviewed] pkg/adapters/engine/mapper_test.go
    • [reviewed] pkg/adapters/engine/positional.go
    • [reviewed] pkg/adapters/engine/positional_test.go
  • pkg/index/
    • [reviewed] pkg/index/adapter_partition_test.go
    • [reviewed] pkg/index/index.go
    • [reviewed] pkg/index/lfu_eviction_test.go
    • [reviewed] pkg/index/tenant_quota_test.go
  • pkg/server/
    • [reviewed] pkg/server/adapter_partition_test.go
    • [reviewed] pkg/server/inferencecache_service.go
    • [reviewed] pkg/server/lmcache_offload_integration_test.go
  • pkg/server/proto/inferencecache/v1alpha1/
    • [skipped — generated] pkg/server/proto/inferencecache/v1alpha1/inferencecache.pb.go
  • proto/inferencecache/v1alpha1/
    • [reviewed] proto/inferencecache/v1alpha1/inferencecache.proto

Findings

Blocking

  • pkg/adapters/engine/config.go:68 — Unmapped LoRA IDs fall back to a replica-local load-order integer, so two replicas can still place different adapters in the same lora:<id> partition. This recreates the wrong-replica alias the PR promises to eliminate; an unmapped non-nil ID should fail closed on ingest unless a globally stable mapping is available.

Should-fix

  • pkg/index/index.go:486 — Adapter-blind serving scope allows TENANT_HOT and AFFINITY_HINT to select replicas that may not have the requested runtime-loaded adapter. Because adapter availability can differ across live replicas, this can route a request to an engine unable to serve it; nonempty adapter requests need adapter-aware eligibility or must suppress these fallbacks when residency is unknown.

Nit

None.

Per-category coverage

  1. Vendor-neutral naming: Clean
  2. Contract & spec / docs sync: findings above at: pkg/adapters/engine/config.go:68, pkg/index/index.go:486
  3. gRPC fail-open semantics: findings above at: pkg/index/index.go:486
  4. Quality (tests / error wrapping / package placement / generated drift): Clean

Verdict

changes-requested

An unmapped non-nil lora_id previously fell back to a replica-local
"lora:<id>" partition, which could place different adapters in one partition
across replicas whose --lora-modules order differs — the cross-replica alias
the partition exists to prevent. Now AdapterID reports ok=false for an unmapped
id and the Reporter drops the BlockStored (warned once per id) rather than
indexing it. The adapter gets no routing hint — a cache miss, never a wrong
replica — until --lora-adapter-names maps it, so LoRA caching now REQUIRES the
map. Base-model (nil lora_id) traffic is unchanged.

Docs (config/flag/proto/grpc-contract/wiring) and tests updated; new
TestReporterFailsClosedOnUnmappedAdapter. go test ./..., vet, gofmt, buf lint pass.
@github-actions

Copy link
Copy Markdown

Codex review

Files reviewed

  • cmd/
    • [reviewed] cmd/kvevent-subscriber/main.go
  • docs/design/
    • [reviewed] docs/design/grpc-contract.md
    • [reviewed] docs/design/kvevent-subscriber-wiring.md
  • docs/reference-stack/scripts/
    • [reviewed] docs/reference-stack/scripts/default_install_smoke.sh
  • pkg/adapters/engine/
    • [reviewed] config.go
    • [reviewed] events.go
    • [reviewed] forwarder.go
    • [reviewed] lora_adapter_test.go
    • [reviewed] mapper.go
    • [reviewed] mapper_test.go
    • [reviewed] positional.go
    • [reviewed] positional_test.go
  • pkg/index/
    • [reviewed] adapter_partition_test.go
    • [reviewed] index.go
    • [reviewed] lfu_eviction_test.go
    • [reviewed] tenant_quota_test.go
  • pkg/server/
    • [reviewed] adapter_partition_test.go
    • [reviewed] inferencecache_service.go
    • [reviewed] lmcache_offload_integration_test.go
  • pkg/server/proto/inferencecache/v1alpha1/
    • [skipped — generated] inferencecache.pb.go
  • proto/inferencecache/v1alpha1/
    • [reviewed] inferencecache.proto

Findings

Blocking

None.

Should-fix

  • docs/design/kvevent-subscriber-wiring.md:231 — Runtime-loaded adapters cannot participate in caching — the motivating runtime load/reload case is dropped unless its engine-local ID was predicted in a static startup flag. Either add dynamic identity resolution or narrow the PR’s advertised runtime-LoRA support.
  • docs/design/kvevent-subscriber-wiring.md:235 — The Kubernetes-managed sidecar path cannot configure --lora-adapter-names — consequently every non-nil LoRA event is discarded on the project’s primary managed deployment path. Thread a mapping through the managed API/injection path or explicitly scope this feature to self-managed subscribers.
  • pkg/adapters/engine/config.go:61 — The implementation drops an unmapped ID, while the PR description promises a lora:<id> fallback. The safer fail-closed behavior should remain, but the PR contract must be corrected so operators do not expect hints from unmapped adapters.
  • proto/inferencecache/v1alpha1/inferencecache.proto:220PrefixEntry.adapter_id is field 6, whereas the PR wire-change table says field 5, which is already occupied by tier. Correct the PR description to prevent clients from implementing the wrong wire contract.

Nit

None.

Per-category coverage

  1. Vendor-neutral naming: Clean
  2. Contract & spec / docs sync: findings above at: pkg/adapters/engine/config.go:61, proto/inferencecache/v1alpha1/inferencecache.proto:220
  3. gRPC fail-open semantics: Clean
  4. Quality (tests / error wrapping / package placement / generated drift): findings above at: docs/design/kvevent-subscriber-wiring.md:231, docs/design/kvevent-subscriber-wiring.md:235

Verdict

changes-requested

@EdHasNoLife
EdHasNoLife merged commit a1116aa into main Jul 24, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants