2026-06-12 23:11:01 +02:00
|
|
|
"""Tests for transcript-retention selection — what the prune deletes, safely.
|
|
|
|
|
|
|
|
|
|
The prune is destructive (it unlinks files on the operator's real ~/.claude), so
|
|
|
|
|
the selection logic is proven here against a temp dir before it ever runs live.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import os
|
|
|
|
|
import time
|
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
|
|
|
|
|
|
from roboco.runtime.transcript_retention import (
|
|
|
|
|
is_agent_owned_dir,
|
|
|
|
|
select_prunable_transcripts,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if TYPE_CHECKING:
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
WORKSPACES = "/data/workspaces"
|
|
|
|
|
_DAY = 86400
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _touch(path: Path, age_days: float) -> Path:
|
|
|
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
path.write_text("{}\n")
|
|
|
|
|
mtime = time.time() - age_days * _DAY
|
|
|
|
|
os.utime(path, (mtime, mtime))
|
|
|
|
|
return path
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_is_agent_owned_dir_matches_app_and_workspaces() -> None:
|
|
|
|
|
assert is_agent_owned_dir("-app", WORKSPACES) is True
|
|
|
|
|
assert is_agent_owned_dir("-data-workspaces-roboco-be-dev-1", WORKSPACES) is True
|
|
|
|
|
# The operator's own sessions are never agent-owned.
|
|
|
|
|
assert is_agent_owned_dir("-Users-renzof-Documents-foo", WORKSPACES) is False
|
|
|
|
|
assert is_agent_owned_dir("-home-renzof-code", WORKSPACES) is False
|
|
|
|
|
|
|
|
|
|
|
2026-06-19 20:57:02 +02:00
|
|
|
def test_is_agent_owned_dir_rejects_sibling_root_prefixes() -> None:
|
|
|
|
|
# A sibling workspace root that merely shares the encoded prefix without a
|
|
|
|
|
# "-" boundary must NOT be treated as agent-owned, else operator transcripts
|
|
|
|
|
# under it get pruned. Encoded root here is "-data-workspaces".
|
|
|
|
|
assert is_agent_owned_dir("-data-workspaces", WORKSPACES) is True # exact
|
|
|
|
|
assert is_agent_owned_dir("-data-workspaces2", WORKSPACES) is False
|
|
|
|
|
assert is_agent_owned_dir("-data-workspaces2-project", WORKSPACES) is False
|
|
|
|
|
assert is_agent_owned_dir("-data-workspacesBackup-x", WORKSPACES) is False
|
|
|
|
|
|
|
|
|
|
|
2026-06-12 23:11:01 +02:00
|
|
|
def test_is_agent_owned_dir_handles_root_slash_safely() -> None:
|
|
|
|
|
# A pathological "/" workspaces root must not make every dir agent-owned.
|
|
|
|
|
assert is_agent_owned_dir("-Users-renzof-secret", "/") is False
|
|
|
|
|
assert is_agent_owned_dir("-app", "/") is True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_select_prunes_only_old_agent_transcripts(tmp_path: Path) -> None:
|
|
|
|
|
projects = tmp_path / "projects"
|
|
|
|
|
old_app = _touch(projects / "-app" / "old.jsonl", age_days=30)
|
|
|
|
|
fresh_app = _touch(projects / "-app" / "fresh.jsonl", age_days=1)
|
|
|
|
|
old_ws = _touch(
|
|
|
|
|
projects / "-data-workspaces-roboco-be-dev-1" / "old.jsonl", age_days=30
|
|
|
|
|
)
|
|
|
|
|
# The operator's own old session — MUST be preserved.
|
|
|
|
|
human_old = _touch(
|
|
|
|
|
projects / "-Users-renzof-Documents-thing" / "old.jsonl", age_days=99
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
cutoff = time.time() - 14 * _DAY
|
|
|
|
|
prunable = set(select_prunable_transcripts(projects, WORKSPACES, cutoff))
|
|
|
|
|
|
|
|
|
|
assert old_app in prunable
|
|
|
|
|
assert old_ws in prunable
|
|
|
|
|
assert fresh_app not in prunable # too new
|
|
|
|
|
assert human_old not in prunable # operator's own session — never pruned
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_select_ignores_non_jsonl_and_directories(tmp_path: Path) -> None:
|
|
|
|
|
projects = tmp_path / "projects"
|
|
|
|
|
_touch(projects / "-app" / "keep.txt", age_days=30) # not a transcript
|
|
|
|
|
(projects / "-app" / "subdir.jsonl").mkdir(parents=True) # a dir named like one
|
|
|
|
|
cutoff = time.time() - 14 * _DAY
|
|
|
|
|
assert select_prunable_transcripts(projects, WORKSPACES, cutoff) == []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_select_handles_missing_projects_root(tmp_path: Path) -> None:
|
|
|
|
|
assert select_prunable_transcripts(tmp_path / "nope", WORKSPACES, time.time()) == []
|