Files
ThothII/harness/tht/paths.py
T

55 lines
1.8 KiB
Python

from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
from typing import TYPE_CHECKING
from tht.config import ConfigError
if TYPE_CHECKING:
from tht.config import Config
@dataclass(frozen=True)
class ResolvedPaths:
workspace: Path
sessions: Path
artifacts: Path
indexes: Path
corpus: Path
def _resolve_root(path: Path, workspace: Path, name: str) -> Path:
if path.is_absolute():
# Existing deployments commonly point at an external workspace checkout. Keep
# those paths working while `tht doctor` identifies them for migration.
return path
resolved = (workspace / path).resolve()
if not resolved.is_relative_to(workspace):
raise ConfigError(f"paths.{name} resolves outside workspace root")
return resolved
def resolve_workspace_paths(
config_path: Path, cfg: Config, data_root: Path
) -> ResolvedPaths:
"""Resolve portable paths under ``<data_root>/workspaces/<config stem>``.
Relative roots are sandboxed to the logical workspace. Absolute roots are a
compatibility bridge for existing installations and are never rewritten.
"""
canonical_data_root = data_root.resolve()
workspaces_root = canonical_data_root / "workspaces"
workspace = (workspaces_root / config_path.stem).resolve()
if not workspace.is_relative_to(workspaces_root):
raise ConfigError("workspace resolves outside workspaces root")
roots = cfg.roots
return ResolvedPaths(
workspace=workspace,
sessions=_resolve_root(roots.sessions, workspace, "sessions"),
artifacts=_resolve_root(roots.artifacts, workspace, "artifacts"),
indexes=_resolve_root(roots.indexes, workspace, "indexes"),
corpus=_resolve_root(Path("corpus"), workspace, "corpus"),
)