This commit is contained in:
DawnARC
2026-04-13 13:09:57 +08:00
parent 7a42a1cb2a
commit b3fb662f3d
17 changed files with 3589 additions and 74 deletions

View File

@@ -62,7 +62,10 @@ if any(arg in {"-h", "--help"} for arg in sys.argv[1:]):
try:
from A_memorix.core.storage import GraphStore, KnowledgeType, MetadataStore, QuantizationType, VectorStore
from A_memorix.core.storage.metadata_store import SCHEMA_VERSION
from A_memorix.core.storage.metadata_store import (
RUNTIME_AUTO_MIGRATION_MIN_SCHEMA_VERSION,
SCHEMA_VERSION,
)
except Exception as e: # pragma: no cover
print(f"❌ failed to import storage modules: {e}")
raise SystemExit(2)
@@ -125,6 +128,14 @@ def _sqlite_table_exists(conn: sqlite3.Connection, table: str) -> bool:
return row is not None
def _sqlite_column_exists(conn: sqlite3.Connection, table: str, column: str) -> bool:
try:
rows = conn.execute(f"PRAGMA table_info({table})").fetchall()
except Exception:
return False
return any(str(row[1] or "") == str(column or "") for row in rows)
def _collect_hash_alias_conflicts(conn: sqlite3.Connection) -> Dict[str, List[str]]:
hashes: List[str] = []
if _sqlite_table_exists(conn, "relations"):
@@ -152,6 +163,8 @@ def _collect_hash_alias_conflicts(conn: sqlite3.Connection) -> Dict[str, List[st
def _collect_invalid_knowledge_types(conn: sqlite3.Connection) -> List[str]:
if not _sqlite_table_exists(conn, "paragraphs"):
return []
if not _sqlite_column_exists(conn, "paragraphs", "knowledge_type"):
return []
allowed = {item.value for item in KnowledgeType}
rows = conn.execute("SELECT DISTINCT knowledge_type FROM paragraphs").fetchall()
@@ -288,6 +301,14 @@ def _preflight_impl(config_path: Path, data_dir: Path) -> Dict[str, Any]:
facts["schema_migrations_exists"] = has_schema_table
has_paragraph_backfill = _sqlite_table_exists(conn, "paragraph_vector_backfill")
facts["paragraph_vector_backfill_exists"] = has_paragraph_backfill
has_stale_marks = _sqlite_table_exists(conn, "paragraph_stale_relation_marks")
facts["paragraph_stale_relation_marks_exists"] = has_stale_marks
has_profile_refresh_queue = _sqlite_table_exists(conn, "person_profile_refresh_queue")
facts["person_profile_refresh_queue_exists"] = has_profile_refresh_queue
has_feedback_rollback_status = _sqlite_column_exists(conn, "memory_feedback_tasks", "rollback_status")
facts["memory_feedback_tasks_rollback_status_exists"] = has_feedback_rollback_status
has_feedback_rollback_plan = _sqlite_column_exists(conn, "memory_feedback_tasks", "rollback_plan_json")
facts["memory_feedback_tasks_rollback_plan_exists"] = has_feedback_rollback_plan
if not has_schema_table:
checks.append(
CheckItem(
@@ -300,14 +321,28 @@ def _preflight_impl(config_path: Path, data_dir: Path) -> Dict[str, Any]:
row = conn.execute("SELECT MAX(version) FROM schema_migrations").fetchone()
version = int(row[0]) if row and row[0] is not None else 0
facts["schema_version"] = version
runtime_auto_migratable = (
version < SCHEMA_VERSION
and version >= RUNTIME_AUTO_MIGRATION_MIN_SCHEMA_VERSION
)
facts["schema_runtime_auto_migratable"] = runtime_auto_migratable
if version != SCHEMA_VERSION:
checks.append(
CheckItem(
"CP-08",
"error",
f"schema version mismatch: current={version}, expected={SCHEMA_VERSION}",
if runtime_auto_migratable:
checks.append(
CheckItem(
"CP-18",
"warning",
f"schema version behind runtime target: current={version}, expected={SCHEMA_VERSION}; runtime auto migration will handle this update",
)
)
else:
checks.append(
CheckItem(
"CP-08",
"error",
f"schema version mismatch: current={version}, expected={SCHEMA_VERSION}",
)
)
)
elif not has_paragraph_backfill:
checks.append(
CheckItem(
@@ -316,6 +351,30 @@ def _preflight_impl(config_path: Path, data_dir: Path) -> Dict[str, Any]:
"paragraph_vector_backfill table missing under current schema version",
)
)
elif not has_stale_marks:
checks.append(
CheckItem(
"CP-15",
"error",
"paragraph_stale_relation_marks table missing under current schema version",
)
)
elif not has_profile_refresh_queue:
checks.append(
CheckItem(
"CP-16",
"error",
"person_profile_refresh_queue table missing under current schema version",
)
)
elif not has_feedback_rollback_status or not has_feedback_rollback_plan:
checks.append(
CheckItem(
"CP-17",
"error",
"memory_feedback_tasks rollback columns missing under current schema version",
)
)
if _sqlite_table_exists(conn, "relations"):
row = conn.execute("SELECT COUNT(*) FROM relations").fetchone()
@@ -616,6 +675,46 @@ def _verify_impl(config_path: Path, data_dir: Path) -> Dict[str, Any]:
"paragraph_vector_backfill table missing after migration",
)
)
has_feedback_tasks = _sqlite_table_exists(conn, "memory_feedback_tasks")
facts["memory_feedback_tasks_exists"] = bool(has_feedback_tasks)
if not has_feedback_tasks:
checks.append(
CheckItem(
"CP-15",
"error",
"memory_feedback_tasks table missing after migration",
)
)
has_feedback_logs = _sqlite_table_exists(conn, "memory_feedback_action_logs")
facts["memory_feedback_action_logs_exists"] = bool(has_feedback_logs)
if not has_feedback_logs:
checks.append(
CheckItem(
"CP-16",
"error",
"memory_feedback_action_logs table missing after migration",
)
)
has_feedback_rollback_status = _sqlite_column_exists(conn, "memory_feedback_tasks", "rollback_status")
facts["memory_feedback_tasks_rollback_status_exists"] = bool(has_feedback_rollback_status)
if not has_feedback_rollback_status:
checks.append(
CheckItem(
"CP-17",
"error",
"memory_feedback_tasks.rollback_status missing after migration",
)
)
has_feedback_rollback_plan = _sqlite_column_exists(conn, "memory_feedback_tasks", "rollback_plan_json")
facts["memory_feedback_tasks_rollback_plan_exists"] = bool(has_feedback_rollback_plan)
if not has_feedback_rollback_plan:
checks.append(
CheckItem(
"CP-18",
"error",
"memory_feedback_tasks.rollback_plan_json missing after migration",
)
)
conflicts = _collect_hash_alias_conflicts(conn)
invalid_knowledge_types = _collect_invalid_knowledge_types(conn)
finally: