Files
roboco/tests/unit/runtime/test_transcript_retention.py
50742d9876 fix(retention): match the encoded workspaces root at a path boundary (#227)
is_agent_owned_dir used a bare startswith on the encoded workspaces root, so a
sibling root that shares the prefix (e.g. -data-workspaces2 vs -data-workspaces)
was misclassified as agent-owned and its operator transcripts pruned. Require an
exact match or a '-' path-boundary prefix.

Supersedes #221 (the same fix from antfleet-ops, who flagged the over-match);
re-done here as our own change so it can land without the contributor CLA.

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
2026-06-19 20:57:02 +02:00

88 lines
3.4 KiB
Python

"""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
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
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()) == []