Skip to content

feat(index): automatic flatbuffers serialization via kotatsu codec::fbs, drop flatc - #598

Merged
16bit-ykiko merged 12 commits into
mainfrom
feat/kota-fbs-index
Aug 13, 2026
Merged

16bit-ykiko merged 12 commits into
mainfrom
feat/kota-fbs-index

Conversation

@16bit-ykiko

@16bit-ykiko 16bit-ykiko commented Aug 13, 2026 •

Copy link
Copy Markdown
Member

What

Index blobs are now serialized by reflecting the index types directly through kotatsu's codec::fbs backend. schema.fbs, the flatc toolchain (CMake custom command + pixi flatbuffers dependency) and all hand-written builder/GetRoot code are gone.

Per blob:

  • TUIndex / ProjectIndex (full-deserialization consumers) decode eagerly through verified from_bytes; TUIndex::from now takes a sized buffer and verifies before reading — closing the unverified-GetRoot-without-length holes on the worker→master path.
  • MergedIndex / PreambleState (mmap-and-query consumers) keep their zero-copy model: the blob is verified once at load (table_view::from_bytes) and queried through unchecked per-query views (from_verified_bytes). Hand-rolled sorted-vector binary searches became map_view lookups — the encoder sorts entries by the same canonical ordering the view searches with, so relations index as DenseMap<SymbolHash, DenseMap<Relation, Bitmap>> and occurrences as DenseMap<Occurrence, Bitmap> (struct keys) directly.

No mirror structs. The persisted schema of every blob is the native type itself — there is no parallel …Repr struct copied field by field at serialize time:

  • MergedIndex::Impl doubles as the shard schema: runtime-only fields (canonical_ref_counts, removed, occurrences_cache) are skip-annotated and occupy no slot, serialize() compacts removal-masked rows in place and reflects the impl straight onto the wire, and loading decodes into the impl directly. The zero-copy query views read table_view<MergedIndex::Impl>.
  • PreambleState::serialize takes its TUIndex by value and assembles the blob by moving the rows out of it; per-file contents and the feature arrays (links, inactive regions, open conditionals) are encoded as StringRef/ArrayRef borrows of the compilation's buffers and the caller's arrays. Symbols persist as borrowed {name, kind} entries — find_symbol serves nothing else, and reflecting the full Symbol would drag every symbol's scope and reference bitmap into large preamble blobs.
  • ProjectIndex reflects itself: symbol bitmaps persist their raw pool ids, and the blob stays self-contained through a pool-id→path table (still written garbage-collected: only ids referenced by a symbol or shard). Loading interns the table and remaps every id in place — the per-symbol bitmap rebuild on the encode side is gone.

Type-level changes:

  • Relation::kind stores the raw RelationKind::Kind enum — the wrapper's constructors hide it from reflection, and reflection is what keeps relation vectors as contiguous struct vectors and Relation valid as a map key.
  • Build-time-only fields (IncludeGraph::file_table, TUIndex::file_indices — both keyed by clang::FileID, meaningless outside the compilation) carry KOTATSU_ANNOTATE(skip = true) and never reach the schema; the annotation wrapper inherits the map type, so call sites are untouched.
  • repr specializations map Bitmap to its roaring image, SymbolKind to its underlying value, std::chrono::milliseconds to its count. PathPool uses the imperative repr form, driving the visitor directly: encode writes the interned StringRefs straight to the wire, decode interns one path at a time (rejecting empty paths). A new llvm::StringMap<uint32_t> repr persists the canonical cache as id-sorted key/value pairs — the repr is format-agnostic because the schema layer classifies fields without a format tag, and StringMapEntry is unclassifiable.

Wire format break (sanctioned): cache_format_version 4→5 (old cache directories are swept wholesale), index_format_version 1→3, preamble_format_version 3→5. kotatsu-encoded blobs carry the codec's file identifier, so pre-migration blobs are rejected at verification regardless of version fields.

Hardening folded from self-review

  • TUIndex::from normalizes path_hashes to the path table's length (the verifier checks structure, not cross-field invariants; consumers index it by path id).
  • TUIndex::serialize no longer wipes a deserialized index's path-keyed rows on re-serialization.
  • Verification-failure paths: Indexer::merge ignores unverifiable payloads; a compile result whose index fails verification drops the session index (honest gap) instead of UB.
  • New tests: direct TUIndex round-trip (multi-file), hostile-input rejection for every loader (garbage / truncation / identifier clobber), version-gate positive controls pinning slot alignment, buffer-path vs impl-path relation lookup parity, multi-occurrence binary search, ProjectIndex out-of-range local-id guard, in-place compaction under partial removal (post-serialize in-memory queries + byte-identical re-serialize), PreambleState move-consume path.

