msnav 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3ea0f1f4fee68dd182f4e86da45adb998043e96a62d79effa4a05094ab8fdbed
4
- data.tar.gz: 75cd16332b3edc2b185adcbd155dcc4fc88e3904a5887065d3d910edaaccea64
3
+ metadata.gz: c6d6d26b0049b7aa5df6c60df61e989ebd2402db9912cfb97ab30f176b6eacd6
4
+ data.tar.gz: 84ba3e939ece29c631b8fd17649888a6f4ef6a7ee1fa4be29d971a497682e40d
5
5
  SHA512:
6
- metadata.gz: 87694f9c0587ea8bea74c54027101d26f90c7c42e06790e853388a8608562a1dfadee1736d15fb272aa0f8f86fead6e45afc6684825d47fca1912b3f5c255c9d
7
- data.tar.gz: 9f1c22c8076ccf207e700b9378ebeb97ee2f4d6c1e6fccde3c6d2f37b23f670edf8aa3164a8c48d038a8f7f5c0629e7d37f0c5cea097956712416cbfb1ad13dd
6
+ metadata.gz: 594621248be0da1c37217e7286b08480dd7f9f76d48f2ad767fa1641c428867a9fa5ea4344f2b57dc0f1cae2f130fc9814563204645b0ed702d7923b5abc27ff
7
+ data.tar.gz: a71963f36a2aece2d7e7f4322f2a207c1e1a1efea2f1d4e366ec0d19b37a72e4739a1eaee592896812102b455e96d0f4826caca4584c9f7539a5c4ac864eb7f7
data/README.md CHANGED
@@ -110,13 +110,19 @@ nothing (msnav never indexes).
110
110
 
111
111
  ## Compatibility
112
112
 
113
- - Reads coderag **schema v4** DBs (errors on older; warns-and-continues on
114
- newer). Writes only the two v4 message-bus tables (`windows`,
115
- `window_commands`) every daemon on the hub shares.
113
+ - Opens `coderag.db` **strictly read-only**. Compatibility is checked via
114
+ the index's `read_contract` meta key (accepts 1.x) when present; older
115
+ indexes are accepted from schema v4 up.
116
+ - The editor-window registry and open-command queue are **msnav-owned**
117
+ (coderag dropped this responsibility entirely): a sidecar
118
+ `msnav-windows.db` next to the index in the hub's storage dir, shared
119
+ through the same bind mount, so cross-container open-routing between msnav
120
+ daemons keeps working. msnav versions it itself (`PRAGMA user_version`).
121
+ It is ephemeral liveness state; deleting it is safe.
116
122
  - Ruby ≥ 2.6; gems: `sqlite3`, `webrick`.
117
123
  - The Docker-Desktop caveat from coderag applies here too: SQLite (WAL) over
118
124
  bind mounts with several concurrent writers is the sensitive spot; msnav
119
- keeps its writes tiny (window registry only).
125
+ writes only to its small sidecar, never to the index.
120
126
 
121
127
  ## Troubleshooting
122
128
 
data/lib/msnav/server.rb CHANGED
@@ -11,7 +11,8 @@ module Msnav
11
11
  # host-side `coderag index` or a peer daemon's rebuild hot-reloads every
12
12
  # msnav on the hub within a second, requests always see a consistent index.
13
13
  class Holder
14
- attr_reader :cfg, :store, :windows, :service_root, :service_name
14
+ attr_reader :cfg, :store, :windows, :windows_store, :service_root,
15
+ :service_name
15
16
 
16
17
  def initialize(cfg, service_root: nil, service_name: nil)
17
18
  @cfg = cfg
@@ -21,7 +22,12 @@ module Msnav
21
22
  @service_root = service_root
22
23
  @service_name = service_name
23
24
  @store = Store.new(cfg.db_path)
