Skip to content

fix(server): layered freshness policy for index queries - #496

Merged
16bit-ykiko merged 11 commits into
mainfrom
fix/query-freshness-policy
Jul 7, 2026
Merged

fix(server): layered freshness policy for index queries#496
16bit-ykiko merged 11 commits into
mainfrom
fix/query-freshness-policy

Conversation

@16bit-ykiko

@16bit-ykiko 16bit-ykiko commented Jul 7, 2026

Copy link
Copy Markdown
Member

Supersedes #493 (bounded-wait approach, rejected). Instead of paying a fixed
delay for a still-incomplete freshness guarantee, index queries now follow a
layered freshness policy.

Cursor resolution waits for the file's compile

Requests that resolve a cursor position into a symbol (definition,
references, declaration, type definition, implementation, call/type
hierarchy) now await the current file's compile before querying the index —
the same await, with no timeout, that hover and every other AST-backed
request already uses. Previously most of these queried the index
immediately, so a query racing a didChange could resolve the cursor
against pre-edit positions and name the wrong symbol.

Cross-file results honor the reindex queue's pending reason

When a query fans out to other files' index contributions, files sitting in
the background reindex queue split two ways, decided by why they were
enqueued:

  • Dependency-only staleness (a header they include changed — the common
    cascade case): their own text did not move, so the existing rows keep
    serving until the reindex lands.
  • Own content changed (disk edit, close after saved edits, compile
    command change): their rows describe text that no longer exists, so their
    contribution is skipped until the reindex lands.

The invalidation engine knows the cause at enqueue time, so the Indexer
records a two-level pending reason (DepsOnly | ContentChanged, upgrades
are absorbing) and the query side checks it in O(1) with no I/O. A file
re-enqueued while its index task is in flight keeps its newer pending state
(ticket-guarded clear). didClose classifies by comparing the disk content
against the shard's stored snapshot — a browse-and-close keeps serving its
rows, a close after saved edits does not. The startup sweep enqueues as
deps-only so a warm index cache keeps serving through the initial scan.
With indexing disabled the gate is off: serving last-known rows beats a
permanent hole.

Results may therefore be incomplete while the queue drains. That is now a
documented contract on IndexQuery (replacing two FIXMEs), together with
two recorded-not-implemented TODOs: a blocking "complete results" query
mode, and a dedicated "is the index ready?" request for agent consumers.

Fixed along the way: the background round used to spawn its per-file task
as an immediately-invoked capturing lambda. A lambda coroutine's captures
live in the lambda object, which dies at the end of the spawning statement,
so anything the task read after its first suspension was dangling. The task
is now a member coroutine taking its inputs as parameters, which are copied
into the coroutine frame.

Buffer desync is tolerated and logged

An incremental didChange whose range does not fit the buffer (client and
server views drifted) was silently discarded; it is now discarded with an
ERROR log. No desync flag, no refusal of service — a full-document change
or reopen resynchronizes.

Tests

  • Unit: pending-reason upgrade semantics; deps-only pending files keep
    serving rows while content-changed ones are skipped (real shards built
    through the test compiler), including line-based symbol resolution;
    didClose classification (no shard / current shard / divergent shard);
    the invalidator suite migrated to the split effect lists with
    complementary-list assertions.
  • Integration: definition/references immediately after didChange resolve
    correctly against the edited buffer; an out-of-range edit produces an
    error log and later requests keep working. The pending-flag clear after
    a reindex lands is exercised end-to-end by the existing file-tracker
    tests (a stuck flag would time them out).
  • Not covered deterministically: the guard that keeps a file's pending
    state when it is re-enqueued while its index task is in flight — forcing
    that interleaving needs an injectable suspension point in the index
    task, which does not exist today.

Summary by CodeRabbit

  • New Features
    • Navigation and symbol lookups stay consistent immediately after edits, even while background indexing is still running.
    • Queries apply a freshness contract, skipping index contributions that are known stale until reindex completes.
  • Bug Fixes
    • Definition, references, and hierarchy views now await compilation before using index-backed results, avoiding stale/superseded session state.
    • Invalid incremental edits are logged with details and dropped without breaking subsequent requests.
    • Reindex behavior is refined for dependency-only vs content-changed cases to improve correctness.
  • Tests
    • Added integration coverage for post-edit navigation accuracy and desync range tolerance, plus new unit tests for query freshness gating.

Cursor resolution awaits the current file's compile like every other
AST-backed request; cross-file results honor the reindex queue's pending
reason (deps-only pending files keep serving, content-changed ones are
skipped until reindexed); out-of-sync didChange ranges are dropped with
an error log instead of silently. Replaces the rejected bounded-wait
approach.
@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds per-file reindex reasons and tickets, splits invalidation into deps-only and content-changed paths, skips stale merged-index contributions in queries, requires compiled sessions for index-backed handlers, logs unmappable edits, and updates tests for the new freshness behavior.

Changes

Reindex reason and query freshness

Layer / File(s) Summary
ReindexReason and pending-state API
src/server/compiler/indexer.h
Adds ReindexReason, updates enqueue and pending-state accessors, and introduces ticketed per-path pending tracking.
Indexer background scheduling
src/server/compiler/indexer.cpp
Loads unreadable shards as content-changed work, refreshes pending state on enqueue, runs ticket-gated index tasks, and reschedules background rounds after join.
Split invalidation queues
src/server/state/invalidator.h, src/server/state/invalidator.cpp
Replaces the single reindex queue with split queues and updates cascade, file-event, and deduplication logic to populate them.
MasterServer reason wiring
src/server/transport/master_server.h, src/server/transport/master_server.cpp
Reorders composed service initialization and enqueues split dirty work with explicit reindex reasons during dispatch and workspace loading.
IndexQuery stale-contribution gating
src/server/service/query.h, src/server/service/query.cpp
Adds the indexer dependency and freshness contract, then skips content-changed merged-index contributions across cursor resolution, relations, definitions, references, and line-based symbol lookup.
FeatureRouter ensure_compiled gate
src/server/service/feature_router.cpp
Adds ensure_compiled and generation checks before index-backed lookups in definition, references, declaration, type_definition, implementation, and call/type hierarchy handlers.
SessionStore desync logging
src/server/state/session_store.cpp, src/server/state/session_store.h
Logs an error when incremental edit ranges cannot be mapped to buffer offsets and updates the related comments.
Invalidator and query freshness tests
tests/unit/server/invalidator_tests.cpp, tests/unit/server/query_freshness_tests.cpp, tests/integration/features/test_query_freshness.py, tests/integration/lifecycle/test_protocol_edges.py
Updates invalidator tests for split queues, adds unit coverage for pending reason and freshness gating, and adds integration coverage for post-edit navigation and desync logging.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant FeatureRouter
  participant Compiler
  participant IndexQuery
  participant Indexer
  Client->>FeatureRouter: definition/references request
  FeatureRouter->>Compiler: ensure_compiled(session)
  FeatureRouter->>IndexQuery: query with session
  IndexQuery->>Indexer: pending_reason(path_id)
Loading

Possibly related PRs

  • clice-io/clice#406: Modifies the same indexing scheduling surface, including Indexer background work and MasterServer dispatching.
  • clice-io/clice#462: Touches the same indexing/query freshness paths, including ensure_compiled and session-generation checks.
  • clice-io/clice#484: Refactors the same invalidation/indexing pipeline that now feeds split reindex reasons.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: layered freshness handling for server index queries.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/query-freshness-policy

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/server/service/query.cpp (1)

158-158: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider extracting the repeated skip predicate.

is_path_open(file_id) || skip_stale_contribution(file_id) is duplicated across six call sites (query_relations, find_definition_location, collect_grouped_relations, collect_unique_targets, get_definition_text, collect_references). A small private helper centralizes the freshness-gating rule and reduces the risk of one site drifting from the others in a future edit.

♻️ Proposed refactor
+    /// Whether a reference file's merged-index contribution should be
+    /// skipped for this query: either an open session already covers it,
+    /// or its own content changed and reindex hasn't landed.
+    bool skip_reference_file(std::uint32_t path_id) const {
+        return is_path_open(path_id) || skip_stale_contribution(path_id);
+    }

Then replace each is_path_open(file_id) || skip_stale_contribution(file_id) with skip_reference_file(file_id).

Also applies to: 256-256, 310-310, 345-345, 431-431, 474-474

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/service/query.cpp` at line 158, The freshness gate
`is_path_open(file_id) || skip_stale_contribution(file_id)` is repeated in
several query paths, so extract it into a small private helper in `query.cpp`
and use that helper everywhere. Add a single method such as
`skip_reference_file` near the existing query helpers, then replace the
duplicated predicate in `query_relations`, `find_definition_location`,
`collect_grouped_relations`, `collect_unique_targets`, `get_definition_text`,
and `collect_references` so they all share the same rule.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/server/compiler/indexer.cpp`:
- Around line 555-560: The scheduling path in indexer.cpp needs to handle
entries added after the dispatch loop exits but before worker joins complete,
because schedule() can return with indexing_active still true and leave queued
work stranded. Update the queue-drain handling around index_queue_pos,
index_queue, and reindex_reasons so that any entries appended during worker
joining are rescheduled into a new round rather than only being cleared when the
queue is fully drained. Use the existing schedule()/enqueue() flow and the
pending_ids/reindex_reasons bookkeeping to ensure the next round starts whenever
index_queue_pos is still behind index_queue.size().
- Around line 453-466: Gate the merge in Indexer::run_index_task, not just the
reindex_reasons cleanup: an older in-flight task can still call index_one() and
merge stale TUIndex data after a newer ticket has taken over. Add the ticket
check around the index_one() result handling so only the current ticket is
allowed to commit the merge, and keep the existing reindex_reasons erase logic
tied to the same ticket in Indexer::run_index_task.

