mirror of
https://github.com/zvx-echo6/meshai.git
synced 2026-05-21 23:24:44 +02:00
fix(migration): deep-equality verification gate for full Config
- Added deep_compare function for recursive dict comparison - Replaced shallow key-list check with full Config dataclass comparison - Uses dataclasses.asdict for consistent dict representation - Reports full path of mismatches (e.g. connection.tcp_host) The previous gate only checked inline sections and missed the include-related bugs that caused the restart loop. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
67ab2689fe
commit
5274933fa0
1 changed files with 46 additions and 18 deletions
|
|
@ -21,7 +21,7 @@ import os
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
from dataclasses import fields
|
from dataclasses import asdict, fields
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
|
@ -36,6 +36,35 @@ logging.basicConfig(
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# DEEP COMPARISON FOR VERIFICATION
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
def deep_compare(old, new, path=""):
|
||||||
|
"""Deep compare two values, returning list of difference descriptions."""
|
||||||
|
differences = []
|
||||||
|
if type(old) != type(new):
|
||||||
|
differences.append(f"{path}: type {type(old).__name__} vs {type(new).__name__}")
|
||||||
|
return differences
|
||||||
|
if isinstance(old, dict):
|
||||||
|
for key in sorted(set(old.keys()) | set(new.keys())):
|
||||||
|
child_path = f"{path}.{key}" if path else key
|
||||||
|
if key not in old:
|
||||||
|
differences.append(f"{child_path}: missing in backup")
|
||||||
|
elif key not in new:
|
||||||
|
differences.append(f"{child_path}: missing in new")
|
||||||
|
else:
|
||||||
|
differences.extend(deep_compare(old[key], new[key], child_path))
|
||||||
|
elif isinstance(old, list):
|
||||||
|
if len(old) != len(new):
|
||||||
|
differences.append(f"{path}: list length {len(old)} vs {len(new)}")
|
||||||
|
else:
|
||||||
|
for i, (o, n) in enumerate(zip(old, new)):
|
||||||
|
differences.extend(deep_compare(o, n, f"{path}[{i}]"))
|
||||||
|
elif old != new:
|
||||||
|
differences.append(f"{path}: {repr(old)[:50]} vs {repr(new)[:50]}")
|
||||||
|
return differences
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# CONFIGURATION
|
# CONFIGURATION
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
@ -633,24 +662,23 @@ def run_migration() -> bool:
|
||||||
|
|
||||||
# Load with new loader
|
# Load with new loader
|
||||||
new_config = new_load(TARGET_DIR)
|
new_config = new_load(TARGET_DIR)
|
||||||
new_dict = _dataclass_to_dict(new_config)
|
|
||||||
|
|
||||||
# Load backup with old loader
|
# Load backup with old loader
|
||||||
old_config = old_load(BACKUP_FILE)
|
old_config = old_load(BACKUP_FILE)
|
||||||
old_dict = _dataclass_to_dict(old_config)
|
# Deep compare all fields using asdict()
|
||||||
|
old_dict = asdict(old_config)
|
||||||
|
new_dict = asdict(new_config)
|
||||||
|
# Remove internal fields that will differ
|
||||||
|
for d in [old_dict, new_dict]:
|
||||||
|
d.pop("_config_path", None)
|
||||||
|
|
||||||
# Compare key fields (some will differ due to local/secret extraction)
|
differences = deep_compare(old_dict, new_dict)
|
||||||
differences = []
|
|
||||||
for key in ["timezone", "response", "history", "memory", "context"]:
|
|
||||||
if new_dict.get(key) != old_dict.get(key):
|
|
||||||
differences.append(f"{key}: {new_dict.get(key)} != {old_dict.get(key)}")
|
|
||||||
|
|
||||||
if differences:
|
if differences:
|
||||||
logger.error("Verification FAILED! Differences found:")
|
logger.error("Verification FAILED! Differences found:")
|
||||||
for diff in differences:
|
for diff in differences:
|
||||||
logger.error(f" {diff}")
|
logger.error(f" {diff}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
logger.info(" Verification PASSED - config loads correctly")
|
logger.info(" Verification PASSED - config loads correctly")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue