Skip to content

feat: split runtime and migration stores#589

Merged
EtanHey merged 1 commit into
mainfrom
feat/writerd-phase1-store-split
Jul 10, 2026
Merged

feat: split runtime and migration stores#589
EtanHey merged 1 commit into
mainfrom
feat/writerd-phase1-store-split

Conversation

@EtanHey

@EtanHey EtanHey commented Jul 10, 2026

Copy link
Copy Markdown
Owner

Summary

  • split database access into strict ReadonlyStore, fast WriterRuntimeStore, and copy-only OfflineMigrator
  • make runtime open validate a semantic schema fingerprint without migrations, FTS reconciliation, trigger recreation, repair, counts, or corpus scans
  • reuse one writer connection across the index loop and expose BRAINLAYER_RUNTIME_STORE=legacy as the rollback switch
  • add explicit copy-target migration/repair commands and refuse the configured canonical database unless the gated-swap environment is set
  • instrument runtime_open with Phase 0 telemetry and schema-fingerprint metadata

Incident / urgency

Incident brainbar-c07ade22-ecc wedged writer startup twice, caused about 1h15 of outage, and reduced ingest from roughly 1,500/min to zero. This change removes the startup scan/mutation path by construction.

Gates

  • 17.17 GB read-only-source copy: runtime-open p99 7.758 ms, p95 7.562 ms, max 26.026 ms over 100 opens
  • legacy control: first sample exceeded the 30,000 ms killable timeout
  • zero corpus-scan/mutation statements during runtime-open benchmark
  • 100/100 completed runtime_open telemetry events, fingerprint 5071698596284721e85760ef30234ce836d79ab377f2d70d627b28e3213b0f9a
  • focused Phase 1/watchdog/transaction gates: 120 passed
  • full safe suite: 3,527 passed, 9 skipped, 44 deselected, 1 xfailed
  • pre-push fallback: 3,472 passed, 9 skipped, 61 deselected, 1 xfailed; MCP registration 3 passed; isolated eval/routing 40 passed; Bun and FTS determinism regressions passed
  • ruff check src/ tests/, ruff format --check src/ tests/, and diff checks passed

Safety / rollback

  • runtime default: new store split
  • immediate rollback: BRAINLAYER_RUNTIME_STORE=legacy
  • offline migration/repair requires an explicit copy target; canonical writes require BRAINLAYER_OFFLINE_MIGRATOR_GATED_SWAP=1
  • during an early RED guard test, the pre-fix legacy repair command began opening the live canonical database before the process was terminated. The six expected FTS triggers and semantic schema contract were subsequently verified read-only; no full live-corpus integrity scan was run. The new guard prevents that path.

Review

Local CodeRabbit review findings were addressed (telemetry completeness, configured DB path, strict semantic schema validation, stale read-only rejection, failure-path close). Re-review was rate-limited, so the documented red/blue fallback audit was completed; it additionally found and fixed telemetry-path clobbering, symlink resolution/TOCTOU, and the legacy migrate canonical-default path.


Note

High Risk
Changes core writer/reader open paths, indexing, MCP store behavior, and migration/repair CLI; misconfiguration or fingerprint drift could block writes or defer MCP stores until offline migration.

Overview
Phase 1 writer store split removes open-time migrations and corpus work from normal indexing and MCP writes, targeting incident brainbar-c07ade22-ecc (writer startup wedging on large DBs).

New runtime_store adds ReadonlyStore, WriterRuntimeStore, and OfflineMigrator, plus open_writer_store() (default runtime; BRAINLAYER_RUNTIME_STORE=legacy rollback). Runtime opens validate a deterministic schema fingerprint from cheap metadata reads only—no DDL/DML, FTS reconciliation, or chunk scans—and emit runtime_open telemetry with fingerprint metadata. SchemaFingerprintMismatch fails closed; MCP treats it like a lock and DEFERRED-queues writes.

CLI / paths: migrate, repair-fts, and new migrate-store require an explicit offline DB copy; canonical is refused unless BRAINLAYER_OFFLINE_MIGRATOR_GATED_SWAP=1. Read-only CLI/search paths no longer bootstrap missing schema—they error with migrate-store instructions. Fast index opens one shared writer for the whole file loop via injected store on index_chunks_to_sqlite.

Supporting changes: APSW best-practice hooks skip WAL/optimize during runtime connect; writer telemetry gains sample_fts / add_metadata. scripts/benchmark_runtime_store_open.py benchmarks copy-only opens and checks for forbidden SQL.

Extensive tests cover fail-closed behavior, single index connection reuse, and benchmark safety.

