From 8ab77671cfe6f65702a6ef4e28cbeadbb3997d33 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Sat, 24 Oct 2026 22:25:33 -0400 Subject: [PATCH 1/3] Begin multiple inheritance batch 3: own-class isinstance checks Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013ejYrMLnLWah8wCDDcb57V From a982765875db799a4fae486a4f9fe94a429374ce Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Sat, 24 Oct 2026 22:27:38 -0400 Subject: [PATCH 2/3] Replace the own-class isinstance and CLI failure roster with declared capabilities ThreadRows.__eq__ asked isinstance of its own class and fell back to Mapping equality; it now returns NotImplemented for anything but rows, the comparison guard shape. CLI_FAILURES listed CoordinationError and GitCommandError by hand; both now inherit the CommandFailure capability, which the tuple names once. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013ejYrMLnLWah8wCDDcb57V --- src/agent_comms/cli_protocol.py | 5 ++--- src/agent_comms/coordination_errors.py | 6 +++++- src/agent_comms/registry_document.py | 5 +++-- src/agent_comms/repo.py | 3 ++- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/agent_comms/cli_protocol.py b/src/agent_comms/cli_protocol.py index dce5305db5..074b52cb70 100644 --- a/src/agent_comms/cli_protocol.py +++ b/src/agent_comms/cli_protocol.py @@ -27,14 +27,13 @@ from pydantic_core import CoreSchema from typing_extensions import TypeForm -from .coordination_errors import CoordinationError, CoordinationReadUnavailable +from .coordination_errors import CommandFailure, CoordinationReadUnavailable from .declared_family import DeclaredFamily, UnknownFamilyMember from .external_transcript import RequestedEarlierPage from .field_codec import FieldCodec, TimestampText from .maintenance_barrier import MaintenanceHeldError from .messages import Message from .recorded_time import UnrecordedTimeError -from .repo import GitCommandError from .turn_item import NativeTurnId, TurnItem from .wire_models import DeclaredWireFamily, WireModel @@ -43,7 +42,7 @@ from .cli_bridges.shared import BindingThread, CliBridge, DeliveryRow -CLI_FAILURES = (ValueError, OSError, CoordinationError, GitCommandError) +CLI_FAILURES = (ValueError, OSError, CommandFailure) async def raise_child_exit(child: AttachedChild, reason: str) -> NoReturn: diff --git a/src/agent_comms/coordination_errors.py b/src/agent_comms/coordination_errors.py index e5c56b2eca..a58ad79410 100644 --- a/src/agent_comms/coordination_errors.py +++ b/src/agent_comms/coordination_errors.py @@ -11,11 +11,15 @@ class DrainDeferred(Exception): # noqa: N818 - capability, not an outcome """A failure an inbox drain records and retries later instead of stopping on.""" +class CommandFailure(Exception): # noqa: N818 - capability, not an outcome + """A failure a command reports to its caller as a failed result instead of crashing.""" + + class DrainUnavailable(DrainDeferred): """A failure meaning the drain's store or coordinator is unavailable right now.""" -class CoordinationError(RuntimeError, DrainUnavailable): +class CoordinationError(RuntimeError, DrainUnavailable, CommandFailure): """Base class for fail-loud coordinator errors.""" def __reduce__(self): diff --git a/src/agent_comms/registry_document.py b/src/agent_comms/registry_document.py index 984b9fd4de..535e864927 100644 --- a/src/agent_comms/registry_document.py +++ b/src/agent_comms/registry_document.py @@ -295,9 +295,10 @@ def bind(self, binding: Callable[[Thread], Thread]) -> None: def __eq__(self, other: object) -> bool: """Rows still stored on both sides compare as their JSON; only rows that differ there, - or that either side wrote, are decoded, so a one-row edit never decodes the registry.""" + or that either side wrote, are decoded, so a one-row edit never decodes the registry. + Rows equal only rows: a plain mapping is a different value.""" if not isinstance(other, ThreadRows): - return super().__eq__(other) + return NotImplemented return self.keys() == other.keys() and all( (name in self._raw and name in other._raw and self._raw[name] == other._raw[name]) or self[name] == other[name] diff --git a/src/agent_comms/repo.py b/src/agent_comms/repo.py index 86330133da..2055d732d9 100644 --- a/src/agent_comms/repo.py +++ b/src/agent_comms/repo.py @@ -12,6 +12,7 @@ from pydantic import BaseModel, ConfigDict, InstanceOf, PlainSerializer, PlainValidator +from .coordination_errors import CommandFailure from .declared_family import DeclaredFamily from .errors import MissingSourceLine from .field_codec import FieldCodec, WireValue @@ -34,7 +35,7 @@ def founding(self, commander: str) -> tuple[Repo, ...]: ... def without_commander(self) -> None: ... -class GitCommandError(RuntimeError): +class GitCommandError(RuntimeError, CommandFailure): """A Git operation failure, not malformed order data to aggregate and continue past.""" def __init__(self, repository: str, run: subprocess.CompletedProcess[bytes]): From 387a8522f1e3589d9b77cd859942305786282a52 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Sat, 24 Oct 2026 22:31:03 -0400 Subject: [PATCH 3/3] Assert removed rows are empty, not equal to a plain dict Rows compare only to rows now; the test asked whether the document holds no thread rows, which is emptiness. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013ejYrMLnLWah8wCDDcb57V --- tests/test_registration_components.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_registration_components.py b/tests/test_registration_components.py index ce4599400b..1488a311f2 100644 --- a/tests/test_registration_components.py +++ b/tests/test_registration_components.py @@ -38,7 +38,7 @@ def test_edit_owns_turn_and_document_owns_later_changes(tmp_path, *, thread_regi document.unregister("renamed") document.begin_delete("renamed") assert document.remove("renamed") == () - assert document.threads == {} + assert not document.threads assert not list(tmp_path.iterdir())