2026-08-17 16:31:15 +00:00
|
|
|
"""Canonical board-registry and Git-ref assignment contracts."""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import json
|
|
|
|
|
import shutil
|
|
|
|
|
import subprocess
|
|
|
|
|
import sys
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
from types import SimpleNamespace
|
|
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
SCRIPTS = Path(__file__).parents[2] / "services/hermes/scripts"
|
|
|
|
|
sys.path.insert(0, str(SCRIPTS))
|
|
|
|
|
|
|
|
|
|
import execution_pool_project as project # noqa: E402
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def git(*arguments: str, cwd: Path | None = None) -> str:
|
|
|
|
|
return subprocess.check_output(
|
|
|
|
|
["git", *arguments], cwd=cwd, text=True, stderr=subprocess.DEVNULL
|
|
|
|
|
).strip()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def registry(tmp_path, monkeypatch, board="metis", remote=None, base=None):
|
|
|
|
|
projects = tmp_path / "projects"
|
|
|
|
|
boards = tmp_path / "boards"
|
|
|
|
|
checkout = projects / board
|
|
|
|
|
checkout.mkdir(parents=True)
|
|
|
|
|
git("init", "-q", cwd=checkout)
|
|
|
|
|
git(
|
|
|
|
|
"remote", "add", "origin",
|
2026-09-01 20:43:33 -03:00
|
|
|
remote
|
|
|
|
|
or f"https://scm.bstein.dev/titan/{project.REPOSITORY_ALIASES.get(board, board)}.git",
|
2026-08-17 16:31:15 +00:00
|
|
|
cwd=checkout,
|
|
|
|
|
)
|
|
|
|
|
if base:
|
|
|
|
|
git(
|
|
|
|
|
"symbolic-ref", "refs/remotes/origin/HEAD", f"refs/remotes/origin/{base}",
|
|
|
|
|
cwd=checkout,
|
|
|
|
|
)
|
|
|
|
|
entry = boards / board
|
|
|
|
|
entry.mkdir(parents=True)
|
|
|
|
|
(entry / "board.json").write_text(
|
|
|
|
|
json.dumps({"slug": board, "default_workdir": str(checkout)})
|
|
|
|
|
)
|
|
|
|
|
monkeypatch.setattr(project, "PROJECT_ROOT", projects)
|
|
|
|
|
monkeypatch.setattr(project, "BOARD_ROOT", boards)
|
|
|
|
|
return checkout, entry / "board.json"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
|
"branch",
|
|
|
|
|
[
|
|
|
|
|
"feature/safe", "fix/safe", "chore/safe", "docs/safe", "test/safe",
|
|
|
|
|
"refactor/safe", "wt/t_deadbeef", "review/t_deadbeef", "hermes/safe",
|
|
|
|
|
"handoff/safe",
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
def test_every_established_feature_namespace_is_accepted(branch):
|
|
|
|
|
assert project.validate_branch(branch, feature=True) == branch
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
|
"branch",
|
|
|
|
|
["main", "unknown/safe", "../main", "feature/../../main", "feature/.hidden", "-bad", "é/safe", "x" * 201],
|
|
|
|
|
)
|
|
|
|
|
def test_ref_validation_denies_traversal_and_unreviewed_namespaces(branch):
|
|
|
|
|
with pytest.raises(project.ProjectPolicyError):
|
|
|
|
|
project.validate_branch(branch, feature=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_canonical_registry_resolves_repo_base_and_default_branch(tmp_path, monkeypatch):
|
|
|
|
|
checkout, _ = registry(tmp_path, monkeypatch, base="trunk")
|
|
|
|
|
assert project.resolve_project("metis") == (
|
2026-09-01 20:43:33 -03:00
|
|
|
"https://scm.bstein.dev/titan/metis.git", "trunk", checkout.resolve()
|
2026-08-17 16:31:15 +00:00
|
|
|
)
|
|
|
|
|
task = SimpleNamespace(id="t_deadbeef", branch_name="")
|
|
|
|
|
assert project.resolve_assignment("metis", task) == (
|
2026-09-01 20:43:33 -03:00
|
|
|
"https://scm.bstein.dev/titan/metis.git", "wt/t_deadbeef", "trunk"
|
2026-08-17 16:31:15 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize("board", ["cassandra", "metis", "soteria", "titan-iac"])
|
|
|
|
|
def test_every_atlas_project_uses_its_own_registry_checkout(tmp_path, monkeypatch, board):
|
|
|
|
|
registry(tmp_path, monkeypatch, board=board)
|
|
|
|
|
repo, base, _ = project.resolve_project(board)
|
2026-09-01 20:43:33 -03:00
|
|
|
repository = project.REPOSITORY_ALIASES.get(board, board)
|
|
|
|
|
assert repo == f"https://scm.bstein.dev/titan/{repository}.git"
|
2026-08-17 16:31:15 +00:00
|
|
|
assert base == "main"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_registered_project_without_checkout_keeps_its_own_repo_identity(
|
|
|
|
|
tmp_path, monkeypatch
|
|
|
|
|
):
|
|
|
|
|
checkout, _ = registry(tmp_path, monkeypatch, board="metis")
|
|
|
|
|
shutil.rmtree(checkout)
|
|
|
|
|
assert project.resolve_project("metis") == (
|
2026-09-01 20:43:33 -03:00
|
|
|
"https://scm.bstein.dev/titan/metis.git",
|
2026-08-17 16:31:15 +00:00
|
|
|
"main",
|
|
|
|
|
checkout.resolve(),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_registry_rejects_project_slug_too_long_for_an_atlas_repo(
|
|
|
|
|
tmp_path, monkeypatch
|
|
|
|
|
):
|
|
|
|
|
board = "m" * 101
|
|
|
|
|
checkout, _ = registry(tmp_path, monkeypatch, board=board)
|
|
|
|
|
shutil.rmtree(checkout)
|
|
|
|
|
with pytest.raises(project.ProjectPolicyError, match="repository identity"):
|
|
|
|
|
project.resolve_project(board)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_noncanonical_remote_head_falls_back_to_main(tmp_path, monkeypatch):
|
|
|
|
|
checkout, _ = registry(tmp_path, monkeypatch)
|
|
|
|
|
real_run = project._run_git
|
|
|
|
|
|
|
|
|
|
def run_git(workdir, *arguments):
|
|
|
|
|
if arguments[0] == "symbolic-ref":
|
|
|
|
|
return "heads/not-origin"
|
|
|
|
|
return real_run(workdir, *arguments)
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr(project, "_run_git", run_git)
|
|
|
|
|
assert project.resolve_project("metis") == (
|
2026-09-01 20:43:33 -03:00
|
|
|
"https://scm.bstein.dev/titan/metis.git",
|
2026-08-17 16:31:15 +00:00
|
|
|
"main",
|
|
|
|
|
checkout.resolve(),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_registry_rejects_slug_traversal_symlink_and_malformed_documents(tmp_path, monkeypatch):
|
|
|
|
|
_, board_file = registry(tmp_path, monkeypatch)
|
|
|
|
|
with pytest.raises(project.ProjectPolicyError, match="slug"):
|
|
|
|
|
project._read_board("../metis")
|
|
|
|
|
board_file.write_text("not-json")
|
|
|
|
|
with pytest.raises(project.ProjectPolicyError, match="malformed"):
|
|
|
|
|
project._read_board("metis")
|
|
|
|
|
board_file.write_text(json.dumps({"slug": "other", "default_workdir": "/tmp"}))
|
|
|
|
|
with pytest.raises(project.ProjectPolicyError, match="identity"):
|
|
|
|
|
project._read_board("metis")
|
|
|
|
|
board_file.unlink()
|
|
|
|
|
board_file.symlink_to("/etc/passwd")
|
|
|
|
|
with pytest.raises(OSError):
|
|
|
|
|
project._read_board("metis")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_registry_rejects_oversized_archived_and_non_regular_entry(tmp_path, monkeypatch):
|
|
|
|
|
_, board_file = registry(tmp_path, monkeypatch)
|
|
|
|
|
board_file.write_text("x" * (project.MAX_BOARD_BYTES + 1))
|
|
|
|
|
with pytest.raises(project.ProjectPolicyError, match="bounded"):
|
|
|
|
|
project._read_board("metis")
|
|
|
|
|
board_file.write_text(json.dumps({"slug": "metis", "archived": True}))
|
|
|
|
|
with pytest.raises(project.ProjectPolicyError, match="archived"):
|
|
|
|
|
project._read_board("metis")
|
|
|
|
|
board_file.unlink()
|
|
|
|
|
board_file.mkdir()
|
|
|
|
|
with pytest.raises(project.ProjectPolicyError, match="regular"):
|
|
|
|
|
project._read_board("metis")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
|
"remote",
|
|
|
|
|
[
|
2026-09-01 20:43:33 -03:00
|
|
|
"https://token@scm.bstein.dev/titan/metis.git",
|
2026-08-17 16:31:15 +00:00
|
|
|
"https://evil.example/atlas/metis.git",
|
2026-09-01 20:43:33 -03:00
|
|
|
"ssh://git@scm.bstein.dev/titan/metis.git",
|
2026-08-17 16:31:15 +00:00
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
def test_registry_rejects_credentialed_or_non_atlas_origin(tmp_path, monkeypatch, remote):
|
|
|
|
|
registry(tmp_path, monkeypatch, remote=remote)
|
|
|
|
|
with pytest.raises(project.ProjectPolicyError, match="origin"):
|
|
|
|
|
project.resolve_project("metis")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_registry_rejects_missing_or_outside_workdir(tmp_path, monkeypatch):
|
|
|
|
|
checkout, board_file = registry(tmp_path, monkeypatch)
|
|
|
|
|
board_file.write_text(json.dumps({"slug": "metis"}))
|
|
|
|
|
with pytest.raises(project.ProjectPolicyError, match="default_workdir"):
|
|
|
|
|
project.resolve_project("metis")
|
|
|
|
|
outside = tmp_path / "outside"
|
|
|
|
|
checkout.rename(outside)
|
|
|
|
|
board_file.write_text(
|
|
|
|
|
json.dumps({"slug": "metis", "default_workdir": str(outside)})
|
|
|
|
|
)
|
|
|
|
|
with pytest.raises(project.ProjectPolicyError, match="outside"):
|
|
|
|
|
project.resolve_project("metis")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_registry_rejects_non_directory_checkout(tmp_path, monkeypatch):
|
|
|
|
|
checkout, _ = registry(tmp_path, monkeypatch)
|
|
|
|
|
shutil.rmtree(checkout)
|
|
|
|
|
checkout.write_text("not a checkout")
|
|
|
|
|
with pytest.raises(project.ProjectPolicyError, match="not a directory"):
|
|
|
|
|
project.resolve_project("metis")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_git_metadata_failure_and_invalid_task_identity_fail_closed(tmp_path, monkeypatch):
|
|
|
|
|
checkout, _ = registry(tmp_path, monkeypatch)
|
|
|
|
|
with pytest.raises(project.ProjectPolicyError, match="metadata"):
|
|
|
|
|
project._run_git(checkout, "remote", "get-url", "missing")
|
|
|
|
|
with pytest.raises(project.ProjectPolicyError, match="identity"):
|
|
|
|
|
project.resolve_assignment("metis", SimpleNamespace(id="../../bad"))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_workspace_migration_boundary_is_explicit():
|
|
|
|
|
assert project.distributed_workspace_eligible(SimpleNamespace(workspace_path=""))
|
|
|
|
|
assert project.distributed_workspace_eligible(SimpleNamespace())
|
|
|
|
|
assert not project.distributed_workspace_eligible(
|
|
|
|
|
SimpleNamespace(workspace_path="/opt/data/workspace/live")
|
|
|
|
|
)
|