diff --git a/loopx/control_plane/status/goal_source.py b/loopx/control_plane/status/goal_source.py index 1400118ba0..1572863b87 100644 --- a/loopx/control_plane/status/goal_source.py +++ b/loopx/control_plane/status/goal_source.py @@ -23,6 +23,7 @@ from urllib.parse import parse_qs, urlencode, urlparse from urllib.request import HTTPRedirectHandler, Request, build_opener +from ..content_digest import BARE_SHA256_PATTERN from ...chat_completed_todos import CompletedTodoPages from ...status_server import is_loopback_host from .ssh_host_catalog import configured_ssh_host_aliases @@ -85,7 +86,7 @@ def __post_init__(self): elif self.host_alias is not None: raise ValueError("Local connections do not have an SSH alias") if self.environment == "container": - if not isinstance(self.container_id, str) or not re.fullmatch(r"[0-9a-f]{64}", self.container_id): + if not isinstance(self.container_id, str) or not BARE_SHA256_PATTERN.fullmatch(self.container_id): raise ValueError("A full pinned container id is required") elif self.container_id is not None: raise ValueError("Host environments do not have a container id") @@ -119,7 +120,7 @@ def parse(cls, raw): container_id = None elif environment.get("kind") == "container" and set(environment) == {"kind", "container_id"}: container_id = environment["container_id"] - if not isinstance(container_id, str) or not re.fullmatch(r"[0-9a-f]{64}", container_id): + if not isinstance(container_id, str) or not BARE_SHA256_PATTERN.fullmatch(container_id): raise ValueError("A full pinned container id is required") else: raise ValueError("Use a host or a pinned container environment") @@ -318,7 +319,7 @@ def read(path): description = read(base) source = description.get("source") namespace = source.get("namespace") if isinstance(source, dict) else None - if not isinstance(namespace, str) or not re.fullmatch(r"[0-9a-f]{64}", namespace): + if not isinstance(namespace, str) or not BARE_SHA256_PATTERN.fullmatch(namespace): raise ValueError("The reader did not expose a source identity") if source_namespace is not None and source_namespace != namespace: raise ValueError("Goal source changed; do not reuse the previous selection") diff --git a/loopx/hosts/checkpoint_retention.py b/loopx/hosts/checkpoint_retention.py index 170ceefe52..7b12f3fe0d 100644 --- a/loopx/hosts/checkpoint_retention.py +++ b/loopx/hosts/checkpoint_retention.py @@ -15,12 +15,13 @@ import time from time import monotonic, sleep +from ..control_plane.content_digest import BARE_SHA256_PATTERN from ..control_plane.effect_runtime import effect_runtime_result from ..file_lock import exclusive_file_lock, write_json_atomic _STATE = ".checkpoint-retention" _NAME = re.compile(r"[A-Za-z0-9][A-Za-z0-9_-]{0,127}\Z") -_SHA = re.compile(r"[0-9a-f]{64}\Z") +_SHA = BARE_SHA256_PATTERN def _physical(path: Path) -> Path: diff --git a/tests/architecture/test_content_digest_single_owner.py b/tests/architecture/test_content_digest_single_owner.py index 755a2fef82..f544b06703 100644 --- a/tests/architecture/test_content_digest_single_owner.py +++ b/tests/architecture/test_content_digest_single_owner.py @@ -194,6 +194,7 @@ "loopx.control_plane.goals.deletion_service", "loopx.control_plane.goals.goal_amendment_proposal", "loopx.control_plane.projects.registry_codec", + "loopx.control_plane.status.goal_source", "loopx.control_plane.testing.cli_output_semantics", "loopx.control_plane.testing.release_commit_qualification", "loopx.control_plane.todos.completion_result", @@ -209,6 +210,7 @@ "loopx.extensions.lark.document_comment_provider", "loopx.extensions.openviking_semantic_preference.history_export", "loopx.extensions.presentation", + "loopx.hosts.checkpoint_retention", "loopx.hosts.codex_checkpoint", "loopx.presentation.chat_bundle", "loopx.presentation.static_site",