diff --git a/src/agent_comms/historical_views.py b/src/agent_comms/historical_views.py index fc4927035d..cf07533b34 100644 --- a/src/agent_comms/historical_views.py +++ b/src/agent_comms/historical_views.py @@ -409,13 +409,24 @@ def __init__(self, bus_path: Path): @classmethod def files_in(cls, root: Path) -> frozenset[Path]: + """Archived roots are found by where attach puts them, never by decoding the + catalogue: a copy of a root in an older shape must copy before any carry reads it.""" declared = super().files_in(root) - for source in cls(Bus.path_in(root)).sources(): - archived = Path(source.root) - if archived.is_relative_to(root): - declared |= RootFile.paths_in(archived) + for archived in cls.archived_roots(root): + declared |= RootFile.paths_in(archived) return declared + @staticmethod + def archive_directory(root: Path) -> Path: + return root / "history" + + @classmethod + def archived_roots(cls, directory: Path) -> tuple[Path, ...]: + archives = cls.archive_directory(directory) + if not archives.is_dir(): + return () + return tuple(sorted(path for path in archives.iterdir() if path.is_dir())) + def sources(self) -> tuple[HistorySource, ...]: with self.read_sources() as sources: return sources @@ -463,7 +474,7 @@ def attach(self, source_root: Path, *, source_read: HistorySource | None = None) existing = next((s for s in sources if s.original_root == str(source_root)), None) if existing is not None: return existing - parent = self.bus_path.parent / "history" + parent = self.archive_directory(self.bus_path.parent) parent.mkdir(mode=0o700, exist_ok=True) stage = Path(tempfile.mkdtemp(prefix="source-", dir=parent)) try: diff --git a/src/agent_comms/locked_store.py b/src/agent_comms/locked_store.py index eba890b9b7..d6076ac4af 100644 --- a/src/agent_comms/locked_store.py +++ b/src/agent_comms/locked_store.py @@ -162,6 +162,21 @@ def existing_in(cls, root: Path) -> dict[Path, type[Self]]: return {path: owner for owner, directory in cls.declarations_in(root) for path in owner.matching_in(directory)} + @classmethod + def archived_roots(cls, directory: Path) -> tuple[Path, ...]: + """Whole bus roots this declaration keeps as immutable copies under ``directory``.""" + return () + + @classmethod + def primaries_in(cls, root: Path) -> dict[Path, type[Self]]: + """Existing declared files under ``root`` and every root archived in it, each with + its own owner, so an archived store's companions stay with that store.""" + primaries = cls.existing_in(root) + for owner, directory in RootFile.declarations_in(root): + for archived in owner.archived_roots(directory): + primaries.update(cls.primaries_in(archived)) + return primaries + @classmethod def owned_files( cls, root: Path, paths: Collection[Path], @@ -172,7 +187,7 @@ def owned_files( undeclared existing file acquires content behavior without inventing a store declaration for it. """ - primaries = cls.existing_in(root) + primaries = cls.primaries_in(root) declarations = {member: owner for owner, directory in cls.declarations_in(root) for member in owner.files_in(directory)} declarations.update(primaries) diff --git a/tests/conftest.py b/tests/conftest.py index 5e6dc8fbe7..b43329bc5b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -239,16 +239,13 @@ def without_mutation_guards(db: sqlite3.Connection, table: type[TypedTable]) -> @pytest.fixture -def scratch_root(tmp_path_factory): +def scratch_root(durable_home): + """The test's own agent-scratch, derived from the storage rule under the test's HOME.""" from agent_comms.storage_rules import StorageRule - base = tmp_path_factory.getbasetemp() - scratch = StorageRule().scratch_base.resolve() - if not base.resolve().is_relative_to(scratch): - base = scratch / "pytest" - base.mkdir(parents=True, exist_ok=True) - with tempfile.TemporaryDirectory(prefix="check-root-", dir=base) as directory: - yield Path(directory) + root = StorageRule().scratch_base / "pytest" / "check-root" + root.mkdir(parents=True) + return root @pytest.fixture