concierge-graph 3.8.2__py3-none-any.whl
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.
- agents/__init__.py +14 -0
- agents/revisor_critico.py +610 -0
- concierge_graph-3.8.2.dist-info/METADATA +327 -0
- concierge_graph-3.8.2.dist-info/RECORD +37 -0
- concierge_graph-3.8.2.dist-info/WHEEL +5 -0
- concierge_graph-3.8.2.dist-info/entry_points.txt +3 -0
- concierge_graph-3.8.2.dist-info/licenses/LICENSE +21 -0
- concierge_graph-3.8.2.dist-info/top_level.txt +8 -0
- core/__init__.py +23 -0
- core/config.py +192 -0
- core/hybrid_search.py +288 -0
- core/memory_extractor.py +245 -0
- core/middleware.py +723 -0
- core/probabilistic_retriever.py +99 -0
- core/project_index.py +316 -0
- core/vector_backend.py +471 -0
- ingestion/__init__.py +25 -0
- ingestion/crawler.py +722 -0
- ingestion/orchestrator.py +920 -0
- ingestion/parser.py +984 -0
- ingestion/summarizer.py +948 -0
- interface/__init__.py +16 -0
- interface/action_hooks.py +261 -0
- interface/cli.py +391 -0
- interface/mcp_server.py +1737 -0
- main.py +281 -0
- scripts/bootstrap_core_memory.py +93 -0
- services/__init__.py +13 -0
- services/janitor.py +762 -0
- storage/__init__.py +31 -0
- storage/base_backend.py +241 -0
- storage/connection.py +310 -0
- storage/logic.py +703 -0
- storage/schema.py +390 -0
- storage/semantic_logic.py +125 -0
- storage/store.py +802 -0
- storage/vector_store.py +759 -0
storage/schema.py
ADDED
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
"""
|
|
2
|
+
storage/schema.py - Grafo Concierge v3.8.0 (Absolute Solidity)
|
|
3
|
+
|
|
4
|
+
SQL Schema Definitions, CHECK constraints and FTS5 Triggers.
|
|
5
|
+
|
|
6
|
+
Responsibilities:
|
|
7
|
+
- SQL constants for creation of the 6 tables of Schema v3.8:
|
|
8
|
+
projects, nodes, edges, reference_wings, trajectories, commit_log.
|
|
9
|
+
- CHECK constraints inline for validation at database level:
|
|
10
|
+
node_type IN ('FACT','SKILL','INSIGHT','TRAJECTORY','PATCH')
|
|
11
|
+
privacy_level IN ('PUBLIC','INTERNAL','RESTRICTED')
|
|
12
|
+
status IN ('ACTIVE','STALE','ARCHIVED')
|
|
13
|
+
- Performance indexes (10 indexes).
|
|
14
|
+
- FTS5 virtual table (nodes_fts) with content sync.
|
|
15
|
+
- FTS5 synchronization triggers (INSERT, DELETE, UPDATE).
|
|
16
|
+
- SchemaManager: class that applies the schema idempotently
|
|
17
|
+
and offers verification and migration utilities.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import logging
|
|
23
|
+
import sqlite3
|
|
24
|
+
from typing import Optional
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger("grafo-concierge.schema")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# ---------------------------------------------------------------------------
|
|
30
|
+
# Validation enums (mirror of SQL CHECK constraints)
|
|
31
|
+
# ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
VALID_NODE_TYPES: frozenset[str] = frozenset({
|
|
34
|
+
"FACT", "SKILL", "INSIGHT", "TRAJECTORY", "PATCH",
|
|
35
|
+
"CLASS", "FUNCTION", "METHOD", "MODULE"
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
VALID_PRIVACY_LEVELS: frozenset[str] = frozenset({
|
|
39
|
+
"PUBLIC", "INTERNAL", "RESTRICTED"
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
VALID_STATUSES: frozenset[str] = frozenset({
|
|
43
|
+
"ACTIVE", "STALE", "ARCHIVED"
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# ---------------------------------------------------------------------------
|
|
48
|
+
# SQL Constants — Tables v3.8.0 with CHECK constraints
|
|
49
|
+
# ---------------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
TABLES_SQL: str = """
|
|
52
|
+
CREATE TABLE IF NOT EXISTS projects (
|
|
53
|
+
uuid TEXT PRIMARY KEY,
|
|
54
|
+
folder_name TEXT NOT NULL,
|
|
55
|
+
primary_wing TEXT NOT NULL DEFAULT 'geral',
|
|
56
|
+
privacy_level TEXT NOT NULL DEFAULT 'PUBLIC'
|
|
57
|
+
CHECK(privacy_level IN ('PUBLIC', 'INTERNAL', 'RESTRICTED')),
|
|
58
|
+
summary TEXT,
|
|
59
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
60
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
CREATE TABLE IF NOT EXISTS nodes (
|
|
64
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
65
|
+
project_uuid TEXT NOT NULL REFERENCES projects(uuid) ON DELETE CASCADE,
|
|
66
|
+
label TEXT NOT NULL,
|
|
67
|
+
summary TEXT,
|
|
68
|
+
content TEXT,
|
|
69
|
+
node_type TEXT NOT NULL DEFAULT 'FACT'
|
|
70
|
+
CHECK(node_type IN ('FACT', 'SKILL', 'INSIGHT', 'TRAJECTORY', 'PATCH', 'CLASS', 'FUNCTION', 'METHOD', 'MODULE')),
|
|
71
|
+
type TEXT NOT NULL DEFAULT 'file',
|
|
72
|
+
tags TEXT,
|
|
73
|
+
file_hash TEXT,
|
|
74
|
+
last_accessed TEXT,
|
|
75
|
+
last_commit_at TEXT,
|
|
76
|
+
status TEXT NOT NULL DEFAULT 'ACTIVE'
|
|
77
|
+
CHECK(status IN ('ACTIVE', 'STALE', 'ARCHIVED')),
|
|
78
|
+
valid_from_commit TEXT NULL,
|
|
79
|
+
valid_to_commit TEXT NULL
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
CREATE TABLE IF NOT EXISTS edges (
|
|
83
|
+
source_id INTEGER NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
|
|
84
|
+
target_id INTEGER NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
|
|
85
|
+
relation_type TEXT NOT NULL DEFAULT 'depends_on',
|
|
86
|
+
weight REAL NOT NULL DEFAULT 1.0,
|
|
87
|
+
valid_from_commit TEXT NULL,
|
|
88
|
+
valid_to_commit TEXT NULL,
|
|
89
|
+
confidence_tag TEXT NOT NULL DEFAULT 'EXTRACTED'
|
|
90
|
+
CHECK(confidence_tag IN ('EXTRACTED', 'INFERRED', 'AMBIGUOUS')),
|
|
91
|
+
PRIMARY KEY (source_id, target_id)
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
CREATE TABLE IF NOT EXISTS reference_wings (
|
|
95
|
+
project_uuid TEXT NOT NULL REFERENCES projects(uuid) ON DELETE CASCADE,
|
|
96
|
+
wing_name TEXT NOT NULL,
|
|
97
|
+
PRIMARY KEY (project_uuid, wing_name)
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
CREATE TABLE IF NOT EXISTS trajectories (
|
|
101
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
102
|
+
project_uuid TEXT NOT NULL REFERENCES projects(uuid) ON DELETE CASCADE,
|
|
103
|
+
prompt_origem TEXT NOT NULL,
|
|
104
|
+
tentativa_execucao TEXT NOT NULL,
|
|
105
|
+
erro_encontrado TEXT,
|
|
106
|
+
solucao_aplicada TEXT,
|
|
107
|
+
status TEXT NOT NULL DEFAULT 'ACTIVE'
|
|
108
|
+
CHECK(status IN ('ACTIVE', 'STALE', 'ARCHIVED')),
|
|
109
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
CREATE TABLE IF NOT EXISTS commit_log (
|
|
113
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
114
|
+
project_uuid TEXT NOT NULL REFERENCES projects(uuid) ON DELETE CASCADE,
|
|
115
|
+
phase TEXT NOT NULL,
|
|
116
|
+
technical_changes TEXT NOT NULL,
|
|
117
|
+
updated_pointers TEXT NOT NULL,
|
|
118
|
+
revisor_approved INTEGER NOT NULL DEFAULT 0,
|
|
119
|
+
partial_audit INTEGER NOT NULL DEFAULT 0,
|
|
120
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
CREATE TABLE IF NOT EXISTS user_core_memory (
|
|
124
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
125
|
+
scope_type TEXT NOT NULL CHECK(scope_type IN ('user', 'session', 'agent', 'org')),
|
|
126
|
+
scope_id TEXT NOT NULL,
|
|
127
|
+
block_label TEXT NOT NULL,
|
|
128
|
+
content TEXT,
|
|
129
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
130
|
+
UNIQUE(scope_type, scope_id, block_label)
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
CREATE TABLE IF NOT EXISTS semantic_facts (
|
|
134
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
135
|
+
scope_type TEXT NOT NULL CHECK(scope_type IN ('user', 'session', 'agent', 'org')),
|
|
136
|
+
scope_id TEXT NOT NULL,
|
|
137
|
+
fact_statement TEXT NOT NULL,
|
|
138
|
+
t_valid TEXT NOT NULL DEFAULT (datetime('now')),
|
|
139
|
+
t_invalid TEXT NULL,
|
|
140
|
+
utility_alpha REAL NOT NULL DEFAULT 1.0,
|
|
141
|
+
utility_beta REAL NOT NULL DEFAULT 1.0,
|
|
142
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
143
|
+
);
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
INDEXES_SQL: str = """
|
|
147
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_project ON nodes(project_uuid);
|
|
148
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_type ON nodes(node_type);
|
|
149
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_status ON nodes(status);
|
|
150
|
+
CREATE INDEX IF NOT EXISTS idx_edges_source ON edges(source_id);
|
|
151
|
+
CREATE INDEX IF NOT EXISTS idx_edges_target ON edges(target_id);
|
|
152
|
+
CREATE INDEX IF NOT EXISTS idx_refwings_project ON reference_wings(project_uuid);
|
|
153
|
+
CREATE INDEX IF NOT EXISTS idx_trajectories_project ON trajectories(project_uuid);
|
|
154
|
+
CREATE INDEX IF NOT EXISTS idx_trajectories_status ON trajectories(status);
|
|
155
|
+
CREATE INDEX IF NOT EXISTS idx_commitlog_project ON commit_log(project_uuid);
|
|
156
|
+
CREATE INDEX IF NOT EXISTS idx_commitlog_date ON commit_log(created_at);
|
|
157
|
+
CREATE INDEX IF NOT EXISTS idx_user_core_memory_scope ON user_core_memory(scope_type, scope_id);
|
|
158
|
+
CREATE INDEX IF NOT EXISTS idx_semantic_facts_scope ON semantic_facts(scope_type, scope_id);
|
|
159
|
+
CREATE INDEX IF NOT EXISTS idx_semantic_facts_temporal ON semantic_facts(t_valid, t_invalid);
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
FTS5_TABLE_SQL: str = """
|
|
163
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS nodes_fts USING fts5(
|
|
164
|
+
label, tags, summary, content='nodes', content_rowid='id'
|
|
165
|
+
);
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
FTS5_TRIGGERS_SQL: str = """
|
|
169
|
+
CREATE TRIGGER IF NOT EXISTS nodes_ai AFTER INSERT ON nodes BEGIN
|
|
170
|
+
INSERT INTO nodes_fts(rowid, label, tags, summary)
|
|
171
|
+
VALUES (new.id, new.label, new.tags, new.summary);
|
|
172
|
+
END;
|
|
173
|
+
|
|
174
|
+
CREATE TRIGGER IF NOT EXISTS nodes_ad AFTER DELETE ON nodes BEGIN
|
|
175
|
+
INSERT INTO nodes_fts(nodes_fts, rowid, label, tags, summary)
|
|
176
|
+
VALUES('delete', old.id, old.label, old.tags, old.summary);
|
|
177
|
+
END;
|
|
178
|
+
|
|
179
|
+
CREATE TRIGGER IF NOT EXISTS nodes_au AFTER UPDATE ON nodes BEGIN
|
|
180
|
+
INSERT INTO nodes_fts(nodes_fts, rowid, label, tags, summary)
|
|
181
|
+
VALUES('delete', old.id, old.label, old.tags, old.summary);
|
|
182
|
+
INSERT INTO nodes_fts(rowid, label, tags, summary)
|
|
183
|
+
VALUES (new.id, new.label, new.tags, new.summary);
|
|
184
|
+
END;
|
|
185
|
+
"""
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
# ---------------------------------------------------------------------------
|
|
189
|
+
# SchemaManager — application and verification of schema
|
|
190
|
+
# ---------------------------------------------------------------------------
|
|
191
|
+
|
|
192
|
+
class SchemaManager:
|
|
193
|
+
"""Applies and verifies Schema v3.8 idempotently.
|
|
194
|
+
|
|
195
|
+
Responsibilities:
|
|
196
|
+
- Creation of tables (IF NOT EXISTS).
|
|
197
|
+
- Application of CHECK constraints at SQL level.
|
|
198
|
+
- Creation of indexes.
|
|
199
|
+
- Creation of the virtual table FTS5 and its triggers.
|
|
200
|
+
- Verification of the schema version.
|
|
201
|
+
- Rebuild of the FTS5 index for large loads.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
conn: Configured SQLite connection (WAL, busy_timeout, foreign_keys).
|
|
205
|
+
"""
|
|
206
|
+
|
|
207
|
+
# Semantic schema version for controlling future migrations.
|
|
208
|
+
SCHEMA_VERSION: str = "3.8.0"
|
|
209
|
+
|
|
210
|
+
def __init__(self, conn: sqlite3.Connection) -> None:
|
|
211
|
+
self._conn = conn
|
|
212
|
+
|
|
213
|
+
def apply_full_schema(self) -> None:
|
|
214
|
+
"""Applies the full schema (tables + indexes + FTS5 + triggers).
|
|
215
|
+
|
|
216
|
+
Idempotent operation — safe to call multiple times.
|
|
217
|
+
Executes within a single transaction.
|
|
218
|
+
|
|
219
|
+
Raises:
|
|
220
|
+
sqlite3.OperationalError: If there is an unrecoverable DDL error.
|
|
221
|
+
"""
|
|
222
|
+
logger.debug("Starting application of Schema v%s", self.SCHEMA_VERSION)
|
|
223
|
+
try:
|
|
224
|
+
self._conn.executescript(TABLES_SQL)
|
|
225
|
+
self._conn.executescript(INDEXES_SQL)
|
|
226
|
+
self._conn.executescript(FTS5_TABLE_SQL)
|
|
227
|
+
self._conn.executescript(FTS5_TRIGGERS_SQL)
|
|
228
|
+
self._conn.commit()
|
|
229
|
+
|
|
230
|
+
# Ensure the column 'content' exists (retroactive migration for legacy databases)
|
|
231
|
+
cursor = self._conn.execute("PRAGMA table_info(nodes)")
|
|
232
|
+
cols = [row[1] for row in cursor.fetchall()]
|
|
233
|
+
if "content" not in cols:
|
|
234
|
+
self._conn.execute("ALTER TABLE nodes ADD COLUMN content TEXT;")
|
|
235
|
+
self._conn.commit()
|
|
236
|
+
logger.info("Migration: 'content' column added to table 'nodes'.")
|
|
237
|
+
|
|
238
|
+
# Ensure 'utility_alpha' and 'utility_beta' columns exist in semantic_facts
|
|
239
|
+
cursor = self._conn.execute("PRAGMA table_info(semantic_facts)")
|
|
240
|
+
sem_cols = [row[1] for row in cursor.fetchall()]
|
|
241
|
+
if "utility_alpha" not in sem_cols:
|
|
242
|
+
self._conn.execute("ALTER TABLE semantic_facts ADD COLUMN utility_alpha REAL NOT NULL DEFAULT 1.0;")
|
|
243
|
+
self._conn.commit()
|
|
244
|
+
logger.info("Migration: 'utility_alpha' column added to table 'semantic_facts'.")
|
|
245
|
+
if "utility_beta" not in sem_cols:
|
|
246
|
+
self._conn.execute("ALTER TABLE semantic_facts ADD COLUMN utility_beta REAL NOT NULL DEFAULT 1.0;")
|
|
247
|
+
self._conn.commit()
|
|
248
|
+
logger.info("Migration: 'utility_beta' column added to table 'semantic_facts'.")
|
|
249
|
+
|
|
250
|
+
# Ensure the current version is saved in DB.
|
|
251
|
+
current_version = self.get_schema_version()
|
|
252
|
+
if not current_version or current_version != self.SCHEMA_VERSION:
|
|
253
|
+
self.set_schema_version(self.SCHEMA_VERSION)
|
|
254
|
+
|
|
255
|
+
# Retroactive migration: UNIQUE(scope_type, scope_id, block_label) in user_core_memory.
|
|
256
|
+
# Databases created before this version do not have the unique index — without it the
|
|
257
|
+
# INSERT OR REPLACE does not work as upsert.
|
|
258
|
+
cursor = self._conn.execute(
|
|
259
|
+
"SELECT name FROM sqlite_master WHERE type='index' AND name='uq_core_memory_scope_label'"
|
|
260
|
+
)
|
|
261
|
+
if cursor.fetchone() is None:
|
|
262
|
+
try:
|
|
263
|
+
# Removes duplicates keeping only the most recent record
|
|
264
|
+
self._conn.execute("""
|
|
265
|
+
DELETE FROM user_core_memory
|
|
266
|
+
WHERE id NOT IN (
|
|
267
|
+
SELECT MAX(id) FROM user_core_memory
|
|
268
|
+
GROUP BY scope_type, scope_id, block_label
|
|
269
|
+
)
|
|
270
|
+
""")
|
|
271
|
+
self._conn.execute("""
|
|
272
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uq_core_memory_scope_label
|
|
273
|
+
ON user_core_memory(scope_type, scope_id, block_label)
|
|
274
|
+
""")
|
|
275
|
+
self._conn.commit()
|
|
276
|
+
logger.info("Migration: UNIQUE index 'uq_core_memory_scope_label' created in user_core_memory.")
|
|
277
|
+
except Exception as idx_err:
|
|
278
|
+
logger.warning("Migration UNIQUE index user_core_memory failed (may already exist): %s", idx_err)
|
|
279
|
+
|
|
280
|
+
except Exception as e:
|
|
281
|
+
self._conn.rollback()
|
|
282
|
+
logger.error("Failed to apply schema: %s", e)
|
|
283
|
+
raise
|
|
284
|
+
|
|
285
|
+
def verify_tables_exist(self) -> dict[str, bool]:
|
|
286
|
+
"""Verifies which tables of Schema v3.8 exist in the database.
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
Dict mapping table name → bool (exists or not).
|
|
290
|
+
Verified tables: projects, nodes, edges, reference_wings,
|
|
291
|
+
trajectories, commit_log, nodes_fts.
|
|
292
|
+
"""
|
|
293
|
+
required_tables = [
|
|
294
|
+
"projects", "nodes", "edges", "reference_wings",
|
|
295
|
+
"trajectories", "commit_log", "nodes_fts",
|
|
296
|
+
"user_core_memory", "semantic_facts"
|
|
297
|
+
]
|
|
298
|
+
|
|
299
|
+
cursor = self._conn.execute(
|
|
300
|
+
"SELECT name FROM sqlite_master WHERE type='table' OR type='view'"
|
|
301
|
+
)
|
|
302
|
+
existing_tables = {row[0] for row in cursor.fetchall()}
|
|
303
|
+
|
|
304
|
+
return {table: table in existing_tables for table in required_tables}
|
|
305
|
+
|
|
306
|
+
def verify_triggers_exist(self) -> dict[str, bool]:
|
|
307
|
+
"""Verifies which FTS5 triggers exist in the database.
|
|
308
|
+
|
|
309
|
+
Returns:
|
|
310
|
+
Dict mapping trigger name → bool.
|
|
311
|
+
Triggers: nodes_ai, nodes_ad, nodes_au.
|
|
312
|
+
"""
|
|
313
|
+
required_triggers = ["nodes_ai", "nodes_ad", "nodes_au"]
|
|
314
|
+
|
|
315
|
+
cursor = self._conn.execute("SELECT name FROM sqlite_master WHERE type='trigger'")
|
|
316
|
+
existing_triggers = {row[0] for row in cursor.fetchall()}
|
|
317
|
+
|
|
318
|
+
return {trigger: trigger in existing_triggers for trigger in required_triggers}
|
|
319
|
+
|
|
320
|
+
def get_schema_version(self) -> Optional[str]:
|
|
321
|
+
"""Returns the schema version stored in user_version of SQLite (converted back).
|
|
322
|
+
In SQLite, we will use 'user_version' which stores int.
|
|
323
|
+
|
|
324
|
+
Since we want to store semantic strings, should we save them in a metadata table?
|
|
325
|
+
But if it doesn't exist, we will use a trick with PRAGMA user_version.
|
|
326
|
+
3.8.0 -> 308000
|
|
327
|
+
"""
|
|
328
|
+
cursor = self._conn.execute("PRAGMA user_version")
|
|
329
|
+
val = cursor.fetchone()[0]
|
|
330
|
+
if val == 0:
|
|
331
|
+
return None
|
|
332
|
+
|
|
333
|
+
# Converts back: 308000 -> 3.8.0
|
|
334
|
+
major = val // 100000
|
|
335
|
+
minor = (val % 100000) // 1000
|
|
336
|
+
patch = val % 1000
|
|
337
|
+
return f"{major}.{minor}.{patch}"
|
|
338
|
+
|
|
339
|
+
def set_schema_version(self, version: str) -> None:
|
|
340
|
+
"""Writes the schema version in PRAGMA user_version.
|
|
341
|
+
|
|
342
|
+
Args:
|
|
343
|
+
version: Semantic version (e.g. '3.8.0').
|
|
344
|
+
"""
|
|
345
|
+
try:
|
|
346
|
+
parts = [int(p) for p in version.split(".")]
|
|
347
|
+
# major * 100000 + minor * 1000 + patch
|
|
348
|
+
val = parts[0] * 100000 + parts[1] * 1000 + parts[2]
|
|
349
|
+
self._conn.execute(f"PRAGMA user_version = {val}")
|
|
350
|
+
self._conn.commit()
|
|
351
|
+
logger.debug("Schema version saved as PRAGMA user_version=%d", val)
|
|
352
|
+
except Exception as e:
|
|
353
|
+
logger.warning("Failed to save schema version %s: %s", version, e)
|
|
354
|
+
|
|
355
|
+
def rebuild_fts_index(self) -> None:
|
|
356
|
+
"""Rebuilds the FTS5 index from the nodes table.
|
|
357
|
+
|
|
358
|
+
Useful after large ingestion operations (concierge mine).
|
|
359
|
+
Potentially slow operation for large databases.
|
|
360
|
+
"""
|
|
361
|
+
logger.info("Manual rebuild of FTS5 index started...")
|
|
362
|
+
try:
|
|
363
|
+
self._conn.execute("INSERT INTO nodes_fts(nodes_fts) VALUES('rebuild');")
|
|
364
|
+
self._conn.commit()
|
|
365
|
+
logger.info("FTS5 index rebuilt successfully.")
|
|
366
|
+
except Exception as e:
|
|
367
|
+
self._conn.rollback()
|
|
368
|
+
logger.error("Failed to rebuild FTS5: %s", e)
|
|
369
|
+
raise
|
|
370
|
+
|
|
371
|
+
def get_table_row_counts(self) -> dict[str, int]:
|
|
372
|
+
"""Returns the row count of each table in the schema.
|
|
373
|
+
|
|
374
|
+
Returns:
|
|
375
|
+
Dict mapping table name → record count.
|
|
376
|
+
"""
|
|
377
|
+
counts = {}
|
|
378
|
+
tables = [
|
|
379
|
+
"projects", "nodes", "edges", "reference_wings",
|
|
380
|
+
"trajectories", "commit_log", "user_core_memory", "semantic_facts"
|
|
381
|
+
]
|
|
382
|
+
|
|
383
|
+
for table in tables:
|
|
384
|
+
try:
|
|
385
|
+
cursor = self._conn.execute(f"SELECT COUNT(*) FROM {table}")
|
|
386
|
+
counts[table] = cursor.fetchone()[0]
|
|
387
|
+
except sqlite3.OperationalError:
|
|
388
|
+
counts[table] = -1 # Indicates that the table probably does not exist
|
|
389
|
+
|
|
390
|
+
return counts
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""
|
|
2
|
+
storage/semantic_logic.py — Grafo Concierge v3.8.0 (Absolute Solidity)
|
|
3
|
+
|
|
4
|
+
Pure mutation and query functions for the semantic_facts table.
|
|
5
|
+
All functions operate by receiving the sqlite3.Connection as a parameter
|
|
6
|
+
in order to ensure isolation and prevent Database is Locked (Single-Writer).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import sqlite3
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def insert_semantic_fact(
|
|
16
|
+
conn: sqlite3.Connection,
|
|
17
|
+
scope_type: str,
|
|
18
|
+
scope_id: str,
|
|
19
|
+
fact_statement: str,
|
|
20
|
+
utility_alpha: float = 1.0,
|
|
21
|
+
utility_beta: float = 1.0
|
|
22
|
+
) -> int:
|
|
23
|
+
"""Inserts a new active semantic fact into the semantic_facts table.
|
|
24
|
+
|
|
25
|
+
Strictly validates scopes and parameters.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
conn: Active SQLite connection.
|
|
29
|
+
scope_type: Scope type (user, session, agent, org).
|
|
30
|
+
scope_id: Unique identifier of the scope.
|
|
31
|
+
fact_statement: The text of the preference or extracted fact.
|
|
32
|
+
utility_alpha: Historical successes of the fact (default: 1.0).
|
|
33
|
+
utility_beta: Historical failures of the fact (default: 1.0).
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
The ID (rowid) of the newly inserted record.
|
|
37
|
+
"""
|
|
38
|
+
if scope_type not in ("user", "session", "agent", "org"):
|
|
39
|
+
raise ValueError(f"Invalid scope_type: '{scope_type}'. Accepted: user, session, agent, org")
|
|
40
|
+
if not scope_id or not scope_id.strip():
|
|
41
|
+
raise ValueError("scope_id cannot be empty or null")
|
|
42
|
+
if not fact_statement or not fact_statement.strip():
|
|
43
|
+
raise ValueError("fact_statement cannot be empty or null")
|
|
44
|
+
|
|
45
|
+
cursor = conn.execute(
|
|
46
|
+
"""INSERT INTO semantic_facts (scope_type, scope_id, fact_statement, utility_alpha, utility_beta)
|
|
47
|
+
VALUES (?, ?, ?, ?, ?)""",
|
|
48
|
+
(scope_type, scope_id.strip(), fact_statement.strip(), utility_alpha, utility_beta)
|
|
49
|
+
)
|
|
50
|
+
return cursor.lastrowid
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def invalidate_semantic_fact(conn: sqlite3.Connection, fact_id: int) -> None:
|
|
54
|
+
"""Invalidates a semantic fact by setting t_invalid to the current timestamp.
|
|
55
|
+
|
|
56
|
+
Represents the temporal revocation of a fact.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
conn: Active SQLite connection.
|
|
60
|
+
fact_id: ID of the semantic fact to be invalidated.
|
|
61
|
+
"""
|
|
62
|
+
if not isinstance(fact_id, int):
|
|
63
|
+
raise ValueError("fact_id must be of type int")
|
|
64
|
+
|
|
65
|
+
conn.execute(
|
|
66
|
+
"""UPDATE semantic_facts
|
|
67
|
+
SET t_invalid = datetime('now')
|
|
68
|
+
WHERE id = ?""",
|
|
69
|
+
(fact_id,)
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def get_active_semantic_facts(conn: sqlite3.Connection, scope_type: str, scope_id: str) -> list[dict[str, Any]]:
|
|
74
|
+
"""Returns all currently active semantic facts for a scope.
|
|
75
|
+
|
|
76
|
+
A fact is considered active if t_invalid is NULL.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
conn: Active SQLite connection.
|
|
80
|
+
scope_type: Scope type (user, session, agent, org).
|
|
81
|
+
scope_id: Unique identifier of the scope.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
List of dictionaries containing active facts data.
|
|
85
|
+
"""
|
|
86
|
+
if scope_type not in ("user", "session", "agent", "org"):
|
|
87
|
+
raise ValueError(f"Invalid scope_type: '{scope_type}'. Accepted: user, session, agent, org")
|
|
88
|
+
if not scope_id or not scope_id.strip():
|
|
89
|
+
raise ValueError("scope_id cannot be empty or null")
|
|
90
|
+
|
|
91
|
+
cursor = conn.execute(
|
|
92
|
+
"""SELECT id, scope_type, scope_id, fact_statement, t_valid, t_invalid, utility_alpha, utility_beta, created_at
|
|
93
|
+
FROM semantic_facts
|
|
94
|
+
WHERE scope_type = ? AND scope_id = ? AND t_invalid IS NULL
|
|
95
|
+
ORDER BY id ASC""",
|
|
96
|
+
(scope_type, scope_id.strip())
|
|
97
|
+
)
|
|
98
|
+
return [dict(row) for row in cursor.fetchall()]
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def update_memory_utility(conn: sqlite3.Connection, fact_id: int, was_useful: bool) -> None:
|
|
102
|
+
"""Updates the Bayesian utility of a fact (increments alpha on success or beta on failure).
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
conn: Active SQLite connection.
|
|
106
|
+
fact_id: ID of the semantic fact.
|
|
107
|
+
was_useful: Boolean indicating if the fact was useful (True) or not (False).
|
|
108
|
+
"""
|
|
109
|
+
if not isinstance(fact_id, int):
|
|
110
|
+
raise ValueError("fact_id must be of type int")
|
|
111
|
+
|
|
112
|
+
if was_useful:
|
|
113
|
+
conn.execute(
|
|
114
|
+
"""UPDATE semantic_facts
|
|
115
|
+
SET utility_alpha = utility_alpha + 1.0
|
|
116
|
+
WHERE id = ?""",
|
|
117
|
+
(fact_id,)
|
|
118
|
+
)
|
|
119
|
+
else:
|
|
120
|
+
conn.execute(
|
|
121
|
+
"""UPDATE semantic_facts
|
|
122
|
+
SET utility_beta = utility_beta + 1.0
|
|
123
|
+
WHERE id = ?""",
|
|
124
|
+
(fact_id,)
|
|
125
|
+
)
|