24
- @windows = WindowRegistry.new(@store)
25
+ # coderag no longer carries window state at all — the registry is
26
+ # msnav-owned, in a sidecar DB inside the same shared storage dir, so
27
+ # every msnav daemon on the hub (one per container) routes opens
28
+ # through the mount exactly like before
29
+ @windows_store = WindowsStore.new(cfg.storage_dir + "msnav-windows.db")
30
+ @windows = WindowRegistry.new(@windows_store)
25
31
  @mutex = Mutex.new
26
32
  @generation = 0
27
33
  @db_generation = @store.index_generation
data/lib/msnav/store.rb CHANGED
@@ -4,17 +4,18 @@ require "pathname"
4
4
  require "sqlite3"
5
5
 
6
6
  module Msnav
7
- # Access to the shared coderag SQLite index.
7
+ # Read-only access to the shared coderag SQLite index.
8
8
  #
9
- # coderag (Python) owns the schema and the writes that matter (graph, facts,
10
- # chunks); msnav writes only the two v4 message-bus tables (`windows`,
11
- # `window_commands`) that every daemon on the hub shares. One connection
12
- # guarded by a Mutex: navigation itself runs on the in-memory graph, so the
13
- # DB sees only window-registry traffic, generation polls, and graph reloads
14
- # serializing those is simpler than per-thread connections and equally
15
- # correct across WEBrick's request threads.
9
+ # coderag (Python) owns the index; since its schema v5 the DB is a pure
10
+ # reader contract (meta key `read_contract`, currently 1.x) and holds no
11
+ # mutable daemon state msnav therefore opens it strictly read-only and
12
+ # never writes to it. The window registry lives elsewhere (WindowsStore).
13
+ #
14
+ # One connection guarded by a Mutex: navigation runs on the in-memory
15
+ # graph, so the DB sees only generation polls and graph reloads.
16
16
  class Store
17
- SCHEMA_VERSION = 4 # the version msnav is written against
17
+ SCHEMA_VERSION = 4 # oldest index schema msnav understands
18
+ READ_CONTRACT_MAJOR = 1 # newest reader contract msnav is written against
18
19
 
19
20
  attr_reader :path
20
21
 
@@ -62,36 +63,104 @@ module Msnav
62
63
  end
63
64
  end
64
65
 
66
+ def has_table?(name)
67
+ with_conn do |db|
68
+ !db.get_first_row(
69
+ "SELECT 1 FROM sqlite_master WHERE type='table' AND name = ?",
70
+ [name]).nil?
71
+ end
72
+ end
73
+
65
74
  private
66
75
 
67
76
  def connect
68
- db = SQLite3::Database.new(@path.to_s)
77
+ db = SQLite3::Database.new(@path.to_s, readonly: true)
69
78
  db.busy_timeout = 5000
70
- db.execute("PRAGMA foreign_keys=ON")
71
79
  db
72
80
  rescue SQLite3::Exception => e
73
81
  raise IndexStaleError, "#{@path} could not be opened (#{e.message})"
74
82
  end
75
83
 
84
+ # Compatibility gate. Newer coderag publishes `read_contract` (the
85
+ # reader-facing surface version) — that is the authoritative check.
86
+ # Older indexes are accepted from schema v4 up.
76
87
  def check_version
77
- row = begin
78
- @conn.get_first_row("SELECT value FROM meta WHERE key='schema_version'")
88
+ contract = begin
89
+ get_meta("read_contract")
79
90
  rescue SQLite3::Exception => e
80
91
  raise IndexStaleError,
81
92
  "#{@path} is not a coderag database (#{e.message}) — " \
82
93
  "delete it and run `coderag index` on the host"
83
94
  end
84
- version = row ? row[0].to_i : 0
95
+ if contract
96
+ major = contract.split(".").first.to_i
97
+ return if major == READ_CONTRACT_MAJOR
98
+ raise IndexStaleError,
99
+ "#{@path} publishes read contract #{contract}, msnav is written " \
100
+ "against #{READ_CONTRACT_MAJOR}.x — upgrade msnav"
101
+ end
102
+ version = (get_meta("schema_version") || 0).to_i
85
103
  if version < SCHEMA_VERSION
