Salvage of #35508 (@dchenk), rebased onto current main. Resolved the tests/tools/test_stage2_hook_puid_pgid.py conflict (kept both the envdir-creation regression test on main and the new config-migration tests). Docker image upgrades replace code under $INSTALL_DIR but preserve $HERMES_HOME on the mounted volume, so the persisted config.yaml never received the schema migrations that non-Docker `hermes update` runs (#35406). This adds scripts/docker_config_migrate.py, invoked from stage2-hook after first-boot seeding and before gateway services start: it backs up config.yaml + .env, runs migrate_config(interactive=False), and honors HERMES_SKIP_CONFIG_MIGRATION=1 for manual control. Also fixes a latent bug in check_config_version(): it called load_config() which deep-merges DEFAULT_CONFIG, so a legacy config with no raw _config_version falsely reported as already-current. It now reads the raw on-disk file so legacy configs are correctly detected for migration. Differs from #35508 as submitted (Option B cleanup): dropped the `_config_version` line added to cli-config.yaml.example and removed the accompanying test_cli_config_example_declares_latest_version change-detector test. The example is a copy-template and has no business asserting a schema version; check_config_version() reads the user's real config.yaml, not the example. This removes a second sync point that drifts on every version bump. Closes #35508. Fixes #35406. Co-authored-by: Dmitriy Cherchenko <17372886+dchenk@users.noreply.github.com>
This commit is contained in:
co-authored by
Dmitriy Cherchenko
parent
92be989291
commit
04d620d91f
+36
-5
@@ -3792,15 +3792,46 @@ def get_custom_provider_context_length(
|
||||
return None
|
||||
|
||||
|
||||
def _coerce_config_version(value: Any) -> int:
|
||||
"""Return a safe integer config version, treating invalid values as legacy."""
|
||||
if isinstance(value, bool):
|
||||
return 0
|
||||
try:
|
||||
version = int(value)
|
||||
except (TypeError, ValueError):
|
||||
return 0
|
||||
return max(version, 0)
|
||||
|
||||
|
||||
def check_config_version() -> Tuple[int, int]:
|
||||
"""
|
||||
Check config version.
|
||||
|
||||
Check the raw on-disk config schema version.
|
||||
|
||||
``load_config()`` deliberately starts from ``DEFAULT_CONFIG`` and deep-merges
|
||||
the user's file, which is correct for runtime reads but wrong for deciding
|
||||
whether the user's persisted schema has been migrated. A config file with no
|
||||
raw ``_config_version`` must remain visible as legacy instead of inheriting
|
||||
the latest default version in memory.
|
||||
|
||||
Returns (current_version, latest_version).
|
||||
"""
|
||||
config = load_config()
|
||||
current = config.get("_config_version", 0)
|
||||
latest = DEFAULT_CONFIG.get("_config_version", 1)
|
||||
latest = _coerce_config_version(DEFAULT_CONFIG.get("_config_version", 1)) or 1
|
||||
config_path = get_config_path()
|
||||
if not config_path.exists():
|
||||
return latest, latest
|
||||
|
||||
try:
|
||||
with open(config_path, encoding="utf-8") as f:
|
||||
config = yaml.safe_load(f) or {}
|
||||
except Exception as e:
|
||||
# Invalid YAML needs a parse warning, not an automatic schema rewrite
|
||||
# that could replace the user's broken file with defaults.
|
||||
_warn_config_parse_failure(config_path, e)
|
||||
return latest, latest
|
||||
|
||||
if not isinstance(config, dict):
|
||||
config = {}
|
||||
current = _coerce_config_version(config.get("_config_version"))
|
||||
return current, latest
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user