fix(checkpoints): profile rename finds the helpers its sibling calls
checkpoint_manager_profile_rename.py (merged from main) reaches for _store_has_head/_ref_tip/_list_project_refs/_unlink_quiet, which main extracted from checkpoint_manager after this branch diverged; a profile rename with checkpoints raised AttributeError. Add the same helpers here. Also: tests follow this branch's contracts (model_metadata_http.get instead of a requests module, no agent.ssl_guard, backup excludes PM trees too, _safe_restore_plan), and run_tests.sh forwards SSL_CERT_FILE/DIR so network tests verify TLS with the pinned interpreter on hosts without a compiled-in bundle path.
This commit is contained in:
@@ -139,11 +139,15 @@ done
|
||||
#
|
||||
# These are test-infrastructure knobs, not credentials — same class as the
|
||||
# HERMES_RUN_SLOW_PET_TESTS / HERMES_E2E_BROWSER opt-ins already forwarded.
|
||||
# SSL_CERT_FILE/DIR are trust-store locations: the pinned interpreter's
|
||||
# OpenSSL has no compiled-in bundle path on NixOS, so network tests (PM
|
||||
# downloads, channel reads) need the host's pointer to verify TLS.
|
||||
# Keep this an explicit allowlist (no HERMES_TEST_* glob) so the "no
|
||||
# credential can leak" property stays auditable at a glance.
|
||||
TEST_ENV=()
|
||||
for _test_var in HERMES_TEST_IMAGE HERMES_TEST_WORKERS HERMES_TEST_PATHS \
|
||||
HERMES_TEST_FILE_TIMEOUT HERMES_TEST_FILE_RETRIES HERMES_TEST_SLICE; do
|
||||
HERMES_TEST_FILE_TIMEOUT HERMES_TEST_FILE_RETRIES HERMES_TEST_SLICE \
|
||||
SSL_CERT_FILE SSL_CERT_DIR; do
|
||||
if [ -n "${!_test_var:-}" ]; then
|
||||
TEST_ENV+=("$_test_var=${!_test_var}")
|
||||
fi
|
||||
|
||||
@@ -59,7 +59,7 @@ def test_astra_900k_opt_in_preserves_live_limits_and_wire_contract(monkeypatch,
|
||||
|
||||
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
|
||||
monkeypatch.setattr(metadata, "_codex_oauth_context_cache", {})
|
||||
monkeypatch.setattr(metadata.requests, "get", lambda *args, **kwargs: SimpleNamespace(
|
||||
monkeypatch.setattr(metadata.model_metadata_http, "get", lambda *args, **kwargs: SimpleNamespace(
|
||||
status_code=200,
|
||||
json=lambda: {"models": [{"slug": "gpt-6-astra", "context_window": advertised}]},
|
||||
))
|
||||
|
||||
@@ -144,7 +144,6 @@ def test_missing_key_banner_is_classified_without_hiding_initialization(tmp_path
|
||||
monkeypatch.setattr(agent_init, "_explicit_client_kwargs",
|
||||
lambda *a: {"api_key": "dummy-key", "base_url": agent.base_url})
|
||||
monkeypatch.setattr(agent_init, "_apply_openai_header_policy", lambda *a: None)
|
||||
monkeypatch.setattr("agent.ssl_guard.verify_ca_bundle", lambda: None)
|
||||
agent_init._init_openai_client(agent, "dummy-key", agent.base_url, None, 30)
|
||||
output = capsys.readouterr().out
|
||||
assert ("API key appears invalid or missing" in output) is (suppress is not True)
|
||||
|
||||
@@ -303,8 +303,7 @@ def test_catalog_requests_use_ungated_client_version(monkeypatch):
|
||||
|
||||
monkeypatch.setitem(sys.modules, "httpx", _FakeHttpx)
|
||||
codex_models._fetch_models_from_api(access_token="tok")
|
||||
monkeypatch.setattr(model_metadata, "requests", _FakeRequests)
|
||||
monkeypatch.setattr(model_metadata, "_ensure_requests", lambda: None)
|
||||
monkeypatch.setattr(model_metadata.model_metadata_http, "get", _FakeRequests.get)
|
||||
monkeypatch.setattr(model_metadata, "_codex_oauth_context_cache", {})
|
||||
model_metadata._fetch_codex_oauth_context_lengths_with_source("tok")
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ def test_rename_preserves_profile_local_checkpoint_history(profile_env, tmp_path
|
||||
assert str(new_workdir.resolve()) in project_paths
|
||||
assert str(workdir.resolve()) not in project_paths
|
||||
|
||||
plan = manager.safe_restore_plan(str(new_workdir), checkpoint_hash)
|
||||
plan = manager._safe_restore_plan(str(new_workdir), checkpoint_hash)
|
||||
assert plan["success"] is True
|
||||
assert plan["restore"] == ["note.txt"]
|
||||
assert plan["skipped"] == []
|
||||
|
||||
@@ -1702,11 +1702,12 @@ class TestCloneAllExcludesRuntimeTrees:
|
||||
|
||||
def test_runtime_trio_is_one_constant_shared_with_backup(self):
|
||||
"""backup's exclusion list and the clone-all root gate must be built from the same
|
||||
constant; two literals drifting apart is how the models/ copy of #111718 crept in."""
|
||||
constant; two literals drifting apart is how the models/ copy of #111718 crept in.
|
||||
backup additionally drops PM's regenerable trees; it never drops less."""
|
||||
from hermes_cli import backup, profiles
|
||||
from hermes_constants import LOCAL_RUNTIME_ROOT_DIRS
|
||||
assert LOCAL_RUNTIME_ROOT_DIRS == frozenset(self.RUNTIME_TREES)
|
||||
assert backup._EXCLUDED_ROOT_DIRS is LOCAL_RUNTIME_ROOT_DIRS
|
||||
assert LOCAL_RUNTIME_ROOT_DIRS <= backup._EXCLUDED_ROOT_DIRS
|
||||
assert LOCAL_RUNTIME_ROOT_DIRS <= profiles._CLONE_ALL_DEFAULT_EXCLUDE_ROOT
|
||||
|
||||
def test_clone_all_from_default_skips_runtime_trees_but_keeps_the_rest(self, profile_env):
|
||||
|
||||
@@ -222,6 +222,10 @@ def _store_path(base: Optional[Path] = None) -> Path:
|
||||
return (base or _resolve_checkpoint_base()) / _STORE_DIRNAME
|
||||
|
||||
|
||||
def _store_has_head(store: Path) -> bool:
|
||||
return (store / "HEAD").exists()
|
||||
|
||||
|
||||
def _index_path(store: Path, dir_hash: str) -> Path:
|
||||
return store / _INDEXES_DIRNAME / dir_hash
|
||||
|
||||
@@ -405,6 +409,29 @@ def _run_git(
|
||||
return False, "", str(exc)
|
||||
|
||||
|
||||
def _git_out(args: List[str], store: Path, working_dir: str, rc: Optional[Set[int]] = None) -> str:
|
||||
"""stdout of a successful git call, else ``""``."""
|
||||
ok, out, _ = _run_git(args, store, working_dir, allowed_returncodes=rc)
|
||||
return out if ok else ""
|
||||
|
||||
|
||||
def _ref_tip(store: Path, working_dir: str, ref: str) -> Optional[str]:
|
||||
"""Commit sha at ``ref``, or None when the ref does not exist yet."""
|
||||
return _git_out(["rev-parse", "--verify", ref + "^{commit}"], store, working_dir, {128}) or None
|
||||
|
||||
|
||||
def _list_project_refs(store: Path, working_dir: str) -> List[str]:
|
||||
out = _git_out(["for-each-ref", "--format=%(refname)", _REFS_PREFIX], store, working_dir, {128})
|
||||
return [r for r in out.splitlines() if r.strip()]
|
||||
|
||||
|
||||
def _unlink_quiet(path: Path) -> None:
|
||||
try:
|
||||
path.unlink(missing_ok=True)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Store initialisation + legacy migration
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user