diff --git a/src/agent_comms/commander_parity/__init__.py b/src/agent_comms/commander_parity/__init__.py index 610372fdef..528ebf57e5 100644 --- a/src/agent_comms/commander_parity/__init__.py +++ b/src/agent_comms/commander_parity/__init__.py @@ -1,12 +1,12 @@ -"""Parity declarations own matrix generation and the cutover decision. +"""The historical capability matrix and optional native workflow diagnostics. -Delete this transition package when both machines run the native commander. +The declared native phases own completion; this transition package never grants cutover. """ from __future__ import annotations from abc import ABC, abstractmethod -from collections.abc import Callable, Iterable +from collections.abc import Callable from dataclasses import dataclass from pathlib import Path from typing import TYPE_CHECKING, ClassVar @@ -15,7 +15,6 @@ if TYPE_CHECKING: from ..plan_phases import PlanPhase - from ..todos import Todo class ParityComparison(ABC): @@ -48,10 +47,6 @@ class ParityRow( phases: ClassVar[tuple[type[PlanPhase], ...]] comparison: ClassVar[Callable[[], ParityComparison]] = UnverifiedParity - @classmethod - def compare(cls, todos: Iterable[Todo]) -> ParityComparison: - return UnverifiedParity() - @classmethod def verify(cls) -> ParityComparison: """Run this declared workflow now; a run is not durable cutover authority.""" @@ -65,11 +60,6 @@ def rows(cls) -> tuple[type[ParityRow], ...]: raise ValueError("Parity declarations require unique positive row numbers") return rows - @classmethod - def done(cls, todos: Iterable[Todo]) -> bool: - history = tuple(todos) - return all(row.compare(history).passed for row in cls.rows()) - @classmethod def matrix(cls) -> str: """Render declared facts only; no mutable completion column lives here.""" diff --git a/src/agent_comms/commander_parity/measurements.py b/src/agent_comms/commander_parity/measurements.py deleted file mode 100644 index dc2910238b..0000000000 --- a/src/agent_comms/commander_parity/measurements.py +++ /dev/null @@ -1,103 +0,0 @@ -"""Observe measurement owners through the standing-rule delivery they supply.""" - -from __future__ import annotations - -from dataclasses import dataclass -from pathlib import Path -from sys import monitoring -from types import CodeType, MethodType - -from agent_comms.commander.config import CommanderConfig -from agent_comms.commander_parity import ParityComparison, UnverifiedParity -from agent_comms.comms import Comms -from agent_comms.private_path import FileRevision -from agent_comms.standing_rules import DiskLow, StandingRule -from agent_comms.storage_rules import StorageViolation -from agent_comms.todos import ProgressRead -from agent_comms.ui_model.unit_state import UnitState - - -@dataclass(frozen=True) -class RuleComparison(ParityComparison): - owner: str - current: tuple[str, ...] - native: tuple[str, ...] - calls: int - private_state_unchanged: bool - - @staticmethod - def state_files(directory: Path) -> tuple[tuple[str, FileRevision], ...]: - return tuple( - (str(path.relative_to(directory)), FileRevision.from_stat(path.stat())) - for path in sorted(directory.rglob("*")) - if path.is_file() - ) - - @property - def passed(self) -> bool: - return self.calls == 1 and self.current == self.native and self.private_state_unchanged - - def describe(self) -> str: - return ( - f"owner={self.owner}; native_calls={self.calls}; " - f"current_count={len(self.current)}; native_count={len(self.native)}; " - f"equal={self.current == self.native}; " - f"private_state_unchanged={self.private_state_unchanged}" - ) - - @classmethod - def read(cls, owner: type[StandingRule]) -> ParityComparison: - method = owner.scan_observed - if not isinstance(method, MethodType): - return UnverifiedParity( - f"{owner.__qualname__}.scan_observed is not a bound classmethod" - ) - code = method.__func__.__code__ - config = CommanderConfig.read(CommanderConfig.path()) - comms = Comms(config.root) - states = UnitState.current(comms, config.commander, ProgressRead()) - before = cls.state_files(config.state) - observed: list[tuple[StandingRule, ...]] = [] - - def observe(_code: CodeType, _offset: int, value: tuple[StandingRule, ...]) -> None: - observed.append(value) - - # Subscribe to the owner's real return event, not string-dispatched profile traffic. - monitoring.use_tool_id(monitoring.PROFILER_ID, __name__) - try: - monitoring.register_callback( - monitoring.PROFILER_ID, monitoring.events.PY_RETURN, observe - ) - monitoring.set_local_events(monitoring.PROFILER_ID, code, monitoring.events.PY_RETURN) - current = owner.broken(comms, config.commander, observed=states) - finally: - monitoring.free_tool_id(monitoring.PROFILER_ID) - if len(observed) != 1: - return UnverifiedParity( - f"Expected one {owner.__qualname__}.scan_observed call: {len(observed)}" - ) - native = tuple(rule for rule in observed[0] if rule.broke_after(0.0)) - return cls( - f"{owner.__module__}.{owner.__qualname__}", - tuple(rule.line() for rule in current), - tuple(rule.line() for rule in native), - len(observed), - before == cls.state_files(config.state), - ) - - -@dataclass(frozen=True) -class StorageComparison(ParityComparison): - units: ParityComparison - disk: ParityComparison - - @property - def passed(self) -> bool: - return self.units.passed and self.disk.passed - - def describe(self) -> str: - return f"units: {self.units.describe()}; disk: {self.disk.describe()}" - - @classmethod - def read(cls) -> StorageComparison: - return cls(RuleComparison.read(StorageViolation.scan_owner()), RuleComparison.read(DiskLow)) diff --git a/src/agent_comms/commander_parity/rows.py b/src/agent_comms/commander_parity/rows.py index bb7d9b6448..80c9c4e443 100644 --- a/src/agent_comms/commander_parity/rows.py +++ b/src/agent_comms/commander_parity/rows.py @@ -11,14 +11,12 @@ StrikeLimitCase, verify_order_progress, ) -from agent_comms.commander_parity.measurements import StorageComparison from agent_comms.commander_parity.story import StoryCase from agent_comms.commander_parity.verdicts import VerdictComparison from agent_comms.commander_parity.wording import ( ApplicationRunComparison, DecisionAlertsComparison, OrderWordingComparison, - PlainWordsComparison, UnitWordingComparison, ) from agent_comms.plan_phases import ( @@ -112,10 +110,6 @@ class PlainWordsParityRow(ParityRow): native = "`PlainText`, `StrikeReason`" phases = () - @classmethod - def verify(cls) -> ParityComparison: - return PlainWordsComparison.read() - class OrderWordingParityRow(ParityRow): number = 10 @@ -199,10 +193,6 @@ class StorageHygieneParityRow(ParityRow): native = "`StorageViolation.scan_observed`, `DiskLow.violations`; live `StorageRule` thresholds" phases = (ControlPlanePhase,) - @classmethod - def verify(cls) -> ParityComparison: - return StorageComparison.read() - class OrderOnlyContactParityRow(ParityRow): number = 20 diff --git a/src/agent_comms/commander_parity/wording.py b/src/agent_comms/commander_parity/wording.py index dec85ab7ad..22d020605b 100644 --- a/src/agent_comms/commander_parity/wording.py +++ b/src/agent_comms/commander_parity/wording.py @@ -5,7 +5,7 @@ import io import json import os -from collections.abc import Callable, Iterable +from collections.abc import Callable from contextlib import redirect_stderr, redirect_stdout from dataclasses import dataclass, replace from functools import partial @@ -15,7 +15,7 @@ from agent_comms.child_process import ProcessIdentity from agent_comms.commander.config import CommanderConfig -from agent_comms.commander.order import CommanderContext, OrderFile, refuse +from agent_comms.commander.order import CommanderContext, OrderFile from agent_comms.commander_parity import ParityComparison, UnverifiedParity from agent_comms.commander_parity.lifecycle import ( InstalledEvidenceCase, @@ -75,62 +75,23 @@ def messages(context: CommanderContext) -> tuple[Message, ...]: @dataclass(frozen=True) -class WordingComparison(ParityComparison): - observations: tuple[tuple[WordingOutcome, WordingOutcome], ...] - native_owner: ClassVar[type[OrderWords]] +class OrderWordingComparison(ParityComparison): + """Compare distinct CLI and native request boundaries, never one constructor with itself.""" - @staticmethod - def agrees(current: WordingOutcome, native: WordingOutcome) -> bool: - return current == native + observations: tuple[tuple[WordingOutcome, WordingOutcome], ...] + native_owner: ClassVar[type[OrderWords]] = OrderText @property def passed(self) -> bool: - return bool(self.observations) and all(self.agrees(a, b) for a, b in self.observations) + return bool(self.observations) and all( + current.accepted == native.accepted for current, native in self.observations + ) def describe(self) -> str: return ( f"observed={len(self.observations)}; " - f"mismatches={sum(not self.agrees(a, b) for a, b in self.observations)}; " - f"current_refusals={sum(not a.accepted for a, _ in self.observations)}; " - f"native_refusals={sum(not b.accepted for _, b in self.observations)}; " - f"native={self.native_owner.__module__}.{self.native_owner.__qualname__}; " - "compared actual refusal and warning results; no publication" - ) - - @classmethod - def compare_texts(cls, texts: Iterable[str]) -> ParityComparison: - observed = tuple( - ( - WordingOutcome.observe(partial(refuse, text, cls.native_owner)), - WordingOutcome.observe(partial(cls.native_owner, text)), - ) - for text in texts - ) - return cls(observed) if observed else UnverifiedParity("No actual wording input exists.") - - -class PlainWordsComparison(WordingComparison): - native_owner = PlainText - - @classmethod - def read(cls) -> ParityComparison: - return cls.compare_texts( - strike.reason for todo in history(selected_context()) for strike in todo.strikes - ) - - -class OrderWordingComparison(WordingComparison): - native_owner = OrderText - - @staticmethod - def agrees(current: WordingOutcome, native: WordingOutcome) -> bool: - # This row asks whether soft orders are refused, not whether surrounding - # CLI prose emits the same unrelated wording notes as the native request. - return current.accepted == native.accepted - - def describe(self) -> str: - return super().describe() + ( - f"; current={OrderFile.problems_found.__qualname__}; " + f"mismatches={sum(a.accepted != b.accepted for a, b in self.observations)}; " + f"current={OrderFile.problems_found.__qualname__}; " f"native_entry={OrderGoalAction.require_ready.__qualname__}" ) diff --git a/src/agent_comms/phase_evidence.py b/src/agent_comms/phase_evidence.py index 7e58b6d4d7..51d3e8d67f 100644 --- a/src/agent_comms/phase_evidence.py +++ b/src/agent_comms/phase_evidence.py @@ -19,7 +19,6 @@ from pathlib import Path from typing import TYPE_CHECKING, ClassVar -from agent_comms.commander_parity import ParityRow from agent_comms.field_codec import FieldCodec from agent_comms.head_review import HeadReview from agent_comms.pinned_audit import ForeignAbsenceProbe, GodFunction, ParsedModule @@ -351,14 +350,8 @@ def describe(self) -> str: @dataclass(frozen=True) -class ParityEvidence(PhaseEvidence): - """The native/current comparison rows, which own the cutover decision.""" - - def holds(self, reading: PlanReading) -> bool: - return ParityRow.done(reading.todos) - - def describe(self) -> str: - return "every parity row passes" +class ParityEvidence(AllEvidence): + """Native owners supply the facts; the historical comparison matrix cannot overrule them.""" @dataclass(frozen=True) diff --git a/src/agent_comms/plan_phases.py b/src/agent_comms/plan_phases.py index 24fa581c95..a918a7214f 100644 --- a/src/agent_comms/plan_phases.py +++ b/src/agent_comms/plan_phases.py @@ -193,18 +193,6 @@ class ControlPlanePhase(CommanderNativePhase): ) -class ParityPhase(CommanderNativePhase): - after = (RecordPlanePhase, ReviewQueuePhase, ControlPlanePhase) - evidence = ParityEvidence() - - -class CutoverPhase(CommanderNativePhase): - """The parity gate passes and the machine-wide commander configuration is gone.""" - - after = (RecordPlanePhase, ReviewQueuePhase, ControlPlanePhase, ParityPhase) - evidence = AllEvidence((ParityEvidence(), PatternAbsent(r"\bCommanderConfig\b"))) - - OUTSIDE_PARITY = ("src", ":!src/agent_comms/commander_parity") @@ -234,6 +222,20 @@ class CommanderPagePhase(CommanderNativePhase): ) +class ParityPhase(CommanderNativePhase): + """The native capability owners, not a second matrix of demonstrations, decide completion.""" + + after = (RecordPlanePhase, ReviewQueuePhase, ControlPlanePhase, CommanderPagePhase) + evidence = ParityEvidence(tuple(phase.evidence for phase in after)) + + +class CutoverPhase(CommanderNativePhase): + """The native evidence passes and the machine-wide commander configuration is gone.""" + + after = (ParityPhase,) + evidence = AllEvidence((ParityPhase.evidence, PatternAbsent(r"\bCommanderConfig\b"))) + + class SkillPhase(CommanderNativePhase): """The skill names no script.""" diff --git a/tests/test_commander_parity.py b/tests/test_commander_parity.py index 538aed0f9c..c86fb28773 100644 --- a/tests/test_commander_parity.py +++ b/tests/test_commander_parity.py @@ -1,16 +1,10 @@ from pathlib import Path -from agent_comms.commander_parity import ParityComparison, ParityRow +from agent_comms.commander_parity import ParityRow PLAN = Path(__file__).resolve().parents[1] / "plans/commander-native-plan.md" -class MatchingComparison(ParityComparison): - passed = True - - - - def test_generated_prose_is_not_input_to_the_gate(tmp_path): plan = tmp_path / "plan.md" plan.write_text( @@ -21,4 +15,3 @@ def test_generated_prose_is_not_input_to_the_gate(tmp_path): assert rendered.startswith("Before\n## Parity matrix\n\n") assert rendered.endswith("**Dropped deliberately:** historical text\nAfter\n") assert "Hand edited" not in rendered - assert not ParityRow.done(()) diff --git a/tests/test_commander_parity_measurements.py b/tests/test_commander_parity_measurements.py deleted file mode 100644 index ecc8fa48ad..0000000000 --- a/tests/test_commander_parity_measurements.py +++ /dev/null @@ -1,42 +0,0 @@ -"""Comparison invariants; configured live-owner proof is the --verify application run.""" - -from dataclasses import replace - -from agent_comms.commander_parity.measurements import RuleComparison, StorageComparison -from agent_comms.commander_parity.rows import ( - LoadForkingParityRow, - MeasuredLoadParityRow, - UnitTimeParityRow, -) - - -def test_measurement_family_requires_native_calls_exact_delivery_and_no_private_write(): - matched = RuleComparison("owner", ("delivered",), ("delivered",), 1, True) - assert matched.passed - for changed in ( - replace(matched, calls=0), - replace(matched, calls=2), - replace(matched, current=("different",)), - replace(matched, native=()), - replace(matched, private_state_unchanged=False), - ): - assert not changed.passed - assert not StorageComparison(changed, matched).passed - assert not StorageComparison(matched, changed).passed - assert StorageComparison(matched, matched).passed - missing = UnitTimeParityRow.verify() - assert not missing.passed - assert UnitTimeParityRow.native in missing.describe() - - -def test_a_load_report_does_not_prove_automatic_forking_or_self_report_comparison(): - for row in (LoadForkingParityRow, MeasuredLoadParityRow): - assert not row.verify().passed - - -def test_storage_comparison_notices_private_state_changes(tmp_path): - path = tmp_path / "private-state" - path.write_text("before") - before = RuleComparison.state_files(tmp_path) - path.write_text("after") - assert RuleComparison.state_files(tmp_path) != before