All suites green: unit 1186, integration 341 (cache-root-sensitive tests explicitly verified against the v5 directory), smoke 3, snap 395.

Requires kotatsu ≥ 5232e67 (#201: reflected struct map keys, trivially-copyable inline structs, from_verified_bytes); the pin is bumped accordingly.

Fixes #594 — every loader now verifies through kotatsu's verifier, whose table budget scales with blob size by construction (max_tables = size/4 + 16; a wire table occupies at least 4 bytes, so no valid blob can exceed it). No fixed-budget verifier remains, including the hand-tuned 1<<26 the preamble loader carried.

…bs, drop flatc

Index blobs are now serialized by reflecting the index types directly
(kota::codec::fbs::to_bytes) and read back either eagerly (verified
from_bytes: TUIndex, ProjectIndex) or through zero-copy views verified
once at load (table_view: MergedIndex, PreambleState). schema.fbs, the
flatc toolchain and all hand-written builder/GetRoot code are gone.

- Relation::kind stores the raw RelationKind::Kind enum so Relation
  keeps its reflected memcpy image (struct vectors, map keys)
- build-time-only fields (FileID-keyed maps) carry skip annotations and
  never reach the schema
- repr specializations map Bitmap to its roaring image, SymbolKind to
  its underlying value, milliseconds to its count
- TUIndex::from/ProjectIndex::from take sized buffers and verify before
  reading (closes the unverified GetRoot holes)
- wire format break: cache_format_version 4 -> 5, index_format_version
  1 -> 2, preamble_format_version 3 -> 4; old cache directories are
  swept and rebuilt
Code folds: TUIndex::from normalizes path_hashes to the path table's
length (the verifier checks structure, not cross-field invariants);
serialize() no longer wipes a deserialized index's path-keyed rows;
shared deserialize_blob/scan_occurrences_at helpers replace triplicated
decode calls and duplicated binary searches; integration tooling's cache
root caught up to v5; stale flatc-era comments trimmed.

Tests: TUIndex direct round-trip (multi-file, built_at, symbols,
path_file_indices), hostile-input rejection for every loader (garbage,
truncation, identifier clobber), version-gate positive controls pinning
slot alignment, buffer-path relation lookup parity with the impl path,
multi-occurrence binary search, ProjectIndex out-of-range local-id
guard, Indexer::merge garbage rejection.
@coderabbitai

coderabbitai Bot commented Aug 13, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 20172c4a-f188-4d89-be42-50dd44c962bb

📥 Commits

Reviewing files that changed from the base of the PR and between 0c6f281 and 94cb918.

📒 Files selected for processing (3)
  • src/index/preamble_state.cpp
  • src/index/project_index.cpp
  • tests/unit/index/project_index_tests.cpp
🚧 Files skipped from review as they are similar to previous changes (3)
  • tests/unit/index/project_index_tests.cpp
  • src/index/preamble_state.cpp
  • src/index/project_index.cpp

📝 Walkthrough

Walkthrough

The PR removes FlatBuffers generation and migrates index, preamble, project, and translation-unit persistence to Kotatsu codec serialization with verified views. It updates loading APIs, cache versions, relation handling, and corruption tests.

Changes

Codec-backed index persistence

Layer / File(s) Summary
Build and serialization contracts
CMakeLists.txt, cmake/package.cmake, pixi.toml, src/index/serialization.h, src/index/include_graph.h
FlatBuffers generation and the pinned build dependency are removed. Kotatsu representations, annotations, verified blob helpers, occurrence scanning, and format version 3 are added.
Project and translation-unit persistence
src/index/project_index.*, src/index/tu_index.*, tests/unit/index/project_index_tests.cpp, tests/unit/index/persisted_index_tests.cpp, tests/unit/index/tu_index_tests.cpp
ProjectIndex and TUIndex use codec blobs, llvm::StringRef, optional deserialization, path validation, path remapping, transient-state annotations, and updated format metadata. Round-trip and malformed-input tests are added.
Preamble state persistence
src/index/preamble_state.*, tests/unit/index/preamble_state_tests.cpp, src/server/worker/stateless_worker.cpp
PreambleState uses reflected representations and verified table views. The persistence version changes to 5, and serialization consumes TUIndex by move. Corruption and content-preservation tests are added.
Merged index persistence and lookup
src/index/merged_index.cpp, src/index/merged_index.h, tests/unit/index/merged_index_tests.cpp
MergedIndex uses verified codec shard views for persistence and lookup operations. Serialization compacts removed rows in place. Tests cover format validation, corruption rejection, canonical IDs, lookup parity, occurrences, and line starts.
Runtime loading and cache migration
src/server/compiler/compiler.cpp, src/server/compiler/indexer.cpp, src/server/service/query.cpp, src/server/state/workspace.h, tests/unit/server/indexer_tests.cpp, tools/client/workspace.ts
Runtime parsing handles invalid optional results and bounded buffers. Relation filtering is updated, and workspace cache versions change to 5.

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

Mergeability Score: 🟠 High · up to 94cb9

This change replaces the index persistence format and verification paths, but malformed or incompatible cache data can still trigger out-of-bounds writes, excessive allocations, unsafe decoding, missed occurrence results, or invalid path handling. These current-head risks can cause crashes, corrupted index state, or incorrect queries, so the PR should not merge until the affected loaders and lookup paths are hardened.

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.69% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes address #594 by replacing the fixed verifier limit with size-scaled verification and rejecting invalid blobs safely.
Out of Scope Changes check ✅ Passed The serialization migration, version updates, loader hardening, and tests support the linked issue and stated pull request objectives.
Title check ✅ Passed The title clearly summarizes the primary change: replacing FlatBuffers generation with Kotatsu codec serialization and removing flatc.
Description check ✅ Passed The description thoroughly explains the implementation, format changes, issue addressed, hardening, and test results, despite using different section headings.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/kota-fbs-index

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: 4

🧹 Nitpick comments (3)
tests/unit/index/preamble_state_tests.cpp (2)

244-258: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Exercise the query APIs on the minimal blob.

AcceptCurrentVersionBlob proves that a blob carrying only format_version loads. Production code therefore hands out a PreambleState whose paths, files, preamble, symbols, links, inactive_regions and open_conditionals fields are all absent. No test queries that state.

preamble_content() and both lookup_preamble overloads read root[&PreambleStateRepr::preamble] and then index into a nested table that was never written. Add calls to every accessor on this state, so the "absent field reads back empty" assumption is asserted rather than assumed.

🧪 Proposed additional assertions
     dir.touch("current.pch.idx",
               llvm::StringRef(reinterpret_cast<const char*>(blob->data()), blob->size()));
-    EXPECT_TRUE(index::PreambleState::load(dir.path("current.pch.idx")) != nullptr);
+    auto minimal = index::PreambleState::load(dir.path("current.pch.idx"));
+    ASSERT_TRUE(minimal != nullptr);
+
+    // Every field is absent: each accessor must answer empty, not crash.
+    EXPECT_TRUE(minimal->source_path().empty());
+    EXPECT_TRUE(minimal->preamble_content().empty());
+    EXPECT_TRUE(minimal->links().empty());
+    EXPECT_TRUE(minimal->inactive_regions().empty());
+    EXPECT_TRUE(minimal->open_conditionals().empty());
+
+    std::string name;
+    SymbolKind kind;
+    EXPECT_FALSE(minimal->find_symbol(42, name, kind));
+
+    minimal->lookup(42, RelationKind::Reference, [](auto&, auto&) { return true; });
+    minimal->lookup_preamble(0, [](const index::Occurrence&) { return true; });
+    minimal->lookup_preamble(42, RelationKind::Reference, [](const index::Relation&) {
+        return true;
+    });
🤖 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 `@tests/unit/index/preamble_state_tests.cpp` around lines 244 - 258, Extend
AcceptCurrentVersionBlob to exercise every accessor on the minimally encoded
PreambleState, including paths, files, preamble_content(), symbols, links,
inactive_regions, open_conditionals, and both lookup_preamble overloads. Assert
that each absent field or lookup result returns its documented empty/null result
without crashing, using the existing state returned by PreambleState::load.

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

Hoist the duplicated VersionOnly fixture to the enclosing test-suite scope and reuse it in both cases. The same cleanup applies to the duplicate fixture in tests/unit/index/merged_index_tests.cpp, keeping the positive-control blob definitions consistent and easier to maintain.

🤖 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 `@tests/unit/index/preamble_state_tests.cpp` around lines 231 - 233, Move the
duplicated VersionOnly struct declarations out of the individual test cases and
define one shared VersionOnly type at the test suite scope. Update both test
cases to reuse this shared declaration without changing their behavior.

Apply the same fix in `@tests/unit/index/merged_index_tests.cpp` around lines 681
- 718: The same duplicated fixture and remediation are present in this test
file.
src/index/preamble_state.cpp (1)

48-59: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Move the path_id bound check into file_of.

file_of indexes paths[entry[&PreambleFileEntryRepr::path_id]] without a bound check. The only caller checks the bound at Line 155. A future caller (for example, one that materializes the preamble entry) would read out of range on a corrupt blob. Put the check inside file_of and return an optional, so the invariant travels with the code that depends on it.

Note that serialize writes repr.preamble.path_id as paths.size() - 1 (Line 99), which underflows when index.graph.paths is empty. That value is not read today, which keeps the current impact at zero.

🤖 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/index/preamble_state.cpp` around lines 48 - 59, Update file_of to
validate the entry path_id against paths.size() before indexing, and return an
optional PreambleState::File that is empty for invalid IDs. Adjust its
caller(s), including the path around the existing Line 155 check, to consume the
optional and preserve current behavior without duplicating the bound check.
🤖 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/index/merged_index.cpp`:
- Around line 343-380: In the merged-index load path, validate every
include.canonical_id and context.canonical_id against
canonical_ref_counts.size() before incrementing it, rejecting the blob through
the existing load/error mechanism rather than indexing invalid values. Replace
the assert-only handling of deserialize_blob in deserialize/load flow with an
explicit failure check that prevents partially decoded repr data from being
moved into Impl in release builds.

In `@src/index/serialization.h`:
- Around line 110-124: Update the binary search in the surrounding serialization
lookup method to order candidates by range.begin, finding the upper bound where
range.begin is less than or equal to offset instead of comparing range.end. Then
inspect all preceding candidates so containing ranges are not skipped, while
preserving the existing range.contains(offset) matching behavior; add a
regression test covering nested ranges such as [0,100] and [1,2].

In `@src/index/tu_index.cpp`:
- Around line 654-662: Update TUIndex and its serialization/deserialization flow
to persist and validate index_format_version, matching the gate used by
ProjectIndex::from and the contract in serialization.h. Write the current
version when serializing, reject mismatched versions by returning std::nullopt
from TUIndex::from before normalization, and add coverage for a stale but
structurally valid blob.
- Around line 654-662: Update TUIndex::from to validate decoded
path_file_indices keys against the graph path table bounds before returning the
index, rejecting or removing rows with out-of-range IDs so Indexer::merge cannot
perform unchecked access. Add a hostile-input test covering an invalid decoded
path ID and verify deserialization fails or safely excludes the invalid row.

---

Nitpick comments:
In `@src/index/preamble_state.cpp`:
- Around line 48-59: Update file_of to validate the entry path_id against
paths.size() before indexing, and return an optional PreambleState::File that is
empty for invalid IDs. Adjust its caller(s), including the path around the
existing Line 155 check, to consume the optional and preserve current behavior
without duplicating the bound check.

In `@tests/unit/index/preamble_state_tests.cpp`:
- Around line 244-258: Extend AcceptCurrentVersionBlob to exercise every
accessor on the minimally encoded PreambleState, including paths, files,
preamble_content(), symbols, links, inactive_regions, open_conditionals, and
both lookup_preamble overloads. Assert that each absent field or lookup result
returns its documented empty/null result without crashing, using the existing
state returned by PreambleState::load.
- Around line 231-233: Move the duplicated VersionOnly struct declarations out
of the individual test cases and define one shared VersionOnly type at the test
suite scope. Update both test cases to reuse this shared declaration without
changing their behavior.

Apply the same fix in `@tests/unit/index/merged_index_tests.cpp` around lines 681
- 718: The same duplicated fixture and remediation are present in this test
file.
🪄 Autofix

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 Plus

Run ID: af5e08bc-df44-4197-b4e3-f5ce6f9ee517

📥 Commits

Reviewing files that changed from the base of the PR and between 7500095 and 771b7f8.

⛔ Files ignored due to path filters (2)
  • package-lock.json is excluded by !**/package-lock.json
  • pixi.lock is excluded by !**/*.lock
📒 Files selected for processing (24)
  • CMakeLists.txt
  • cmake/package.cmake
  • pixi.toml
  • src/index/include_graph.h
  • src/index/merged_index.cpp
  • src/index/preamble_state.cpp
  • src/index/preamble_state.h
  • src/index/project_index.cpp
  • src/index/project_index.h
  • src/index/schema.fbs
  • src/index/serialization.h
  • src/index/tu_index.cpp
  • src/index/tu_index.h
  • src/server/compiler/compiler.cpp
  • src/server/compiler/indexer.cpp
  • src/server/service/query.cpp
  • src/server/state/workspace.h
  • tests/unit/index/merged_index_tests.cpp
  • tests/unit/index/persisted_index_tests.cpp
  • tests/unit/index/preamble_state_tests.cpp
  • tests/unit/index/project_index_tests.cpp
  • tests/unit/index/tu_index_tests.cpp
  • tests/unit/server/indexer_tests.cpp
  • tools/client/workspace.ts
💤 Files with no reviewable changes (2)
  • src/index/schema.fbs
  • pixi.toml

Comment thread src/index/merged_index.cpp Outdated
Comment thread src/index/serialization.h
Comment thread src/index/tu_index.cpp
TUIndex now stamps and gates index_format_version: the blob is IPC-only,
but a worker respawned after an on-disk binary upgrade can run one build
ahead of the server, and a layout change need not be structurally
detectable. from() also rejects blobs whose location path ids, file
index keys or symbol bitmap values fall outside the blob's own path
table, and MergedIndex::load_in_memory range-validates every decoded
canonical id (cache entries, header includes, compilation contexts)
instead of assert-only decode checking — the verifier checks structure,
not cross-field invariants, and consumers index ref-count vectors with
these values unchecked. Binary-searching occurrences on range.end is
documented sound: name-token spans are pairwise disjoint or identical.

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/index/merged_index.cpp (2)

381-393: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Bound max_canonical_id before resizing canonical_ref_counts.

A structurally valid blob can set max_canonical_id to UINT32_MAX while leaving canonical_cache, header contexts, and compilation contexts empty. It passes usable(), then Line 393 attempts to allocate about 16 GiB for std::uint32_t entries. An uncaught allocation failure can terminate the server when it loads a corrupt shard.

Reject values above an explicit resource limit before resize, or compact and remap canonical IDs during serialization so the persisted maximum remains bounded.

🤖 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/index/merged_index.cpp` around lines 381 - 393, Validate
index.max_canonical_id against an explicit resource limit immediately after
assigning it from repr.max_canonical_id and before canonical_ref_counts.resize.
Reject or otherwise fail safely when the bound is exceeded, while preserving
normal loading for valid blobs and avoiding the unbounded allocation path.

301-304: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Validate MergedIndex(llvm::StringRef) before storing the buffer.

The public constructor stores arbitrary bytes, but buffer-backed queries use ShardView::from_verified_bytes. Validate the bytes with ShardView::from_bytes and require index_format_version, or make the constructor private.

🤖 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/index/merged_index.cpp` around lines 301 - 304, Update the public
MergedIndex(llvm::StringRef) constructor to validate the supplied bytes with
ShardView::from_bytes and require index_format_version before storing the
buffer, ensuring root_of uses only validated data; alternatively, make the
constructor private if callers must use an already-validated construction path.
🤖 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.

Outside diff comments:
In `@src/index/merged_index.cpp`:
- Around line 381-393: Validate index.max_canonical_id against an explicit
resource limit immediately after assigning it from repr.max_canonical_id and
before canonical_ref_counts.resize. Reject or otherwise fail safely when the
bound is exceeded, while preserving normal loading for valid blobs and avoiding
the unbounded allocation path.
- Around line 301-304: Update the public MergedIndex(llvm::StringRef)
constructor to validate the supplied bytes with ShardView::from_bytes and
require index_format_version before storing the buffer, ensuring root_of uses
only validated data; alternatively, make the constructor private if callers must
use an already-validated construction path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: b37a9a61-b3be-4682-9bf2-d62b85038c66

📥 Commits

Reviewing files that changed from the base of the PR and between 771b7f8 and c583b4b.

📒 Files selected for processing (6)
  • src/index/merged_index.cpp
  • src/index/serialization.h
  • src/index/tu_index.cpp
  • src/index/tu_index.h
  • tests/unit/index/merged_index_tests.cpp
  • tests/unit/index/tu_index_tests.cpp
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/index/tu_index.cpp
  • tests/unit/index/merged_index_tests.cpp

The fbs blob schemas are now the native structs themselves instead of
field-by-field mirror copies:

- MergedIndex::Impl doubles as the shard schema: runtime-only fields are
  skip-annotated, serialize() compacts masked rows in place and reflects
  the impl straight onto the wire, load decodes into it directly.
- PreambleState::serialize takes the TUIndex by value and assembles the
  blob by moving its rows; file contents and feature arrays are encoded
  as StringRef/ArrayRef borrows. Symbols persist as the full SymbolTable.
- ProjectIndex persists its symbol bitmaps with raw pool ids plus an
  id-to-path table (still garbage-collected), remapped at load; the
  per-symbol bitmap rebuild on the encode side is gone.
- PathPool repr drives the visitor imperatively (zero-copy encode,
  interning decode); new StringMap repr for the canonical cache.

index_format_version 2 -> 3, preamble_format_version 4 -> 5.

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/index/serialization.h (1)

39-41: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Use roaring_bitmap_deserialize_safe for the non-portable image.

Bitmap::read performs unbounded reads, and FBS verification does not validate the embedded bitmap. Call roaring_bitmap_deserialize_safe with buffer.size(), check for nullptr, and reject invalid images before constructing the index. Do not use C++ readSafe directly because it only supports the portable format. Add a corrupt-bitmap FBS fixture.

🤖 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/index/serialization.h` around lines 39 - 41, Update Bitmap::from to
validate the non-portable serialized image with roaring_bitmap_deserialize_safe,
passing buffer.data() and buffer.size(), and reject a nullptr result before
constructing the clice::Bitmap. Keep the existing non-portable format handling
and avoid the C++ readSafe API; add a corrupt-bitmap FBS fixture covering the
rejection path.
🤖 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/index/project_index.cpp`:
- Around line 69-73: Validate each entry in the project-index path remapping
loop before calling pool.intern, rejecting an empty path by returning nullopt
through the existing cache-loading failure path. Update the project-index
corruption tests to cover a codec-valid paths table containing an empty entry
and assert loading fails cleanly.

---

Outside diff comments:
In `@src/index/serialization.h`:
- Around line 39-41: Update Bitmap::from to validate the non-portable serialized
image with roaring_bitmap_deserialize_safe, passing buffer.data() and
buffer.size(), and reject a nullptr result before constructing the
clice::Bitmap. Keep the existing non-portable format handling and avoid the C++
readSafe API; add a corrupt-bitmap FBS fixture covering the rejection path.
🪄 Autofix

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 Plus

Run ID: 90723869-7556-481e-a681-4f9669ae07ef

📥 Commits

Reviewing files that changed from the base of the PR and between c583b4b and 0c6f281.

📒 Files selected for processing (11)
  • src/index/merged_index.cpp
  • src/index/merged_index.h
  • src/index/preamble_state.cpp
  • src/index/preamble_state.h
  • src/index/project_index.cpp
  • src/index/project_index.h
  • src/index/serialization.h
  • src/server/worker/stateless_worker.cpp
  • tests/unit/index/merged_index_tests.cpp
  • tests/unit/index/persisted_index_tests.cpp
  • tests/unit/index/preamble_state_tests.cpp
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/index/preamble_state.cpp
  • tests/unit/index/persisted_index_tests.cpp
  • src/index/merged_index.cpp
  • tests/unit/index/merged_index_tests.cpp

Comment thread src/index/project_index.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: 0c6f2814a7

ℹ️ 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/index/preamble_state.cpp
Comment thread src/index/preamble_state.cpp Outdated
Review round: the preamble blob stores a borrowed {name, kind} entry per
symbol instead of reflecting the full Symbol (scope and reference bitmaps
were dead weight on the wire), and ProjectIndex::from rejects a corrupt
blob whose path table carries an empty entry instead of interning it.
@16bit-ykiko
16bit-ykiko merged commit 944eb3f into main Aug 13, 2026
34 checks passed
@16bit-ykiko
16bit-ykiko deleted the feat/kota-fbs-index branch August 27, 2026 06:32
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.

bug(index): preserve large valid blobs during cache verification

1 participant