diff --git a/apps/api/src/services/agent_market_governance_snapshot.py b/apps/api/src/services/agent_market_governance_snapshot.py index d2e93088..13738f97 100644 --- a/apps/api/src/services/agent_market_governance_snapshot.py +++ b/apps/api/src/services/agent_market_governance_snapshot.py @@ -16,8 +16,9 @@ from pathlib import Path from typing import Any from zoneinfo import ZoneInfo -_REPO_ROOT = Path(__file__).resolve().parents[4] -_DEFAULT_EVALUATIONS_DIR = _REPO_ROOT / "docs" / "evaluations" +from src.services.snapshot_paths import default_evaluations_dir + +_DEFAULT_EVALUATIONS_DIR = default_evaluations_dir(Path(__file__)) _SNAPSHOT_PATTERN = "agent_market_governance_snapshot_*.json" _MARKET_WATCH_WORKFLOW = ".gitea/workflows/agent-market-watch.yaml" _TAIPEI_TZ = ZoneInfo("Asia/Taipei") diff --git a/apps/api/src/services/ai_agent_automation_backlog_snapshot.py b/apps/api/src/services/ai_agent_automation_backlog_snapshot.py index 16fbb77d..f6b72000 100644 --- a/apps/api/src/services/ai_agent_automation_backlog_snapshot.py +++ b/apps/api/src/services/ai_agent_automation_backlog_snapshot.py @@ -13,8 +13,9 @@ import json from pathlib import Path from typing import Any -_REPO_ROOT = Path(__file__).resolve().parents[4] -_DEFAULT_EVALUATIONS_DIR = _REPO_ROOT / "docs" / "evaluations" +from src.services.snapshot_paths import default_evaluations_dir + +_DEFAULT_EVALUATIONS_DIR = default_evaluations_dir(Path(__file__)) _SNAPSHOT_PATTERN = "ai_agent_automation_backlog_*.json" _SCHEMA_VERSION = "ai_agent_automation_backlog_v1" diff --git a/apps/api/src/services/ai_agent_automation_inventory_snapshot.py b/apps/api/src/services/ai_agent_automation_inventory_snapshot.py index 03da5f42..0e78d787 100644 --- a/apps/api/src/services/ai_agent_automation_inventory_snapshot.py +++ b/apps/api/src/services/ai_agent_automation_inventory_snapshot.py @@ -12,8 +12,9 @@ import json from pathlib import Path from typing import Any -_REPO_ROOT = Path(__file__).resolve().parents[4] -_DEFAULT_EVALUATIONS_DIR = _REPO_ROOT / "docs" / "evaluations" +from src.services.snapshot_paths import default_evaluations_dir + +_DEFAULT_EVALUATIONS_DIR = default_evaluations_dir(Path(__file__)) _SNAPSHOT_PATTERN = "ai_agent_automation_inventory_snapshot_*.json" _SCHEMA_VERSION = "ai_agent_automation_inventory_snapshot_v1" diff --git a/apps/api/src/services/backup_dr_readiness_matrix.py b/apps/api/src/services/backup_dr_readiness_matrix.py index b3b73018..4ee0cf25 100644 --- a/apps/api/src/services/backup_dr_readiness_matrix.py +++ b/apps/api/src/services/backup_dr_readiness_matrix.py @@ -12,8 +12,9 @@ import json from pathlib import Path from typing import Any -_REPO_ROOT = Path(__file__).resolve().parents[4] -_DEFAULT_EVALUATIONS_DIR = _REPO_ROOT / "docs" / "evaluations" +from src.services.snapshot_paths import default_evaluations_dir + +_DEFAULT_EVALUATIONS_DIR = default_evaluations_dir(Path(__file__)) _SNAPSHOT_PATTERN = "backup_dr_readiness_matrix_*.json" _SCHEMA_VERSION = "backup_dr_readiness_matrix_v1" diff --git a/apps/api/src/services/backup_dr_target_inventory.py b/apps/api/src/services/backup_dr_target_inventory.py index dccf78ac..eb981abd 100644 --- a/apps/api/src/services/backup_dr_target_inventory.py +++ b/apps/api/src/services/backup_dr_target_inventory.py @@ -12,8 +12,9 @@ import json from pathlib import Path from typing import Any -_REPO_ROOT = Path(__file__).resolve().parents[4] -_DEFAULT_EVALUATIONS_DIR = _REPO_ROOT / "docs" / "evaluations" +from src.services.snapshot_paths import default_evaluations_dir + +_DEFAULT_EVALUATIONS_DIR = default_evaluations_dir(Path(__file__)) _SNAPSHOT_PATTERN = "backup_dr_target_inventory_*.json" _SCHEMA_VERSION = "backup_dr_target_inventory_v1" diff --git a/apps/api/src/services/backup_notification_policy.py b/apps/api/src/services/backup_notification_policy.py index 55865654..b0a993d9 100644 --- a/apps/api/src/services/backup_notification_policy.py +++ b/apps/api/src/services/backup_notification_policy.py @@ -14,8 +14,9 @@ import json from pathlib import Path from typing import Any -_REPO_ROOT = Path(__file__).resolve().parents[4] -_DEFAULT_EVALUATIONS_DIR = _REPO_ROOT / "docs" / "evaluations" +from src.services.snapshot_paths import default_evaluations_dir + +_DEFAULT_EVALUATIONS_DIR = default_evaluations_dir(Path(__file__)) _SNAPSHOT_PATTERN = "backup_notification_policy_*.json" _SCHEMA_VERSION = "backup_notification_policy_v1" diff --git a/apps/api/src/services/dependency_drift_check_plan.py b/apps/api/src/services/dependency_drift_check_plan.py index f301dbc4..41d6b9b7 100644 --- a/apps/api/src/services/dependency_drift_check_plan.py +++ b/apps/api/src/services/dependency_drift_check_plan.py @@ -14,8 +14,9 @@ import json from pathlib import Path from typing import Any -_REPO_ROOT = Path(__file__).resolve().parents[4] -_DEFAULT_EVALUATIONS_DIR = _REPO_ROOT / "docs" / "evaluations" +from src.services.snapshot_paths import default_evaluations_dir + +_DEFAULT_EVALUATIONS_DIR = default_evaluations_dir(Path(__file__)) _SNAPSHOT_PATTERN = "dependency_drift_check_plan_*.json" _SCHEMA_VERSION = "dependency_drift_check_plan_v1" diff --git a/apps/api/src/services/dependency_risk_policy.py b/apps/api/src/services/dependency_risk_policy.py index a43a2b60..0f926740 100644 --- a/apps/api/src/services/dependency_risk_policy.py +++ b/apps/api/src/services/dependency_risk_policy.py @@ -14,8 +14,9 @@ import json from pathlib import Path from typing import Any -_REPO_ROOT = Path(__file__).resolve().parents[4] -_DEFAULT_EVALUATIONS_DIR = _REPO_ROOT / "docs" / "evaluations" +from src.services.snapshot_paths import default_evaluations_dir + +_DEFAULT_EVALUATIONS_DIR = default_evaluations_dir(Path(__file__)) _SNAPSHOT_PATTERN = "dependency_risk_policy_*.json" _SCHEMA_VERSION = "dependency_risk_policy_v1" diff --git a/apps/api/src/services/dependency_upgrade_approval_package_template.py b/apps/api/src/services/dependency_upgrade_approval_package_template.py index 38893bc7..1e2e0b0c 100644 --- a/apps/api/src/services/dependency_upgrade_approval_package_template.py +++ b/apps/api/src/services/dependency_upgrade_approval_package_template.py @@ -15,8 +15,9 @@ import json from pathlib import Path from typing import Any -_REPO_ROOT = Path(__file__).resolve().parents[4] -_DEFAULT_EVALUATIONS_DIR = _REPO_ROOT / "docs" / "evaluations" +from src.services.snapshot_paths import default_evaluations_dir + +_DEFAULT_EVALUATIONS_DIR = default_evaluations_dir(Path(__file__)) _SNAPSHOT_PATTERN = "dependency_upgrade_approval_package_template_*.json" _SCHEMA_VERSION = "dependency_upgrade_approval_package_template_v1" diff --git a/apps/api/src/services/docker_build_surface_inventory.py b/apps/api/src/services/docker_build_surface_inventory.py index de8e8f04..64c10280 100644 --- a/apps/api/src/services/docker_build_surface_inventory.py +++ b/apps/api/src/services/docker_build_surface_inventory.py @@ -11,8 +11,9 @@ import json from pathlib import Path from typing import Any -_REPO_ROOT = Path(__file__).resolve().parents[4] -_DEFAULT_EVALUATIONS_DIR = _REPO_ROOT / "docs" / "evaluations" +from src.services.snapshot_paths import default_evaluations_dir + +_DEFAULT_EVALUATIONS_DIR = default_evaluations_dir(Path(__file__)) _SNAPSHOT_PATTERN = "docker_build_surface_inventory_*.json" _SCHEMA_VERSION = "docker_build_surface_inventory_v1" diff --git a/apps/api/src/services/javascript_package_inventory.py b/apps/api/src/services/javascript_package_inventory.py index 01469e13..b8b3b332 100644 --- a/apps/api/src/services/javascript_package_inventory.py +++ b/apps/api/src/services/javascript_package_inventory.py @@ -11,8 +11,9 @@ import json from pathlib import Path from typing import Any -_REPO_ROOT = Path(__file__).resolve().parents[4] -_DEFAULT_EVALUATIONS_DIR = _REPO_ROOT / "docs" / "evaluations" +from src.services.snapshot_paths import default_evaluations_dir + +_DEFAULT_EVALUATIONS_DIR = default_evaluations_dir(Path(__file__)) _SNAPSHOT_PATTERN = "javascript_package_inventory_*.json" _SCHEMA_VERSION = "javascript_package_inventory_v1" diff --git a/apps/api/src/services/package_supply_chain_inventory.py b/apps/api/src/services/package_supply_chain_inventory.py index b4084c48..734e81ab 100644 --- a/apps/api/src/services/package_supply_chain_inventory.py +++ b/apps/api/src/services/package_supply_chain_inventory.py @@ -12,8 +12,9 @@ import json from pathlib import Path from typing import Any -_REPO_ROOT = Path(__file__).resolve().parents[4] -_DEFAULT_EVALUATIONS_DIR = _REPO_ROOT / "docs" / "evaluations" +from src.services.snapshot_paths import default_evaluations_dir + +_DEFAULT_EVALUATIONS_DIR = default_evaluations_dir(Path(__file__)) _SNAPSHOT_PATTERN = "package_supply_chain_inventory_*.json" _SCHEMA_VERSION = "package_supply_chain_inventory_v1" diff --git a/apps/api/src/services/snapshot_paths.py b/apps/api/src/services/snapshot_paths.py new file mode 100644 index 00000000..6640ca1a --- /dev/null +++ b/apps/api/src/services/snapshot_paths.py @@ -0,0 +1,32 @@ +""" +Snapshot path helpers. + +Resolve committed documentation snapshot paths across local repo checkouts and +production API images. In containers, service modules live under +``/app/src/services`` instead of ``apps/api/src/services``. +""" + +from __future__ import annotations + +from pathlib import Path + + +def resolve_repo_root(anchor: Path) -> Path: + """Resolve the repository root that contains committed docs snapshots.""" + resolved = anchor.resolve() + start = resolved if resolved.is_dir() else resolved.parent + for candidate in (start, *start.parents): + if (candidate / "docs").is_dir(): + return candidate + + container_root = Path("/app") + if (container_root / "docs").is_dir(): + return container_root + + parents = list(start.parents) + return parents[2] if len(parents) > 2 else start + + +def default_evaluations_dir(anchor: Path) -> Path: + """Resolve the default committed evaluations snapshot directory.""" + return resolve_repo_root(anchor) / "docs" / "evaluations" diff --git a/apps/api/tests/test_snapshot_paths.py b/apps/api/tests/test_snapshot_paths.py new file mode 100644 index 00000000..31452b13 --- /dev/null +++ b/apps/api/tests/test_snapshot_paths.py @@ -0,0 +1,27 @@ +from __future__ import annotations + +from src.services.snapshot_paths import default_evaluations_dir, resolve_repo_root + + +def test_resolve_repo_root_supports_monorepo_service_layout(tmp_path): + repo_root = tmp_path / "repo" + evaluations_dir = repo_root / "docs" / "evaluations" + evaluations_dir.mkdir(parents=True) + anchor = repo_root / "apps" / "api" / "src" / "services" / "snapshot.py" + anchor.parent.mkdir(parents=True) + anchor.write_text("", encoding="utf-8") + + assert resolve_repo_root(anchor) == repo_root + assert default_evaluations_dir(anchor) == evaluations_dir + + +def test_resolve_repo_root_supports_api_container_layout(tmp_path): + app_root = tmp_path / "app" + evaluations_dir = app_root / "docs" / "evaluations" + evaluations_dir.mkdir(parents=True) + anchor = app_root / "src" / "services" / "snapshot.py" + anchor.parent.mkdir(parents=True) + anchor.write_text("", encoding="utf-8") + + assert resolve_repo_root(anchor) == app_root + assert default_evaluations_dir(anchor) == evaluations_dir