Reviewed by Cursor Bugbot for commit 6dfef1f. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Split runtime and migration store paths to enforce schema validation before reads and writes

  • Introduces WriterRuntimeStore and ReadonlyStore in runtime_store.py that validate a SHA-256 schema fingerprint on open and fail closed if the DB is missing, stale, or mismatched — replacing implicit bootstrap via VectorStore.
  • Adds OfflineMigrator (a VectorStore subclass) that is the only class permitted to create or migrate schemas, restricted to non-canonical DB paths unless BRAINLAYER_OFFLINE_MIGRATOR_GATED_SWAP=1 is set.
  • Adds open_writer_store factory that selects WriterRuntimeStore by default or falls back to legacy VectorStore via BRAINLAYER_RUNTIME_STORE=legacy.
  • Updates CLI commands (index-fast, migrate, migrate-store, repair-fts) and MCP store handler to use the new store types; index-fast now reuses a single writer store connection across all indexed files.
  • Adds a benchmarking script at scripts/benchmark_runtime_store_open.py that measures writer-open latency and emits JSON with p50/p95/p99 timing, schema fingerprints, and statement-violation checks.
  • Risk: stats, search, and MCP search paths now raise SchemaFingerprintMismatch instead of auto-creating a DB when the configured DB is absent or has a stale schema.
📊 Macroscope summarized 6dfef1f. 11 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@EtanHey, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 2 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: c05f7f92-8d67-44b3-bcdd-4020789315fa

📥 Commits

Reviewing files that changed from the base of the PR and between 9d8a2b5 and 6dfef1f.

📒 Files selected for processing (18)
  • docs/plans/2026-07-10-writerd-phase1-store-split.md
  • scripts/benchmark_runtime_store_open.py
  • src/brainlayer/cli/__init__.py
  • src/brainlayer/cli_new.py
  • src/brainlayer/index_new.py
  • src/brainlayer/mcp/_shared.py
  • src/brainlayer/mcp/store_handler.py
  • src/brainlayer/migrate.py
  • src/brainlayer/runtime_store.py
  • src/brainlayer/vector_store.py
  • src/brainlayer/writer_telemetry.py
  • tests/test_cli_direct_sqlite.py
  • tests/test_cli_index_watchdog.py
  • tests/test_context_pipeline.py
  • tests/test_runtime_store.py
  • tests/test_runtime_store_benchmark.py
  • tests/test_store_handler.py
  • tests/test_vector_store_readonly.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/writerd-phase1-store-split

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.

@cursor

cursor Bot commented Jul 10, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_85f2e986-c7e2-40e1-b0fb-d22fc1be61a9)

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

Your trial has ended. Reactivate Greptile to resume code reviews.

@EtanHey

EtanHey commented Jul 10, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@cursor review

@BugBot review

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

@EtanHey I'll review the changes now.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

"idx_chunks_simhash_band_0": "index",
"idx_chunks_simhash_band_1": "index",
"idx_chunks_simhash_band_2": "index",
"idx_chunks_simhash_band_3": "index",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium brainlayer/runtime_store.py:62

_REQUIRED_SCHEMA_OBJECTS omits kg_entities_fts and its sync triggers, so _validate_runtime_schema accepts a database where the KG FTS table was dropped or corrupted. Later KG-backed search paths in search_repo.py and mcp/search_handler.py execute kg_entities_fts queries directly and throw SQLite no such table errors at runtime. Add kg_entities_fts and its sync triggers to _REQUIRED_SCHEMA_OBJECTS (and corresponding hashes to _REQUIRED_SQL_HASHES) so the schema contract rejects databases missing KG FTS.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @src/brainlayer/runtime_store.py around line 62:

`_REQUIRED_SCHEMA_OBJECTS` omits `kg_entities_fts` and its sync triggers, so `_validate_runtime_schema` accepts a database where the KG FTS table was dropped or corrupted. Later KG-backed search paths in `search_repo.py` and `mcp/search_handler.py` execute `kg_entities_fts` queries directly and throw SQLite `no such table` errors at runtime. Add `kg_entities_fts` and its sync triggers to `_REQUIRED_SCHEMA_OBJECTS` (and corresponding hashes to `_REQUIRED_SQL_HASHES`) so the schema contract rejects databases missing KG FTS.

@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: 6dfef1f9e5

ℹ️ 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".

with _without_connection_maintenance_hooks():
self.conn = apsw.Connection(str(self.db_path), flags=apsw.SQLITE_OPEN_READWRITE)
try:
self.conn.setbusytimeout(_write_busy_timeout_ms())

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Restore the default busy timeout after runtime open

When the MCP cold path opens the writer inside temporary_write_busy_timeout_ms (see _store around store_handler.py lines 862-864), _write_busy_timeout_ms() returns only the current store budget (default 400 ms). This line persists that short value on the cached WriterRuntimeStore connection, and unlike the legacy VectorStore init path there is no reset to the default timeout after the schema probe. If the first writer open is a brain_store call, later tools that reuse the singleton writer (for example brain_digest, brain_tags, or stats/maintenance paths) inherit the 400 ms timeout and can fail or defer under normal write contention instead of waiting the intended default.

Useful? React with 👍 / 👎.

@EtanHey EtanHey merged commit 826e1cd into main Jul 10, 2026
8 of 9 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.

1 participant