From d3d57698dcf65c5a3503abe63d51b549763e7792 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Thu, 22 Oct 2026 23:32:30 -0400 Subject: [PATCH 1/2] Judge roster phase by internal declarations and retain producer ABI --- src/agent_comms/phase_evidence.py | 5 ++++- src/agent_comms/plan_phases.py | 18 ++++++++++++++--- tests/test_phase_evidence.py | 33 ++++++++++++++++++++++++++++++- 3 files changed, 51 insertions(+), 5 deletions(-) diff --git a/src/agent_comms/phase_evidence.py b/src/agent_comms/phase_evidence.py index 18289353ab..672cc84c8f 100644 --- a/src/agent_comms/phase_evidence.py +++ b/src/agent_comms/phase_evidence.py @@ -497,6 +497,7 @@ class ClassFlagsAbsent(MainEvidence): pattern: str paths: tuple[str, ...] = ("src",) + class_names: tuple[str, ...] = () def read(self, main: MainTree) -> bool: matched = main.matching_lines(self.pattern, self.paths) @@ -504,11 +505,13 @@ def read(self, main: MainTree) -> bool: (path, statement.lineno) in matched for path in {path for path, _ in matched} for declared in Classes.of((main.module(path).tree,)) + if not self.class_names or declared.name in self.class_names for statement in declared.body ) def describe(self) -> str: - return f"no class body line {self.pattern!r} in {' '.join(self.paths)} on main" + owners = f" of {', '.join(self.class_names)}" if self.class_names else "" + return f"no class body line {self.pattern!r}{owners} in {' '.join(self.paths)} on main" class NamedClasses(ast.NodeVisitor): diff --git a/src/agent_comms/plan_phases.py b/src/agent_comms/plan_phases.py index 1c49d3b01b..6f69c8bbad 100644 --- a/src/agent_comms/plan_phases.py +++ b/src/agent_comms/plan_phases.py @@ -652,9 +652,21 @@ class KindFamiliesPhase(RemainingSlopPhase): class RostersPhase(RemainingSlopPhase): - """6.4: rosters beside families are derived from the family.""" - - evidence = PatternAbsent(r"PI_0_85_1_EVENT_KINDS|THREAD_CAPABILITIES|serves_new_threads") + """6.4: remove the known internal registries; external producer ABI stays independent.""" + + evidence = AllEvidence(( + PatternAbsent(r"^\s*THREAD_CAPABILITIES\s*[:=]"), + ClassFlagsAbsent(r"^\s*serves_(new_)?threads\s*[:=]"), + *(ClassFlagsAbsent(r"^\s*" + attribute + r"\s*[:=]", class_names=(owner,)) + for owner, attribute in ( + ("Carry", "declared"), + ("ThreadRename", "kinds"), + ("BirthlessIncarnation", "members"), + ("CliToolArguments", "types"), + ("PiTurnPhase", "_start_phases"), + ("PiTurnPhase", "_end_events"), + )), + )) class RecordsPhase(RemainingSlopPhase): diff --git a/tests/test_phase_evidence.py b/tests/test_phase_evidence.py index 480f9c2826..b4b84e2089 100644 --- a/tests/test_phase_evidence.py +++ b/tests/test_phase_evidence.py @@ -1,4 +1,5 @@ import subprocess +from pathlib import Path from agent_comms.field_codec import FieldCodec from agent_comms.head_review import HeadReview @@ -27,7 +28,7 @@ UnreadableEvidenceError, ) from agent_comms.pinned_audit import GodFunction -from agent_comms.plan_phases import FLAG, ExternalBoundariesPhase, PlanPhase +from agent_comms.plan_phases import FLAG, ExternalBoundariesPhase, PlanPhase, RostersPhase from agent_comms.repo import Repo from agent_comms.repository_name import RepositoryName @@ -100,6 +101,36 @@ def main_of(tmp_path, files): Repo(checkout=tmp_path, github=RepositoryName.from_wire("OpenHCSDev/agent-comms")))) +def test_roster_evidence_keeps_external_abi_and_each_remaining_internal_registry(tmp_path): + source = Path(__file__).parents[1] / "src/agent_comms" + reading = main_of(tmp_path, { + "src/phase_evidence.py": (source / "phase_evidence.py").read_text(), + "src/plan_phases.py": (source / "plan_phases.py").read_text(), + "src/producer.py": "PI_0_85_1_EVENT_KINDS = frozenset({'alpha', 'beta'})\n" + "class Runtime:\n event_kinds = PI_0_85_1_EVENT_KINDS\n", + "src/unrelated.py": "class OtherOwner:\n members = {}\n" + "def local():\n types = {}\n", + }) + assert reading.holds(RostersPhase.evidence) + for remaining in ( + "THREAD_CAPABILITIES: tuple[type, ...] = (Steers, Compacts)\n", + "class Backend:\n serves_threads = True\n", + "class Backend:\n serves_new_threads: ClassVar[bool] = True\n", + "class Carry:\n declared: ClassVar[dict] = {}\n", + "class ThreadRename:\n kinds = {}\n", + "class BirthlessIncarnation:\n members = {}\n", + "class CliToolArguments:\n types = {}\n", + "class PiTurnPhase:\n _start_phases = {}\n", + "class PiTurnPhase:\n _end_events = {}\n", + ): + (tmp_path / "src/remaining.py").write_text(remaining) + git(tmp_path, "add", ".") + git(tmp_path, "-c", "user.name=t", "-c", "user.email=t@t", "commit", "-qm", "registry") + git(tmp_path, "update-ref", "refs/remotes/origin/main", git(tmp_path, "rev-parse", "HEAD")) + changed = PlanReading((), MainTree.of(reading.main.repo)) + assert not changed.holds(RostersPhase.evidence), remaining + + def test_source_shape_evidence_reads_declarations_on_main(tmp_path): copy = " def __init__(self, path):\n self.path = path\n" reading = main_of(tmp_path, { From 98a2305f119de92ba64a3f3220031fc33bf17c70 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Thu, 22 Oct 2026 23:35:22 -0400 Subject: [PATCH 2/2] Keep committed-tree grep within its repository owner --- src/agent_comms/phase_evidence.py | 9 +++++++-- tests/test_phase_evidence.py | 20 ++++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/agent_comms/phase_evidence.py b/src/agent_comms/phase_evidence.py index 672cc84c8f..2ac4cbbdff 100644 --- a/src/agent_comms/phase_evidence.py +++ b/src/agent_comms/phase_evidence.py @@ -43,8 +43,13 @@ def has(self, path: str) -> bool: return self.git("cat-file", "-e", f"{self.commit}:{path}").returncode == 0 def grep(self, *options: str, pattern: str, paths: tuple[str, ...]) -> list[str]: - """``git grep -E`` rows under ``paths``; git pathspecs such as ``:!file`` exclude.""" - run = self.git("grep", *options, "-E", "-e", pattern, self.commit, "--", *paths) + """``git grep -E`` rows in this tree, like ``module`` and ``modules``. + + A submodule is another repository's tree, regardless of local recurse settings. + Git pathspecs such as ``:!file`` exclude. + """ + run = self.git("grep", "--no-recurse-submodules", *options, + "-E", "-e", pattern, self.commit, "--", *paths) if run.returncode not in (0, 1): run.check_returncode() return run.stdout.splitlines() diff --git a/tests/test_phase_evidence.py b/tests/test_phase_evidence.py index b4b84e2089..35e70ab1a8 100644 --- a/tests/test_phase_evidence.py +++ b/tests/test_phase_evidence.py @@ -131,6 +131,26 @@ def test_roster_evidence_keeps_external_abi_and_each_remaining_internal_registry assert not changed.holds(RostersPhase.evidence), remaining +def test_main_tree_grep_uses_the_same_repository_tree_as_its_module_reader(tmp_path): + dependency = tmp_path / "dependency" + dependency.mkdir() + main_of(dependency, {"src/debt.py": "class Carry:\n declared = {}\n"}) + checkout = tmp_path / "owner" + checkout.mkdir() + reading = main_of(checkout, {"src/owner.py": "class OtherOwner:\n members = {}\n"}) + git(checkout, "-c", "protocol.file.allow=always", "submodule", "add", "-q", + str(dependency), "src/dependency") + git(checkout, "-c", "user.name=t", "-c", "user.email=t@t", "commit", "-qam", "dependency") + git(checkout, "update-ref", "refs/remotes/origin/main", git(checkout, "rev-parse", "HEAD")) + git(checkout, "config", "submodule.recurse", "true") + assert "src/dependency" in git(checkout, "grep", "--recurse-submodules", "-n", + "-e", "declared", "HEAD", "--", "src") + current = PlanReading((), MainTree.of(reading.main.repo)) + assert current.holds(RostersPhase.evidence) + assert not current.main.matching_lines(r"declared", ("src",)) + assert len(tuple(current.main.modules(("src",)))) == 1 + + def test_source_shape_evidence_reads_declarations_on_main(tmp_path): copy = " def __init__(self, path):\n self.path = path\n" reading = main_of(tmp_path, {