---

Nitpick comments:
In `@src/server/service/query.cpp`:
- Line 158: The freshness gate `is_path_open(file_id) ||
skip_stale_contribution(file_id)` is repeated in several query paths, so extract
it into a small private helper in `query.cpp` and use that helper everywhere.
Add a single method such as `skip_reference_file` near the existing query
helpers, then replace the duplicated predicate in `query_relations`,
`find_definition_location`, `collect_grouped_relations`,
`collect_unique_targets`, `get_definition_text`, and `collect_references` so
they all share the same rule.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a2720760-8cce-4af2-93d6-1b8157d52e5f

📥 Commits

Reviewing files that changed from the base of the PR and between 00f0e53 and 9ff09a6.

📒 Files selected for processing (16)
  • src/server/compiler/indexer.cpp
  • src/server/compiler/indexer.h
  • src/server/service/feature_router.cpp
  • src/server/service/feature_router.h
  • src/server/service/query.cpp
  • src/server/service/query.h
  • src/server/state/invalidator.cpp
  • src/server/state/invalidator.h
  • src/server/state/session_store.cpp
  • src/server/state/session_store.h
  • src/server/transport/master_server.cpp
  • src/server/transport/master_server.h
  • tests/integration/features/test_query_freshness.py
  • tests/integration/lifecycle/test_protocol_edges.py
  • tests/unit/server/invalidator_tests.cpp
  • tests/unit/server/query_freshness_tests.cpp

Comment thread src/server/compiler/indexer.cpp
Comment thread src/server/compiler/indexer.cpp

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9ff09a6fec

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/server/compiler/indexer.cpp
Comment thread src/server/compiler/indexer.cpp

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: aeb191c1ee

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/server/state/invalidator.cpp Outdated
Comment thread src/server/service/feature_router.cpp Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/server/service/feature_router.cpp`:
- Around line 109-114: The compile-and-generation freshness check is duplicated
across the FeatureRouter handlers, so extract that repeated block into a shared
helper in FeatureRouter (for example the existing settle_cursor_file pattern or
a new settle_cursor_session method in feature_router.h/.cpp). Have each handler
call the helper and co_return serde_raw{"null"} when it reports the session is
stale or not compiled, so the policy lives in one place and all 11 call sites
stay consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b6b31f0e-3903-41df-9168-5c7230cc32cf

📥 Commits

Reviewing files that changed from the base of the PR and between aeb191c and 1ccd0b1.

📒 Files selected for processing (6)
  • src/server/compiler/indexer.h
  • src/server/service/feature_router.cpp
  • src/server/state/invalidator.cpp
  • src/server/state/invalidator.h
  • src/server/transport/master_server.cpp
  • tests/unit/server/invalidator_tests.cpp
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/server/state/invalidator.h
  • src/server/transport/master_server.cpp
  • src/server/compiler/indexer.h
  • src/server/state/invalidator.cpp
  • tests/unit/server/invalidator_tests.cpp

Comment thread src/server/service/feature_router.cpp

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1ccd0b123e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/server/compiler/indexer.cpp Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 69ca224bce

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/server/transport/master_server.cpp

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: be847fc215

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/server/service/feature_router.cpp Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 701e5d203c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/server/state/invalidator.cpp
Comment thread src/server/compiler/indexer.cpp
Comment thread src/server/state/invalidator.cpp

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6ac86c12cc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/server/state/invalidator.cpp

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 31334a8356

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/server/compiler/indexer.cpp Outdated
Comment thread src/server/service/feature_router.cpp
Comment thread src/server/compiler/indexer.cpp Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 74f8d9556e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/server/compiler/indexer.cpp Outdated
Comment thread src/server/service/query.cpp
@16bit-ykiko
16bit-ykiko merged commit 87b1726 into main Jul 7, 2026
22 checks passed
@16bit-ykiko
16bit-ykiko deleted the fix/query-freshness-policy branch July 17, 2026 12:55
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.

1 participant