86
104
  raise IndexStaleError,
87
- "#{@path} is schema v#{version}, msnav needs v#{SCHEMA_VERSION} — " \
105
+ "#{@path} is schema v#{version}, msnav needs v#{SCHEMA_VERSION}+ — " \
88
106
  "run `coderag index` on the host to migrate"
89
107
  end
90
108
  return if version == SCHEMA_VERSION
91
- # a newer coderag may have added tables msnav doesn't know; the v4
92
- # tables it reads are append-only by convention, so proceed loudly
93
- warn "msnav: #{@path} is schema v#{version} (msnav is written against " \
94
- "v#{SCHEMA_VERSION}) — continuing, but upgrade msnav if navigation misbehaves"
109
+ warn "msnav: #{@path} is schema v#{version} without a read_contract " \
110
+ "(msnav is written against v#{SCHEMA_VERSION}) continuing, " \
111
+ "but upgrade msnav if navigation misbehaves"
112
+ end
113
+ end
114
+
115
+ # Writable sidecar for msnav's own mutable state: the editor-window
116
+ # registry and open-command queue. coderag dropped this responsibility
117
+ # entirely (its index is a read-only contract now), so msnav owns the
118
+ # storage — and versions it itself via PRAGMA user_version. Lives next to
119
+ # the index in the hub's storage dir, shared through the same bind mount,
120
+ # so every msnav daemon on the hub sees the same windows. Created on
121
+ # demand; safe to delete (ephemeral liveness state).
122
+ class WindowsStore
123
+ USER_VERSION = 1
124
+
125
+ DDL = [
126
+ "CREATE TABLE IF NOT EXISTS windows (" \
127
+ "window_id TEXT PRIMARY KEY, authority TEXT NOT NULL DEFAULT ''," \
128
+ "roots TEXT NOT NULL, path_mappings TEXT NOT NULL," \
129
+ "host_roots TEXT NOT NULL, seq INTEGER NOT NULL, last_poll REAL NOT NULL)",
130
+ "CREATE TABLE IF NOT EXISTS window_commands (" \
131
+ "id INTEGER PRIMARY KEY AUTOINCREMENT, window_id TEXT NOT NULL," \
132
+ "payload TEXT NOT NULL, created_at REAL NOT NULL)",
133
+ "CREATE INDEX IF NOT EXISTS idx_window_commands_window " \
134
+ "ON window_commands(window_id)",
135
+ ].freeze
136
+
137
+ attr_reader :path
138
+
139
+ def initialize(path)
140
+ @path = Pathname.new(path.to_s)
141
+ @path.parent.mkpath
142
+ @mutex = Mutex.new
143
+ @conn = SQLite3::Database.new(@path.to_s)
144
+ @conn.busy_timeout = 5000
145
+ @conn.execute("PRAGMA journal_mode=WAL")
146
+ version = @conn.get_first_value("PRAGMA user_version").to_i
147
+ if version > USER_VERSION
148
+ raise IndexStaleError,
149
+ "#{@path} was created by a newer msnav (registry v#{version}, " \
150
+ "this msnav understands v#{USER_VERSION}) — upgrade msnav"
151
+ end
152
+ DDL.each { |stmt| @conn.execute(stmt) }
153
+ @conn.execute("PRAGMA user_version = #{USER_VERSION}") if version.zero?
154
+ end
155
+
156
+ def with_conn
157
+ @mutex.synchronize { yield @conn }
158
+ end
159
+
160
+ def close
161
+ @mutex.synchronize do
162
+ @conn.close unless @conn.closed?
163
+ end
95
164
  end
96
165
  end
97
166
  end
data/lib/msnav/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Msnav
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: msnav
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yaroslav Zahoruiko
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-07-05 00:00:00.000000000 Z
11
+ date: 2026-07-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sqlite3