From 81c9c559b83219f6dedb888e6709f0ebabe10ff6 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 13:43:55 -0400 Subject: [PATCH 01/12] Make commander cutover precede presentation phases --- docs/refactor/shared-plan-claims-A.md | 127 ++++++++++++++++++++++++++ src/agent_comms/plan_phases.py | 22 +++-- tests/test_plan_phases.py | 57 ++++++++++++ 3 files changed, 198 insertions(+), 8 deletions(-) create mode 100644 docs/refactor/shared-plan-claims-A.md create mode 100644 tests/test_plan_phases.py diff --git a/docs/refactor/shared-plan-claims-A.md b/docs/refactor/shared-plan-claims-A.md new file mode 100644 index 0000000000..6ac3765e76 --- /dev/null +++ b/docs/refactor/shared-plan-claims-A.md @@ -0,0 +1,127 @@ +# Shared plan claims: A dependency checkpoint + +Owner: pi-plan-claims. Branch: feat/shared-plan-claims. Base: 011084fc5. +This checkpoint changes commander-native dependency declarations only. Shared +claims are the next, separate commit. Nothing is installed or merged here. + +## Change and limit + +`CutoverPhase.after` now names `RecordPlanePhase`, `ReviewQueuePhase` and +`ControlPlanePhase`. `StoryPhase` and `CommanderPagePhase` follow cutover, whose +prerequisites own their source records. No phase depends on either view. +`SkillPhase` continues to follow cutover. + +**There is no declared executable commander parity gate.** The three requirements +in `plans/commander-native-plan.md:199-203` are prose: native owners on main, a +shadow session comparing native behaviour to scripts, and no private-state writes. +No phase or check implements that comparison. `PlanPhase.done()` reads order +closure, not parity. This change does not satisfy or encode the missing parity +prerequisite and does not authorize deleting the scripts. + +Consequently there is no real parity-check output from which to enumerate failing +matrix rows for phases 3, 4 and 7, map each to a check class, or assign each +measured failure. Those results are **unavailable**, not passing. The commander +must identify an existing external check if one was intended, or commission its +implementation separately. No substitute parity check was invented in A. + +## Actual Core output, before and after + +Both reads used this checkout's `Todo.select`, `PlanPhase.ready` and +`PlanPhase.ordered` against a SQLite read-only connection to +`/home/ts/.agent-comms/todos.sqlite3`, selecting `creator='claude-code'`. Each +returned 214 retained orders. No synthetic completion state was used here. +This is the proposed Core source evaluating real retained state, not an installed +runtime update. "Next startable" is the same ready-and-not-ordered predicate +used by `PlanIdle.violations`. + +Before: + +```text +BEFORE: live commander claude-code; todos: 214 +Core ready: record_plane, commander_page +Core next startable (ready, no open order): commander_page +record_plane done= False ordered= True after= retained_store open= P107,P167,P195,P197,P198,P204,P208,P213 +review_queue done= True ordered= False after= record_plane open= +story done= True ordered= False after= review_queue open= +commander_page done= False ordered= False after= story open= +control_plane done= False ordered= True after= record_plane open= P100,P199 +cutover done= False ordered= True after= review_queue,story,commander_page,control_plane open= P207 +skill done= False ordered= False after= cutover open= +``` + +After: + +```text +AFTER: live commander claude-code; todos: 214 +Core ready: record_plane +Core next startable (ready, no open order): (none) +record_plane done= False ordered= True after= retained_store open= P107,P167,P195,P197,P198,P204,P208,P213 +review_queue done= True ordered= False after= record_plane open= +control_plane done= False ordered= True after= record_plane open= P100,P199 +cutover done= False ordered= True after= record_plane,review_queue,control_plane open= P207 +story done= True ordered= False after= cutover open= +commander_page done= False ordered= False after= cutover open= +skill done= False ordered= False after= cutover open= +``` + +The retained state contradicts the initial description that nobody started the +story: Core reports `story` and `review_queue` done. Its unfinished native work +is in record plane and control plane. These are order-state observations, not +parity failures. + +## Source ownership evidence + +Reused refactor-audit's `audit.findings.Package.load` AST substrate at the base: +379 production modules, 476 test modules and 34 tool modules parsed, no omissions. +The first attempt to use NRA's parser could not import `tree_sitter` in the +available Python environment; the existing refactor-audit parser succeeded. + +- `plan_phases.py` alone declares phase classes, dependency writes, completion, + readiness and ordering decisions. No second dependency list was introduced. +- Production consumers: `standing_rules.py` (`PlanIdle`), `todos.py` + (`ProblemOrder.serves` and serve command), `tools.py` (serve tool), + `commander/order.py` (order parsing and serve verb). +- Test consumers at the base: `test_standing_rules.py` and `test_tool_replies.py`. +- Before: cutover depended on story and commander page; page depended on story. + After: cutover depends on the three native phases; both views depend on cutover; + no dependency names either view. Wire names and all consumer APIs are unchanged. +- Dynamic membership is derived through `DeclaredFamily.members_with`; static + name enumeration does not prove absence of arbitrary external runtime mutation. +- Search for `parity` across production, tests, tools and CI found no commander + comparison check. Unrelated Pi prompt-byte and entry-index parity tests do not + compare commander behaviour. + +## Verification + +The existing native test environment for this checkout was verified and reused; +no runtime package was built or installed. One sequential, bounded test run: + +```text +PYTHONPATH=src timeout 120 /home/ts/code/projects/agent-comms/.venv/bin/python -X faulthandler -m pytest --native-cache /home/ts/.cache/agent-comms/native -n 0 --no-cov -p no:cacheprovider -q tests/test_plan_phases.py tests/test_standing_rules.py -k 'cutover_waits or plan_idle' +2 passed, 16 deselected, 14 warnings in 4.63s +``` + +The new family test uses real checked orders and the serve tool. It verifies +cutover waits for each native prerequisite independently, unfinished views cannot +block cutover, and the views and skill become ready after cutover. The existing +PlanIdle test still passes. Warnings were an existing unknown pytest timeout +option and pytest cleanup trying to retire unrelated busy DQDock snapshot paths; +no manual cleanup of those paths was attempted. + +`/home/ts/bin/agent-resource-check` is absent on this machine. Before this small +run, available RAM was 3.7 GiB and disk free was 26 GiB. No parallel workers or +large build was launched. + +## Next identity choice + +B will use one shared phase family with an unnamed parent per plan. Existing +commander-native phase names remain stable; the remaining-slop declarations get +unambiguous names derived from their classes. A plan parent groups discovery and +rendering without copying phase readiness/claim behaviour into a second family. +Markdown/board references identify declarations, never titles or new UUIDs. + +Assumed: the missing parity comparison is reported, not invented; its absence +blocks parity acceptance and script deletion, but does not prevent reviewing this +dependency-only checkpoint. Both machines can write the approved shared claim +store. pi-cn-phase7 owns dispatch and will propose any checked-task capability; +this checkpoint does not fabricate runnable task bodies from phase names. diff --git a/src/agent_comms/plan_phases.py b/src/agent_comms/plan_phases.py index 2a78e0159f..0a58d54706 100644 --- a/src/agent_comms/plan_phases.py +++ b/src/agent_comms/plan_phases.py @@ -82,20 +82,26 @@ class ReviewQueuePhase(PlanPhase): after = (RecordPlanePhase,) -class StoryPhase(PlanPhase): - after = (ReviewQueuePhase,) +class ControlPlanePhase(PlanPhase): + after = (RecordPlanePhase,) -class CommanderPagePhase(PlanPhase): - after = (StoryPhase,) +class CutoverPhase(PlanPhase): + # The parity gate in the Markdown plan has no executable declaration yet. + # These dependencies do not establish parity or authorize deleting scripts. + after = (RecordPlanePhase, ReviewQueuePhase, ControlPlanePhase) -class ControlPlanePhase(PlanPhase): - after = (RecordPlanePhase,) +class StoryPhase(PlanPhase): + """A view of the native records, not a prerequisite for their cutover.""" + + after = (CutoverPhase,) -class CutoverPhase(PlanPhase): - after = (ReviewQueuePhase, ControlPlanePhase) +class CommanderPagePhase(PlanPhase): + """A view of the native records and control plane after their cutover.""" + + after = (CutoverPhase,) class SkillPhase(PlanPhase): diff --git a/tests/test_plan_phases.py b/tests/test_plan_phases.py new file mode 100644 index 0000000000..123edd2d7b --- /dev/null +++ b/tests/test_plan_phases.py @@ -0,0 +1,57 @@ +from dataclasses import replace + +from agent_comms.plan_phases import ( + CommanderPagePhase, + ControlPlanePhase, + CutoverPhase, + PlanPhase, + RecordPlanePhase, + ReviewQueuePhase, + SkillPhase, + StoryPhase, +) +from agent_comms.todos import DoneTodoState +from agent_comms.tools import CommsOrderServeTool + +from test_commander_problem_orders import create + + +def test_cutover_waits_for_native_work_and_views_follow_cutover(commander): + comms, caller = commander + required = (RecordPlanePhase, ReviewQueuePhase, ControlPlanePhase) + phases = (*required, CutoverPhase, StoryPhase, CommanderPagePhase) + todos = [] + for index, phase in enumerate(phases, 1): + todo = create(commander, f"P{index}") + CommsOrderServeTool(problem=todo.id, serves=phase).apply(comms, caller) + todos.append(comms.goals.todos.get(todo.id)) + + native_done = tuple( + replace(todo, state=DoneTodoState) if todo.order.serves in required else todo + for todo in todos + ) + assert CutoverPhase.ready(native_done) + assert not StoryPhase.ready(native_done) + assert not CommanderPagePhase.ready(native_done) + assert not SkillPhase.ready(native_done) + + for unfinished in required: + snapshot = tuple( + todo if todo.order.serves in (unfinished, CutoverPhase) + else replace(todo, state=DoneTodoState) + for todo in todos + ) + assert not CutoverPhase.ready(snapshot) + + cutover_done = tuple( + replace(todo, state=DoneTodoState) if todo.order.serves is CutoverPhase else todo + for todo in native_done + ) + assert StoryPhase.ready(cutover_done) + assert CommanderPagePhase.ready(cutover_done) + assert SkillPhase.ready(cutover_done) + assert not any( + dependency in (StoryPhase, CommanderPagePhase) + for phase in PlanPhase.members_with(PlanPhase) + for dependency in phase.after + ) From a272bf31084e6154421a00f485f9b3f086b3670f Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 13:51:03 -0400 Subject: [PATCH 02/12] Make commander views wait on their source phases --- docs/refactor/shared-plan-claims-A.md | 56 +++++++++++++++++++++++++++ src/agent_comms/plan_phases.py | 6 +-- tests/test_plan_phases.py | 13 +++++-- 3 files changed, 69 insertions(+), 6 deletions(-) diff --git a/docs/refactor/shared-plan-claims-A.md b/docs/refactor/shared-plan-claims-A.md index 6ac3765e76..1935b957a3 100644 --- a/docs/refactor/shared-plan-claims-A.md +++ b/docs/refactor/shared-plan-claims-A.md @@ -1,5 +1,9 @@ # Shared plan claims: A dependency checkpoint +The original A dependency design below is superseded by the A2 correction at +this file's end: views wait on their source phases, not cutover. A2's completion +rule and executable parity gate remain in progress; B has not started. + Owner: pi-plan-claims. Branch: feat/shared-plan-claims. Base: 011084fc5. This checkpoint changes commander-native dependency declarations only. Shared claims are the next, separate commit. Nothing is installed or merged here. @@ -125,3 +129,55 @@ blocks parity acceptance and script deletion, but does not prevent reviewing thi dependency-only checkpoint. Both machines can write the approved shared claim store. pi-cn-phase7 owns dispatch and will propose any checked-task capability; this checkpoint does not fabricate runnable task bodies from phase names. + +## A2: corrected view prerequisites and false story completion + +Owner correction 1753: story waits on record plane; commander page waits on +record plane and control plane. Neither waits on cutover, and no phase waits +on either view. The earlier dependency on cutover was my mistake. + +Actual source evaluation of the same 214 retained commander orders: + +```text +Before A2 (A commit): +Core ready: record_plane +Core next startable (ready, no open order): (none) +story done= True after= cutover +commander_page done= False after= cutover + +After the A2 view dependency correction: +Core ready: record_plane +Core next startable (ready, no open order): (none) +cutover done= False after= record_plane,review_queue,control_plane +story done= True after= record_plane +commander_page done= False after= record_plane,control_plane +``` + +Story's only serving order is P149, revision 8, installed/done, order +`b258d208cd26`, serving `story`. Its text is: + +> every send-back wakes all units because verdicts declare no audience + +Its actual Works record is: + +> Merged to main as 74176e31a (#1678, delivered commit f72e47943); closed on merge evidence by owner directive 1520, run not re-observed: a send-back reaches only its unit and its order's thread, so other units take no turn for it, while a verdict every unit must act on still reaches all of them + +This is legitimately related to the story phase, but it is not acceptance of the +whole phase's order/fleet story tools. The wrong rule is `PlanPhase.done`: all +currently known serving subtasks closed does not mean the whole phase passed. +The retained record was not edited or reopened. The completion-rule repair will +use the phase's explicitly declared full-phase checked task, not infer completion +from this serving set. pi-cn-phase7 owns that task type and its accepted-order +semantics; I own its use in PlanPhase. Owner-confirmed review queue delivery is +PR #1654, merge 41e4ec7319432755d75dcd8dbbab35839a4eb005. + +The expanded family test confirms story can start after record plane while +control plane and cutover are still open; page starts after record and control +plane without waiting for cutover. Both focused tests passed (2 passed, 16 +deselected, 1 pre-existing timeout-option warning, 5.37 seconds). The run used +`--basetemp /home/ts/.cache/agent-scratch/pi-plan-claims/a2-view-check/pytest`; +that owned disposable directory is retired after the run. + +A2's executable per-matrix-row comparison is newly authorized. It is not yet +implemented. No failing parity rows or passing parity acceptance are asserted +by this view-dependency checkpoint. B remains after A2. diff --git a/src/agent_comms/plan_phases.py b/src/agent_comms/plan_phases.py index 0a58d54706..43a527cc5a 100644 --- a/src/agent_comms/plan_phases.py +++ b/src/agent_comms/plan_phases.py @@ -95,13 +95,13 @@ class CutoverPhase(PlanPhase): class StoryPhase(PlanPhase): """A view of the native records, not a prerequisite for their cutover.""" - after = (CutoverPhase,) + after = (RecordPlanePhase,) class CommanderPagePhase(PlanPhase): - """A view of the native records and control plane after their cutover.""" + """A view of the native records and control plane, independent of cutover.""" - after = (CutoverPhase,) + after = (RecordPlanePhase, ControlPlanePhase) class SkillPhase(PlanPhase): diff --git a/tests/test_plan_phases.py b/tests/test_plan_phases.py index 123edd2d7b..82eaa1283b 100644 --- a/tests/test_plan_phases.py +++ b/tests/test_plan_phases.py @@ -16,7 +16,7 @@ from test_commander_problem_orders import create -def test_cutover_waits_for_native_work_and_views_follow_cutover(commander): +def test_views_wait_for_native_work_but_never_block_cutover(commander): comms, caller = commander required = (RecordPlanePhase, ReviewQueuePhase, ControlPlanePhase) phases = (*required, CutoverPhase, StoryPhase, CommanderPagePhase) @@ -26,13 +26,20 @@ def test_cutover_waits_for_native_work_and_views_follow_cutover(commander): CommsOrderServeTool(problem=todo.id, serves=phase).apply(comms, caller) todos.append(comms.goals.todos.get(todo.id)) + record_done = tuple( + replace(todo, state=DoneTodoState) if todo.order.serves is RecordPlanePhase else todo + for todo in todos + ) + assert StoryPhase.ready(record_done) + assert not CommanderPagePhase.ready(record_done) + native_done = tuple( replace(todo, state=DoneTodoState) if todo.order.serves in required else todo for todo in todos ) assert CutoverPhase.ready(native_done) - assert not StoryPhase.ready(native_done) - assert not CommanderPagePhase.ready(native_done) + assert StoryPhase.ready(native_done) + assert CommanderPagePhase.ready(native_done) assert not SkillPhase.ready(native_done) for unfinished in required: From c0da9abdfc7d00251f8a9ee7a678bd8ccf56958e Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 13:53:55 -0400 Subject: [PATCH 03/12] Complete plan phases only through declared acceptance --- docs/refactor/shared-plan-claims-A.md | 41 +++++++- plans/commander-native-plan.md | 2 +- src/agent_comms/plan_phases.py | 12 ++- src/agent_comms/todos.py | 2 + src/agent_comms/work_dispatch.py | 144 ++++++++++++++++++++++++++ tests/test_plan_phases.py | 67 ++++++++++-- 6 files changed, 252 insertions(+), 16 deletions(-) create mode 100644 src/agent_comms/work_dispatch.py diff --git a/docs/refactor/shared-plan-claims-A.md b/docs/refactor/shared-plan-claims-A.md index 1935b957a3..1d55cf68d8 100644 --- a/docs/refactor/shared-plan-claims-A.md +++ b/docs/refactor/shared-plan-claims-A.md @@ -2,7 +2,8 @@ The original A dependency design below is superseded by the A2 correction at this file's end: views wait on their source phases, not cutover. A2's completion -rule and executable parity gate remain in progress; B has not started. +rule is now corrected; its executable parity gate remains in progress. B has not +started. Owner: pi-plan-claims. Branch: feat/shared-plan-claims. Base: 011084fc5. This checkpoint changes commander-native dependency declarations only. Shared @@ -181,3 +182,41 @@ that owned disposable directory is retired after the run. A2's executable per-matrix-row comparison is newly authorized. It is not yet implemented. No failing parity rows or passing parity acceptance are asserted by this view-dependency checkpoint. B remains after A2. + +### A2 completion-rule repair + +Integrated `work_dispatch.py` exactly from pi-cn-phase7's d123d18e2 (Git blob +67cccd2507bf12f5f69748a81c10675677912309), its imports/task declaration, and the +`ProblemOrder.accepted` declarations. No phase-7 enforcement or trace changes +were imported. pi-cn-phase7 retains ownership of dispatch implementation and its +verification. No production task briefs or automatic launches were added here. + +`PlanPhase.done` now asks its declared task about acceptance of the tasks serving +that phase. An unspecified full-phase task returns false. `CheckedPhaseTask` +matches the exact declared problem identity and requires a closed installed +order; a withdrawn/folded order is not accepted. Review queue is explicitly a +previously delivered phase, owner-confirmed as #1654, not inferred from a closed +subset of its subtasks. The Markdown plan now describes this same rule. + +Actual Core output after the completion-rule correction: + +```text +A2 completion correction; retained commander orders: 214 +Core ready: record_plane +Core next startable (ready, no open order): (none) +review_queue done= True after= record_plane +cutover done= False after= record_plane,review_queue,control_plane +story done= False after= record_plane +commander_page done= False after= record_plane,control_plane +P149 accepted= True +Story task= UnspecifiedPhaseTask +``` + +P149 is still legitimately closed; the story phase no longer falsely reads done. +Three focused tests passed (3 passed, 16 deselected, one existing timeout-option +warning, 7.26 seconds), covering phase/view prerequisites, exact whole-phase +acceptance versus closed subtasks/withdrawal/wrong-phase records, and PlanIdle. +The owned run directory was +`/home/ts/.cache/agent-scratch/pi-plan-claims/a2-completion-check/pytest`; disposable +run output is removed after acceptance of this check. No original records changed. +The full parity comparison remains unimplemented, so A2 as a whole is unfinished. diff --git a/plans/commander-native-plan.md b/plans/commander-native-plan.md index 7e1eeae84b..888180377f 100644 --- a/plans/commander-native-plan.md +++ b/plans/commander-native-plan.md @@ -206,4 +206,4 @@ Toad is a view of Core, so the commander's page is one more view over Core's ans ## Order and dependencies -The phases and what each waits for are declared once, in `src/agent_comms/plan_phases.py` (the `PlanPhase` family). An order names the phase it serves with a `Serves: ` line, stored as `ProblemOrder.serves`; a phase is done when every order serving it is closed. Each rotation prints `PLAN IDLE` for every phase whose dependencies are done and that has no open order (`PlanIdle` in `src/agent_comms/standing_rules.py`). +The phases and what each waits for are declared once, in `src/agent_comms/plan_phases.py` (the `PlanPhase` family). An order names the phase it serves with a `Serves: ` line, stored as `ProblemOrder.serves`. A phase is done when its explicitly declared full-phase checked task has accepted installed evidence; closing related subtasks does not complete the phase. Earlier delivered phases record their delivery on their declarations. Each rotation prints `PLAN IDLE` for every phase whose dependencies are done and that has no open order (`PlanIdle` in `src/agent_comms/standing_rules.py`). diff --git a/src/agent_comms/plan_phases.py b/src/agent_comms/plan_phases.py index 43a527cc5a..d93984f5f0 100644 --- a/src/agent_comms/plan_phases.py +++ b/src/agent_comms/plan_phases.py @@ -3,7 +3,8 @@ This family is the one owner of the plan's order and dependencies; ``plans/commander-native-plan.md`` points here. An order names the phase it serves in ``ProblemOrder.serves``; an order outside the plan serves -``UnplannedPhase``. A phase is done when every order serving it is closed. +``UnplannedPhase``. A phase is done only when its declared full-phase task is accepted; closing +related subtasks does not establish completion of the phase. """ from __future__ import annotations @@ -12,9 +13,11 @@ from typing import TYPE_CHECKING, ClassVar from .declared_family import DeclaredFamily +from .work_dispatch import UnspecifiedPhaseTask if TYPE_CHECKING: from .todos import Todo + from .work_dispatch import PhaseTask class OrderPhase(DeclaredFamily, affix="Phase"): @@ -27,6 +30,7 @@ class UnplannedPhase(OrderPhase): class PlanPhase(OrderPhase, declared_name=None): after: ClassVar[tuple[type[PlanPhase], ...]] = () + task: ClassVar[PhaseTask] = UnspecifiedPhaseTask() @classmethod def serving(cls, todos: Iterable[Todo]) -> tuple[Todo, ...]: @@ -34,8 +38,7 @@ def serving(cls, todos: Iterable[Todo]) -> tuple[Todo, ...]: @classmethod def done(cls, todos: Iterable[Todo]) -> bool: - serving = cls.serving(todos) - return bool(serving) and not any(todo.state.open for todo in serving) + return cls.task.done(cls.serving(todos)) @classmethod def ordered(cls, todos: Iterable[Todo]) -> bool: @@ -78,8 +81,9 @@ class RecordPlanePhase(PlanPhase): after = (RetainedStorePhase,) -class ReviewQueuePhase(PlanPhase): +class ReviewQueuePhase(MergedPhase): after = (RecordPlanePhase,) + delivered = "41e4ec7319432755d75dcd8dbbab35839a4eb005 (#1654)" class ControlPlanePhase(PlanPhase): diff --git a/src/agent_comms/todos.py b/src/agent_comms/todos.py index 046202fe31..af62b2719f 100644 --- a/src/agent_comms/todos.py +++ b/src/agent_comms/todos.py @@ -1313,6 +1313,7 @@ def instructions(self) -> tuple[str, ...]: class ProblemOrder(DeclaredFamily, affix="ProblemOrder"): """A problem's order state; an unassigned finding is not a fabricated order.""" pending: ClassVar[bool] = False + accepted: ClassVar[bool] = False line: ClassVar[str] = "No order" pull_request_label: ClassVar[str] = "Agent-Comms-Order:" finding: ProblemFinding = field(default_factory=UnlocatedProblemFinding, @@ -1619,6 +1620,7 @@ def waiting_on_install(self, todo: Todo, read: dict[str, OrderProgress]) -> tupl class InstalledProblemOrder(DeliveredProblemOrder): works: str artifact: str + accepted: ClassVar[bool] = True line: ClassVar[str] = "Installed" def __post_init__(self): diff --git a/src/agent_comms/work_dispatch.py b/src/agent_comms/work_dispatch.py new file mode 100644 index 0000000000..67cccd2507 --- /dev/null +++ b/src/agent_comms/work_dispatch.py @@ -0,0 +1,144 @@ +"""Checked work starts through Core's existing order and native lifecycle owners. + +A phase declares an existing PendingProblemOrder, not a second task format. Work +without that declaration stays visible; readiness is not permission to invent an +acceptance check. Failed startup state stays retained for explicit disposition. +""" + +from __future__ import annotations + +import subprocess +import time +from abc import ABC, abstractmethod +from collections.abc import Iterable +from dataclasses import dataclass, replace +from typing import TYPE_CHECKING, ClassVar + +if TYPE_CHECKING: + from .comms import Comms + from .goal_actions import ProblemOrderGoalAction + from .plan_phases import PlanPhase + from .threads import Thread + from .todos import PendingProblemOrder, Todo + + +@dataclass(frozen=True) +class WorkDispatch: + """One checked native order: allocate its unit, publish custody, then start it.""" + + action: ProblemOrderGoalAction + revision: int + + def start(self, comms: Comms) -> Thread: + from .goal_actions import GoalActionContext + from .native_settings import NativeThreadDefaults + from .storage_rules import CheckoutStorageEffect + from .thread_status import StoppedThreadStatus + + defaults = NativeThreadDefaults() + # A concurrent rotation of this same row contends on one git checkout, + # not on two newly generated unit names. Never adopt a partial attempt. + name = f"unit-{self.action.problem.lower()}-r{self.revision}" + storage = comms.registry.storage_rule + checkout = storage.order_checkout(name, self.action.problem) + candidate = comms.registry.new_thread( + name, frozenset(), str(checkout), model=defaults.model, + thinking_level=defaults.thinking_level, + ) + self.action.request_ref(GoalActionContext(comms.goals, candidate)) + comms.registry.require_declaration_available(candidate, StoppedThreadStatus()) + CheckoutStorageEffect(checkout).check(storage, candidate) + subprocess.run( + ("git", "-C", self.action.order.check.directory, "fetch", "origin", "main"), + check=True, capture_output=True, text=True, timeout=60, + ) + subprocess.run( + ("git", "-C", self.action.order.check.directory, "worktree", "add", "-b", + f"auto/{name}", str(checkout), "origin/main"), + check=True, capture_output=True, text=True, timeout=60, + ) + location = candidate.runtime.new_location(comms.root, checkout) + child = comms.registry.declare( + replace(candidate, session_file=location.read_key), StoppedThreadStatus(), + ) + # A failed publication leaves a stopped unit and its checkout, not an + # untracked running task. A failed/uncertain start never releases custody. + comms.goals.update_goal(child.name, self.action) + comms.owners.start(child.name) + return comms.registry.require(child.name) + + @classmethod + def reissue(cls, comms: Comms, commander: Thread, todo: Todo) -> None: + """Retained checked work needs no already-idle unit.""" + from .goal_actions import OrderGoalAction + + current = comms.goals.todos.get(todo.id) + current.require_revision(todo.revision) + if current.holder or not current.pending: + return + action = OrderGoalAction( + problem=current.id, expected_revision=current.revision, + order=current.order.reissued(time.time()), request=current.goal.request(), + deadline=current.deadline, commander=commander.name, + ) + cls(action, revision=current.revision).start(comms) + + +class PhaseTask(ABC): + instruction: ClassVar[str] + + @abstractmethod + def start(self, comms: Comms, commander: Thread, phase: type[PlanPhase]) -> None: ... + + @abstractmethod + def done(self, todos: Iterable[Todo]) -> bool: ... + + +class UnspecifiedPhaseTask(PhaseTask): + instruction = "no checked task declared; supply its source, check and brief" + + def start(self, comms: Comms, commander: Thread, phase: type[PlanPhase]) -> None: + """Readiness alone cannot authorize a fabricated order.""" + + def done(self, todos: Iterable[Todo]) -> bool: + return False + + +@dataclass(frozen=True) +class CheckedPhaseTask(PhaseTask): + """An explicitly authored task; its order owns source, check, edits and run.""" + + problem: str + repo: str + text: str + order: PendingProblemOrder + deadline_seconds: float + instruction = "Core starts the declared checked task in its own unit checkout" + + def __post_init__(self) -> None: + if not 0 < self.deadline_seconds <= 24 * 60 * 60: + raise ValueError("A phase task needs a positive deadline of at most one day.") + + def start(self, comms: Comms, commander: Thread, phase: type[PlanPhase]) -> None: + from .goal_actions import CreateProblemGoalAction + from .recorded_time import RecordedEventTime + + history = comms.goals.todos.history_for_commander(commander.incarnation) + if not phase.ready(history) or phase.ordered(history): + return + # An existing unowned row belongs to UnownedWork, which reissues that + # row's actual order instead of replacing it with a phase template. + if any(todo.state.open for todo in phase.serving(history)): + return + now = time.time() + order = replace(self.order.reissued(now), serves=phase) + action = CreateProblemGoalAction( + problem=self.problem, repo=self.repo, text=self.text, order=order, + request=order.request(commander, (self.problem,)), commander=commander.name, + deadline=RecordedEventTime(now + self.deadline_seconds), + ) + WorkDispatch(action, revision=0).start(comms) + + def done(self, todos: Iterable[Todo]) -> bool: + return any(todo.id == self.problem and not todo.state.open and todo.order.accepted + for todo in todos) diff --git a/tests/test_plan_phases.py b/tests/test_plan_phases.py index 82eaa1283b..dc17a3e3e8 100644 --- a/tests/test_plan_phases.py +++ b/tests/test_plan_phases.py @@ -6,35 +6,60 @@ CutoverPhase, PlanPhase, RecordPlanePhase, - ReviewQueuePhase, SkillPhase, StoryPhase, ) -from agent_comms.todos import DoneTodoState +from agent_comms.todos import ( + DoneTodoState, + InstalledProblemOrder, + OperationDelivery, + UnorderedProblemOrder, + WithdrawnProblemOrder, +) from agent_comms.tools import CommsOrderServeTool +from agent_comms.work_dispatch import CheckedPhaseTask from test_commander_problem_orders import create -def test_views_wait_for_native_work_but_never_block_cutover(commander): +def accepted(todo): + installed = todo.order.copy_as( + InstalledProblemOrder, delivery=OperationDelivery(), + works="The declared whole phase worked through its acceptance run.", + artifact="phase-acceptance.txt", + ) + return replace(todo, state=DoneTodoState, assignment=None, order=installed) + + +def task(todo): + return CheckedPhaseTask( + problem=todo.id, repo=todo.repo, text=todo.text, + order=todo.order, deadline_seconds=300, + ) + + +def test_views_wait_for_native_work_but_never_block_cutover(commander, monkeypatch): comms, caller = commander - required = (RecordPlanePhase, ReviewQueuePhase, ControlPlanePhase) + # ReviewQueuePhase is explicitly delivered as #1654, not inferred from a subtask. + required = (RecordPlanePhase, ControlPlanePhase) phases = (*required, CutoverPhase, StoryPhase, CommanderPagePhase) todos = [] for index, phase in enumerate(phases, 1): todo = create(commander, f"P{index}") CommsOrderServeTool(problem=todo.id, serves=phase).apply(comms, caller) - todos.append(comms.goals.todos.get(todo.id)) + todo = comms.goals.todos.get(todo.id) + monkeypatch.setattr(phase, "task", task(todo)) + todos.append(todo) record_done = tuple( - replace(todo, state=DoneTodoState) if todo.order.serves is RecordPlanePhase else todo + accepted(todo) if todo.order.serves is RecordPlanePhase else todo for todo in todos ) assert StoryPhase.ready(record_done) assert not CommanderPagePhase.ready(record_done) native_done = tuple( - replace(todo, state=DoneTodoState) if todo.order.serves in required else todo + accepted(todo) if todo.order.serves in required else todo for todo in todos ) assert CutoverPhase.ready(native_done) @@ -44,14 +69,13 @@ def test_views_wait_for_native_work_but_never_block_cutover(commander): for unfinished in required: snapshot = tuple( - todo if todo.order.serves in (unfinished, CutoverPhase) - else replace(todo, state=DoneTodoState) + todo if todo.order.serves in (unfinished, CutoverPhase) else accepted(todo) for todo in todos ) assert not CutoverPhase.ready(snapshot) cutover_done = tuple( - replace(todo, state=DoneTodoState) if todo.order.serves is CutoverPhase else todo + accepted(todo) if todo.order.serves is CutoverPhase else todo for todo in native_done ) assert StoryPhase.ready(cutover_done) @@ -62,3 +86,26 @@ def test_views_wait_for_native_work_but_never_block_cutover(commander): for phase in PlanPhase.members_with(PlanPhase) for dependency in phase.after ) + + +def test_closed_related_subtask_does_not_accept_a_whole_phase(commander, monkeypatch): + comms, caller = commander + subtask = create(commander, "P149") + CommsOrderServeTool(problem=subtask.id, serves=StoryPhase).apply(comms, caller) + subtask = accepted(comms.goals.todos.get(subtask.id)) + assert not StoryPhase.done((subtask,)) + + whole = create(commander, "P999") + CommsOrderServeTool(problem=whole.id, serves=StoryPhase).apply(comms, caller) + whole = comms.goals.todos.get(whole.id) + monkeypatch.setattr(StoryPhase, "task", task(whole)) + assert not StoryPhase.done((subtask, whole)) + assert StoryPhase.done((subtask, accepted(whole))) + + withdrawn = whole.order.copy_as( + WithdrawnProblemOrder, previous=whole.held(), why="The order was sent in error.", + ) + assert not StoryPhase.done((replace(whole, state=DoneTodoState, order=withdrawn),)) + assert not UnorderedProblemOrder().accepted + wrong_phase = replace(accepted(whole), order=replace(accepted(whole).order, serves=ControlPlanePhase)) + assert not StoryPhase.done((wrong_phase,)) From 5ec3e1adc25a5d54c563d8b109ba4c538e24aa1b Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 14:28:27 -0400 Subject: [PATCH 04/12] Declare every parity row and fail closed on incomplete comparisons --- docs/refactor/commander-parity-progress.md | 86 ++++++++ parity-gate.patch | 22 +++ phase-task-consumers.patch | 33 ++++ phase-task-tests.patch | 36 ++++ phase7-task-declarations.patch | 97 +++++++++ src/agent_comms/commander_parity/__init__.py | 1 + src/agent_comms/commander_parity/rows.py | 191 ++++++++++++++++++ src/agent_comms/commander_parity/text.py | 44 +++++ src/agent_comms/commander_parity_cli.py | 110 +++++++++++ src/agent_comms/parity_checks.py | 196 +++++++++++++++++++ tests/test_commander_parity.py | 57 ++++++ work-dispatch-followup.patch | 36 ++++ 12 files changed, 909 insertions(+) create mode 100644 docs/refactor/commander-parity-progress.md create mode 100644 parity-gate.patch create mode 100644 phase-task-consumers.patch create mode 100644 phase-task-tests.patch create mode 100644 phase7-task-declarations.patch create mode 100644 src/agent_comms/commander_parity/__init__.py create mode 100644 src/agent_comms/commander_parity/rows.py create mode 100644 src/agent_comms/commander_parity/text.py create mode 100644 src/agent_comms/commander_parity_cli.py create mode 100644 src/agent_comms/parity_checks.py create mode 100644 tests/test_commander_parity.py create mode 100644 work-dispatch-followup.patch diff --git a/docs/refactor/commander-parity-progress.md b/docs/refactor/commander-parity-progress.md new file mode 100644 index 0000000000..fe9758310a --- /dev/null +++ b/docs/refactor/commander-parity-progress.md @@ -0,0 +1,86 @@ +# Commander parity checkpoint (2026-10-19) + +## Working source application paths + +`CheckRoot._session` incorrectly called `NativeRecord.project` on an already +normalized `ExternalRecord`. The projection boundary already yields events. +`ImportedSessionWriter.import_events` now consumes those events, retaining each +original timestamp; the single caller no longer assumes a native record. +The replaced `import_record` method and its ImportBuffer/ImportLimits dependency +are deleted, not kept as an alternate path. Full session import still uses +`writer.append` and is unchanged. + +Actual source CLI over the live saved `pi-plan-claims` native history: +`check-root --threads pi-plan-claims --into /native-history-live-01 +-- python -c {root}` returned 0. +177 conversation events retained timestamps present in the original native +source. The disposable root was removed. No input was submitted or replayed; +original sessions, custody and uncertain inputs were untouched. + +Actual saved `claude-code` external startup now refuses precisely: +`Copied conversation event has no original timestamp`. Read-only inspection of +its latest 200 source records found 164 ToolStartTranscript and 164 +ToolEndTranscript events with no recorded time, plus 35 dated AssistantTranscript +events. No original time exists to preserve for those tool events. They are not +retimed to hide the mismatch. Full saved-external startup remains unaccepted. + +## Partial parity, never full acceptance + +Core declares all 33 matrix rows as classes, with native owning phases. Identity +comes from each declaration, not the prose title. The class registry drives the +runner and computed completion; the command decodes the Markdown matrix and +checks its row identities against the declarations. A class-owned checked task +must be accepted through the existing Todo order before that row is done. +Unspecified tasks and partial coverage cannot complete a row. No parity state +column, parity_done boolean or parallel evidence store was added. + +Real isolated CLI worker compared native FieldCodec decoding with current +`commander.order.refuse`: 11 text cases across rows 9, 10 and 25 agree with their +expected outcomes. These are explicitly PARTIAL comparisons, not three finished +rows. The other 30 declarations have no observations. All 33 rows remain +unverified; no observed case failed. Report exit is 1. Each row reports its +class identity, number, native owning phases and coverage. Native owning phases +are attribution, not prerequisites on view completion. + +`CommanderParityCheck.all_done` consumes accepted existing checked tasks; it +returns false for the current declarations. No script deletion is authorized. +The actual Cutover/ParityPhase source edit was denied by stale claims: +`Resource already claimed by 'pi-plan-claims'`. `parity-gate.patch` contains the +exact requested dependency and computed phase; it passes `git apply --check` +but has NOT been applied. The source after-line ratchet still returns 0. + +## Confirmation and staging + +After the coherent source changes: 13 focused tests passed serially +(test_check_root.py, test_commander_parity.py, test_plan_phases.py). These include +native clock preservation, dated external projection without text truncation, +refusal of undated source, actual subprocess worker, codec round trip and all +matrix declarations. Pytest reports its existing unknown `timeout` config warning. +No merged or installed build is claimed. + +Before edits, refactor-audit Package.load parsed 380 production / 477 test / 34 +tool modules with no omissions. AST evidence found one import_record declaration +and one caller; there are now zero of either, one import_events declaration in +native_entries.py and one caller in check_root.py. Runtime source dispatch is +polymorphic; actual native and external paths above corroborate its resolution. + +Pending patches are source under this persistent checkout, never shell-applied +to denied files: +- parity-gate.patch: computed ParityPhase and exact four-part Cutover dependency. +- phase7-task-declarations.patch: peer-authored Record/Control task() factories. +- work-dispatch-followup.patch: finite positive deadlines and exact task identity. +- phase-task-consumers.patch: PlanIdle/proof consumers, against peer #1932. +- phase-task-tests.patch: fixture factory migration; state-only commander fixtures + explicitly disable production starts. Do not run them with live factories. + +P900003/P900007 were absent in the real global Todo store at this checkpoint; +this is not a reservation or permission to overwrite a later collision. No live +board record was created. Consumer staging depends on pi-cn-phase7 #1932; its +native dispatch proof is not whole-phase acceptance. + +Scratch owner: pi-plan-claims. Only scratch root: +/home/ts/.cache/agent-scratch/pi-plan-claims (13 MiB at checkpoint); source checkout +27 MiB; free filesystem 25 GiB. Test subdirectories parity-worker-check, +parity-final-check and parity-confirm are disposable after active borrower +checks. Real-copy directories were already retired by CheckRoot. Patch handoffs +stay in /home/ts/wt/pi-plan-claims--P1. B shared GitHub claims remains unstarted. diff --git a/parity-gate.patch b/parity-gate.patch new file mode 100644 index 0000000000..07d28697c7 --- /dev/null +++ b/parity-gate.patch @@ -0,0 +1,22 @@ +--- a/src/agent_comms/plan_phases.py ++++ b/src/agent_comms/plan_phases.py +@@ -93,8 +93,16 @@ ++class ParityPhase(PlanPhase): ++ after = (RecordPlanePhase, ReviewQueuePhase, ControlPlanePhase) ++ ++ @classmethod ++ def done(cls, todos: Iterable[Todo]) -> bool: ++ from .parity_checks import CommanderParityCheck ++ ++ return CommanderParityCheck.all_done(todos) ++ ++ + class CutoverPhase(PlanPhase): +- # The parity gate in the Markdown plan has no executable declaration yet. +- # These dependencies do not establish parity or authorize deleting scripts. +- after = (RecordPlanePhase, ReviewQueuePhase, ControlPlanePhase) ++ after = (RecordPlanePhase, ReviewQueuePhase, ControlPlanePhase, ParityPhase) + + + class StoryPhase(PlanPhase): + """A view of the native records, not a prerequisite for their cutover.""" diff --git a/phase-task-consumers.patch b/phase-task-consumers.patch new file mode 100644 index 0000000000..7072ab06a6 --- /dev/null +++ b/phase-task-consumers.patch @@ -0,0 +1,33 @@ +--- a/src/agent_comms/standing_rules.py ++++ b/src/agent_comms/standing_rules.py +@@ -218,12 +218,12 @@ + ) + + def enforce(self, comms: Comms, commander: str) -> None: +- self.phase.task.start(comms, comms.registry.require(commander), self.phase) ++ self.phase.task().start(comms, comms.registry.require(commander), self.phase) + + def line(self) -> str: + return ( + f"PLAN IDLE: phase {self.phase.declared_name} ready, no open order" +- f" -> {self.phase.task.instruction}" ++ f" -> {self.phase.task().instruction}" + ) + + +--- a/tools/prove_commander_dispatch.py ++++ b/tools/prove_commander_dispatch.py +@@ -94,11 +94,12 @@ + at=RecordedEventTime(1), first_call_deadline=RecordedEventTime(121), + run=MechanicalOrderRun(), + ) +- RecordPlanePhase.task = CheckedPhaseTask( ++ checked_task = CheckedPhaseTask( + problem, Repo.of(project).github, + "Verify rotation starts a native unit and receives its first real reply.", + order, deadline_seconds=300, + ) ++ RecordPlanePhase.task = classmethod(lambda cls: checked_task) + from agent_comms.commander import rotate + + print(f"ROOT {root}", flush=True) diff --git a/phase-task-tests.patch b/phase-task-tests.patch new file mode 100644 index 0000000000..556152f1ad --- /dev/null +++ b/phase-task-tests.patch @@ -0,0 +1,36 @@ +--- a/tests/test_plan_phases.py ++++ b/tests/test_plan_phases.py +@@ -48,7 +48,7 @@ + todo = create(commander, f"P{index}") + CommsOrderServeTool(problem=todo.id, serves=phase).apply(comms, caller) + todo = comms.goals.todos.get(todo.id) +- monkeypatch.setattr(phase, "task", task(todo)) ++ monkeypatch.setattr(phase, "task", classmethod(lambda cls, value=task(todo): value)) + todos.append(todo) + + record_done = tuple( +@@ -98,7 +98,7 @@ + whole = create(commander, "P999") + CommsOrderServeTool(problem=whole.id, serves=StoryPhase).apply(comms, caller) + whole = comms.goals.todos.get(whole.id) +- monkeypatch.setattr(StoryPhase, "task", task(whole)) ++ monkeypatch.setattr(StoryPhase, "task", classmethod(lambda cls: task(whole))) + assert not StoryPhase.done((subtask, whole)) + assert StoryPhase.done((subtask, accepted(whole))) + +--- a/tests/conftest.py ++++ b/tests/conftest.py +@@ -262,8 +262,13 @@ + from agent_comms.goals import RequestGoalOrder + from agent_comms.todos import PullRequests + from agent_comms.tool_caller import ThreadCaller ++ from agent_comms.plan_phases import PlanPhase ++ from agent_comms.work_dispatch import UnspecifiedPhaseTask + + monkeypatch.setattr(PullRequests, "read", lambda self, repo, state, order: ()) ++ # This fixture observes board state; it never opts into production task starts. ++ for phase in PlanPhase.members_with(PlanPhase): ++ monkeypatch.setattr(phase, "task", classmethod(lambda cls: UnspecifiedPhaseTask())) + + for created, name in enumerate(("lead", "worker"), 1): + comms.registry.declare(comms.registry.new_thread(name, frozenset({'comms'}), str(tmp_path), created_at=float(created))) diff --git a/phase7-task-declarations.patch b/phase7-task-declarations.patch new file mode 100644 index 0000000000..7211d37ef2 --- /dev/null +++ b/phase7-task-declarations.patch @@ -0,0 +1,97 @@ +--- a/src/agent_comms/plan_phases.py ++++ b/src/agent_comms/plan_phases.py +@@ -30,7 +30,9 @@ + + class PlanPhase(OrderPhase, declared_name=None): + after: ClassVar[tuple[type[PlanPhase], ...]] = () +- task: ClassVar[PhaseTask] = UnspecifiedPhaseTask() ++ @classmethod ++ def task(cls) -> PhaseTask: ++ return UnspecifiedPhaseTask() + + @classmethod + def serving(cls, todos: Iterable[Todo]) -> tuple[Todo, ...]: +@@ -38,7 +40,7 @@ + + @classmethod + def done(cls, todos: Iterable[Todo]) -> bool: +- return cls.task.done(cls.serving(todos)) ++ return cls.task().done(cls.serving(todos)) + + @classmethod + def ordered(cls, todos: Iterable[Todo]) -> bool: +@@ -80,6 +82,36 @@ + class RecordPlanePhase(PlanPhase): + after = (RetainedStorePhase,) + ++ @classmethod ++ def task(cls) -> PhaseTask: ++ import shlex ++ import sys ++ from pathlib import Path ++ from .change_report import FileLine ++ from .goals import OrderCheck ++ from .recorded_time import RecordedEventTime ++ from .todos import ApplicationOrderRun, PendingProblemOrder ++ from .work_dispatch import CheckedPhaseTask ++ ++ checkout = Path(__file__).resolve().parents[2] ++ check = "timeout 300 " + shlex.join((sys.executable, "-X", "faulthandler", ++ "-m", "pytest", "-o", "addopts=", "-n", "0", ++ "tests/test_commander_problem_orders.py", "tests/test_commander_run_rules.py")) ++ return CheckedPhaseTask( ++ problem="P900003", repo="OpenHCSDev/agent-comms", ++ text="Complete and verify the full native commander record plane.", ++ deadline_seconds=7200, ++ order=PendingProblemOrder( ++ id="record-plane-full-task", owner=FileLine(str(Path(__file__).with_name("todos.py")), 1066), ++ check=OrderCheck(str(checkout), check), ++ edits=("Finish native verdict, strike, transfer, delivery and build-bound closing through the existing Todo and Goal owners.",), ++ banned=("No private verdict or strike files; no fixture pass claimed as native application acceptance.",), ++ report="Report the delivered commit and the copied-root native record-plane observations with their build revisions.", ++ run=ApplicationOrderRun("On a copied root, a real send-back, missed deadline and false completion each produce one record; a second strike fires the unit once; missing Works and wrong-build evidence are refused; a delivered checked order cannot transfer; renaming a deletion target cannot pass; only real installed evidence closes the whole task."), ++ at=RecordedEventTime(1), first_call_deadline=RecordedEventTime(121), ++ ), ++ ) ++ + + class ReviewQueuePhase(MergedPhase): + after = (RecordPlanePhase,) +@@ -88,6 +120,37 @@ + + class ControlPlanePhase(PlanPhase): + after = (RecordPlanePhase,) ++ ++ @classmethod ++ def task(cls) -> PhaseTask: ++ import shlex ++ import sys ++ from pathlib import Path ++ from .change_report import FileLine ++ from .goals import OrderCheck ++ from .recorded_time import RecordedEventTime ++ from .todos import ApplicationOrderRun, PendingProblemOrder ++ from .work_dispatch import CheckedPhaseTask ++ ++ checkout = Path(__file__).resolve().parents[2] ++ check = "timeout 300 " + shlex.join((sys.executable, "-X", "faulthandler", ++ "-m", "pytest", "-o", "addopts=", "-n", "0", ++ "tests/test_work_dispatch.py", "tests/test_standing_rules.py", ++ "tests/test_storage_rules.py", "tests/test_turn_digests.py")) ++ return CheckedPhaseTask( ++ problem="P900007", repo="OpenHCSDev/agent-comms", ++ text="Complete and verify the full native commander control plane.", ++ deadline_seconds=7200, ++ order=PendingProblemOrder( ++ id="control-plane-full-task", owner=FileLine(str(Path(__file__).with_name("standing_rules.py")), 25), ++ check=OrderCheck(str(checkout), check), ++ edits=("Move the minute wake into Core scheduling; dispatch checked ready work through native order and lifecycle owners; derive standing rules, load, growth, storage and time from their original owners; delete the replaced script and private-state decisions.",), ++ banned=("No live-bus proof writes, phase-name dispatch map, second codec, private heartbeat files or fixture pass claimed as native application acceptance.",), ++ report="Name the started scratch unit, retained order, first reply and build; report native session parity, duplicate decisions, time totals and every removed competing path.", ++ run=ApplicationOrderRun("Scratch rotation starts a real Pi unit for a declared ready task and receives its first reply; a second rotation duplicates neither unit nor order; a full native commander session uses no script process; replay of a preserved hour produces each required decision once and UnitTime classes sum to elapsed time; stopping the commander ends its wake."), ++ at=RecordedEventTime(1), first_call_deadline=RecordedEventTime(121), ++ ), ++ ) + + + class CutoverPhase(PlanPhase): diff --git a/src/agent_comms/commander_parity/__init__.py b/src/agent_comms/commander_parity/__init__.py new file mode 100644 index 0000000000..8d925713b5 --- /dev/null +++ b/src/agent_comms/commander_parity/__init__.py @@ -0,0 +1 @@ +"""Commander parity checks, discovered from their own declarations.""" diff --git a/src/agent_comms/commander_parity/rows.py b/src/agent_comms/commander_parity/rows.py new file mode 100644 index 0000000000..6d80abe987 --- /dev/null +++ b/src/agent_comms/commander_parity/rows.py @@ -0,0 +1,191 @@ +"""The rest of the matrix, explicitly unverified until real comparisons exist. + +Each declaration owns its identity and native integration phases. Owning a row +is not completing that phase; view phases never become phase prerequisites. +""" + +from ..parity_checks import UnimplementedParityCheck +from ..plan_phases import ( + CommanderPagePhase, ControlPlanePhase, CutoverPhase, OneOrderOwnerPhase, + RecordPlanePhase, ReviewQueuePhase, StoryPhase, +) + + +class CreateProblemParityCheck(UnimplementedParityCheck): + number = 1 + capability = "Create a problem with owner, order and deadline" + phases = (OneOrderOwnerPhase,) + + +class AssignOrderParityCheck(UnimplementedParityCheck): + number = 2 + capability = "Assign, reassign and drop an order" + phases = (OneOrderOwnerPhase,) + + +class DeliveredCommitParityCheck(UnimplementedParityCheck): + number = 3 + capability = "Complete only on the delivered commit" + phases = (OneOrderOwnerPhase, RecordPlanePhase) + + +class InstalledEvidenceParityCheck(UnimplementedParityCheck): + number = 4 + capability = "Close with installed evidence" + phases = (OneOrderOwnerPhase,) + + +class BoardParityCheck(UnimplementedParityCheck): + number = 5 + capability = "Board, overdue, silent and spinning" + phases = (OneOrderOwnerPhase,) + + +class BranchHygieneParityCheck(UnimplementedParityCheck): + number = 6 + capability = "Branch hygiene" + phases = (OneOrderOwnerPhase,) + + +class StrikeLimitParityCheck(UnimplementedParityCheck): + number = 7 + capability = "Strikes and the strike limit" + phases = (RecordPlanePhase,) + + +class VerdictParityCheck(UnimplementedParityCheck): + number = 8 + capability = "Verdicts on a unit's tool calls" + phases = (RecordPlanePhase,) + + +class OrderWorkParityCheck(UnimplementedParityCheck): + number = 11 + capability = "What a unit did during its order" + phases = (ReviewQueuePhase,) + + +class UnjudgedParityCheck(UnimplementedParityCheck): + number = 12 + capability = "What the commander has not judged yet" + phases = (ReviewQueuePhase,) + + +class PublicStoryParityCheck(UnimplementedParityCheck): + number = 13 + capability = "Public story and roasts" + phases = (StoryPhase,) + + +class ScheduledWakeParityCheck(UnimplementedParityCheck): + number = 14 + capability = "Heartbeat: next unit every minute" + phases = (ControlPlanePhase,) + + +class StandingRulesParityCheck(UnimplementedParityCheck): + number = 15 + capability = "Standing rules broken right now" + phases = (ControlPlanePhase,) + + +class RuleReviewParityCheck(UnimplementedParityCheck): + number = 16 + capability = "Rule review" + phases = (ControlPlanePhase,) + + +class LoadForkingParityCheck(UnimplementedParityCheck): + number = 17 + capability = "Load and forking" + phases = (ControlPlanePhase,) + + +class GrowthAlarmParityCheck(UnimplementedParityCheck): + number = 18 + capability = "Growth alarm" + phases = (ControlPlanePhase,) + + +class StorageHygieneParityCheck(UnimplementedParityCheck): + number = 19 + capability = "Storage hygiene" + phases = (ControlPlanePhase,) + + +class OrderOnlyContactParityCheck(UnimplementedParityCheck): + number = 20 + capability = "Commander reaches units only through orders" + phases = (ControlPlanePhase,) + + +class ConfigurationParityCheck(UnimplementedParityCheck): + number = 21 + capability = "Paths, repositories and commander identity" + phases = (CutoverPhase,) + + +class CommanderPageParityCheck(UnimplementedParityCheck): + number = 22 + capability = "Seeing the fleet: board, strikes, queue, story and alarms" + phases = (CommanderPagePhase,) + + +class ApplicationRunParityCheck(UnimplementedParityCheck): + number = 23 + capability = "An order states the user-visible run that closes it" + phases = (RecordPlanePhase,) + + +class TracedTransferParityCheck(UnimplementedParityCheck): + number = 24 + capability = "Reassigning past two owners needs the commander's trace" + phases = (RecordPlanePhase,) + + +class DecisionAlertsParityCheck(UnimplementedParityCheck): + number = 26 + capability = "Alerts carry only decisions" + phases = (ControlPlanePhase,) + + +class MomentumParityCheck(UnimplementedParityCheck): + number = 27 + capability = "Momentum prediction per unit compared with the outcome" + phases = (CommanderPagePhase,) + + +class MeasuredLoadParityCheck(UnimplementedParityCheck): + number = 28 + capability = "Measured load and self-report" + phases = (ControlPlanePhase,) + + +class FinishedWorkTransferParityCheck(UnimplementedParityCheck): + number = 29 + capability = "No handoff of finished, checked work" + phases = (RecordPlanePhase,) + + +class BuildBoundProofParityCheck(UnimplementedParityCheck): + number = 30 + capability = "A proof is bound to the build it ran on" + phases = (RecordPlanePhase,) + + +class DeletionTargetParityCheck(UnimplementedParityCheck): + number = 31 + capability = "An order states its intent as a deletion target" + phases = (RecordPlanePhase,) + + +class UnitTimeParityCheck(UnimplementedParityCheck): + number = 32 + capability = "Where units' time goes, classified" + phases = (CommanderPagePhase, ControlPlanePhase) + + +class OrderProgressParityCheck(UnimplementedParityCheck): + number = 33 + capability = "Order progress beside its deadline; the unit sees no due time" + phases = (OneOrderOwnerPhase, ControlPlanePhase) diff --git a/src/agent_comms/commander_parity/text.py b/src/agent_comms/commander_parity/text.py new file mode 100644 index 0000000000..26bce229d2 --- /dev/null +++ b/src/agent_comms/commander_parity/text.py @@ -0,0 +1,44 @@ +"""The native field codec and current commander's real wording refusals.""" + +from ..parity_checks import TextParityCheck, TextSample +from ..plan_phases import RecordPlanePhase +from ..todos import OrderText, PlainText, StrikeReason + + +class PlainWordsParityCheck(TextParityCheck): + number = 9 + capability = "Plain words in verdicts and strikes" + phases = (RecordPlanePhase,) + samples = ( + TextSample(PlainText, "The file now reads the order from its original row.", True), + TextSample(PlainText, "The receipt certifies the authority.", False), + TextSample(PlainText, "Remove the quoted `authority` from that message.", True), + TextSample(StrikeReason, "The unit changed another file and left the ordered repair broken.", True), + TextSample(StrikeReason, "The edit broke.", False), + ) + + +class OrderWordingParityCheck(TextParityCheck): + number = 10 + capability = "Refuse soft or vague orders" + phases = (RecordPlanePhase,) + samples = ( + TextSample(OrderText, "Repair the existing reader and remove the duplicate store.", True), + TextSample(OrderText, "Could you repair the existing reader when you get a chance?", False), + TextSample(OrderText, "Consider keeping both stores if possible.", False), + TextSample(OrderText, "Remove the literal `please` from the help message now.", True), + ) + + +class UnitWordingParityCheck(TextParityCheck): + number = 25 + capability = "Unit wording gets a correction, not a strike" + phases = (RecordPlanePhase,) + samples = ( + TextSample(StrikeReason, + "The unit wrote `canonical` instead of explaining the file that actually failed.", + False), + TextSample(StrikeReason, + "The unit edited `/project/reader.py` but left the requested repair completely broken.", + True), + ) diff --git a/src/agent_comms/commander_parity_cli.py b/src/agent_comms/commander_parity_cli.py new file mode 100644 index 0000000000..0de76d13e9 --- /dev/null +++ b/src/agent_comms/commander_parity_cli.py @@ -0,0 +1,110 @@ +"""Run comparisons through the actual application, with isolated script configuration.""" + +from __future__ import annotations + +import os +import subprocess +import sys +from dataclasses import dataclass +from pathlib import Path +from typing import Annotated + +from .check_root import CheckRoot +from .cli_commands import CliCommand, option +from .commander.config import CommanderConfig +from .comms import Comms +from .field_codec import FieldCodec, PathText +from .launch_runtime import RestartEnvironment, ThreadLaunch +from .parity_checks import CommanderComparison, CommanderParityCheck, CommanderParityReport + + +@dataclass(frozen=True) +class ParityMatrix: + """Decode the existing Markdown matrix once; never identify a row by its title.""" + + rows: tuple[int, ...] + + @classmethod + def read(cls, path: Path) -> ParityMatrix: + text = path.read_text() + try: + section = text.split("## Parity matrix\n", 1)[1].split("\n## ", 1)[0] + except IndexError as error: + raise ValueError(f"No parity matrix in {path}") from error + rows = [] + for line in section.splitlines(): + if not line.startswith("|"): + continue + first = line.split("|")[1].strip() + if first == "#" or first and set(first) <= {"-", ":"}: + continue + rows.append(int(first)) + if not rows or any(number <= 0 for number in rows) or len(set(rows)) != len(rows): + raise ValueError("A parity matrix needs unique positive row numbers") + return cls(tuple(rows)) + + def compare(self, context: CommanderComparison) -> CommanderParityReport: + checks = CommanderParityCheck.members_with(CommanderParityCheck) + numbers = tuple(check.number for check in checks) + if len(set(numbers)) != len(numbers) or set(numbers) - set(self.rows): + raise ValueError("Parity declarations duplicate a row or name a row outside the matrix") + comparisons = tuple( + check.compare(context) for check in sorted(checks, key=lambda check: check.number)) + complete = {row.check.number for row in comparisons + if row.coverage.complete and row.observations} + return CommanderParityReport( + comparisons, tuple(number for number in self.rows if number not in complete)) + + +@dataclass(frozen=True, kw_only=True) +class CommanderParityCliCommand(CliCommand, declared_name="commander-parity"): + help = "Compare native/current commander behaviour in an isolated saved-history root" + plan: Annotated[Path, PathText] = option(help="Markdown plan containing the numbered parity matrix") + into: Annotated[Path, PathText] = option(help="New directory under your agent scratch directory") + + def apply(self, ctx: Comms) -> CommanderParityReport: + config = CommanderConfig.installed() + if config.root.resolve() != ctx.root.resolve(): + raise ValueError("The current commander configuration belongs to another root") + with CheckRoot(ctx, (config.commander,), self.into).open() as copied: + home = copied.root / "home" + config.model_copy(update={ + "root": copied.root, "state": copied.root / "state", + "board": copied.root / "board.md", "narrative": copied.root / "narrative.md", + "scratch": copied.root / "scratch", "disk": copied.root, + }).write(home) + environment = ThreadLaunch.strip(dict(os.environ)) + RestartEnvironment.strip(environment, "root") + environment.update(HOME=str(home), PYTHONPATH=os.pathsep.join(sys.path)) + # A separate interpreter prevents the current scripts' import-time + # globals from ever binding this check to the original live root. + result = subprocess.run( + (sys.executable, "-m", "agent_comms.cli", "--root", str(copied.root), + "commander-parity-observe", "--plan", str(self.plan.resolve())), + env=environment, capture_output=True, text=True, timeout=60, + ) + try: + report = FieldCodec.decode(CommanderParityReport, FieldCodec.parse(result.stdout)) + except ValueError as error: + raise ValueError( + f"Parity worker produced no report (exit {result.returncode}): " + f"{result.stdout}\n{result.stderr}" + ) from error + if result.returncode != report.exit_code(): + raise ValueError(f"Parity worker exit disagrees with its report: {result.stderr}") + return report + + +@dataclass(frozen=True, kw_only=True) +class ObserveCommanderParityCliCommand(CliCommand, declared_name="commander-parity-observe"): + help = "Observe declared comparisons; commander-parity supplies an isolated root" + plan: Annotated[Path, PathText] = option(help="Markdown plan containing the numbered parity matrix") + + def apply(self, ctx: Comms) -> CommanderParityReport: + # This is the real current commander's function, not a reimplementation + # of its refusals or a mocked transport. Its globals are isolated above. + from .commander import order + + if order.COMMS.root.resolve() != ctx.root.resolve(): + raise ValueError("The current scripts opened another root; no comparison ran") + return ParityMatrix.read(self.plan).compare(CommanderComparison(order.refuse)) diff --git a/src/agent_comms/parity_checks.py b/src/agent_comms/parity_checks.py new file mode 100644 index 0000000000..b9e9b00094 --- /dev/null +++ b/src/agent_comms/parity_checks.py @@ -0,0 +1,196 @@ +"""Declared commander comparisons and their actual observations. + +A missing comparison is unverified, never a passing feature. The phase owns this +check family; row identity, numbering and reporting derive from its declarations. +""" + +from __future__ import annotations + +from abc import abstractmethod +from collections.abc import Callable, Iterable +from dataclasses import dataclass +from typing import TYPE_CHECKING, ClassVar + +from .cli_result import CliResult +from .declared_family import DeclaredFamily +from .field_codec import FieldCodec, projected +from .plan_phases import PlanPhase + +if TYPE_CHECKING: + from .todos import OrderWords, Todo + from .work_dispatch import PhaseTask + + +class ProbeObservation(DeclaredFamily, affix="Observation"): + accepted: ClassVar[bool] + + +@dataclass(frozen=True) +class AcceptedObservation(ProbeObservation): + accepted = True + + +@dataclass(frozen=True) +class RefusedObservation(ProbeObservation): + reason: str + accepted = False + + +@dataclass(frozen=True) +class TextSample: + family: type[OrderWords] + text: str + accepted: bool + + +class ParityObservation(DeclaredFamily, affix="Observation"): + @property + @abstractmethod + def passed(self) -> bool: ... + + +@dataclass(frozen=True) +class TextProbeResult(ParityObservation): + family: str + text: str + expected_acceptance: bool + native: ProbeObservation + current: ProbeObservation + + @projected(view="wire") + def passed(self) -> bool: + return self.native.accepted == self.current.accepted == self.expected_acceptance + + +@dataclass(frozen=True) +class CommanderComparison: + """The current script's real boundary, supplied only in an isolated process.""" + + refuse: Callable[[str, type[OrderWords]], None] + + def text(self, sample: TextSample) -> TextProbeResult: + try: + FieldCodec.decode(sample.family, sample.text) + except ValueError as error: + native = RefusedObservation(str(error)) + else: + native = AcceptedObservation() + try: + self.refuse(sample.text, sample.family) + except SystemExit as error: + current = RefusedObservation(str(error)) + else: + current = AcceptedObservation() + return TextProbeResult(sample.family.__name__, sample.text, sample.accepted, + native, current) + + +class ComparisonCoverage(DeclaredFamily, affix="Coverage"): + complete: ClassVar[bool] + + +@dataclass(frozen=True) +class PartialCoverage(ComparisonCoverage): + missing: tuple[str, ...] + complete = False + + def __post_init__(self) -> None: + if not self.missing: + raise ValueError("A partial comparison names what it did not exercise") + + +@dataclass(frozen=True) +class CompleteCoverage(ComparisonCoverage): + complete = True + + +class CommanderParityCheck( + DeclaredFamily, affix="ParityCheck", discovery_package="agent_comms.commander_parity" +): + number: ClassVar[int] + phases: ClassVar[tuple[type[PlanPhase], ...]] + capability: ClassVar[str] + coverage: ClassVar[ComparisonCoverage] + + @classmethod + @abstractmethod + def compare(cls, context: CommanderComparison) -> ParityRowResult: ... + + @classmethod + def task(cls) -> PhaseTask: + from .work_dispatch import UnspecifiedPhaseTask + + return UnspecifiedPhaseTask() + + @classmethod + def done(cls, todos: Iterable[Todo]) -> bool: + # Probe agreement alone never accepts a row. Its checked task must also + # have passed and received the existing order's application acceptance. + return cls.coverage.complete and cls.task().done(todos) + + @classmethod + def all_done(cls, todos: Iterable[Todo]) -> bool: + checks = cls.members_with(cls) + history = tuple(todos) + return bool(checks) and all(check.done(history) for check in checks) + + +class UnimplementedParityCheck(CommanderParityCheck, declared_name=None): + coverage = PartialCoverage(("The native/current workflow comparison is not implemented.",)) + + @classmethod + def compare(cls, context: CommanderComparison) -> ParityRowResult: + return ParityRowResult(cls, ()) + + +class TextParityCheck(CommanderParityCheck, declared_name=None): + samples: ClassVar[tuple[TextSample, ...]] + coverage = PartialCoverage(( + "Only native field decoding and current script refusal samples ran; " + "the row's complete verdict/order/correction workflow has not run.", + )) + + @classmethod + def compare(cls, context: CommanderComparison) -> ParityRowResult: + return ParityRowResult(cls, tuple(context.text(sample) for sample in cls.samples)) + + +@dataclass(frozen=True) +class ParityRowResult: + check: type[CommanderParityCheck] + observations: tuple[ParityObservation, ...] + + @projected(view="wire") + def number(self) -> int: + return self.check.number + + @projected(view="wire") + def coverage(self) -> ComparisonCoverage: + return self.check.coverage + + @projected(view="wire") + def phases(self) -> tuple[type[PlanPhase], ...]: + return self.check.phases + + @projected(view="wire") + def passed(self) -> bool: + return bool(self.observations) and all(item.passed for item in self.observations) + + +@dataclass(frozen=True) +class CommanderParityReport(CliResult): + comparisons: tuple[ParityRowResult, ...] + unverified_rows: tuple[int, ...] + + @projected(view="wire") + def passed(self) -> bool: + return bool(self.comparisons) and not self.unverified_rows and all( + item.passed for item in self.comparisons) + + @projected(view="wire") + def failing_rows(self) -> tuple[int, ...]: + return tuple(item.check.number for item in self.comparisons + if item.observations and not item.passed) + + def exit_code(self) -> int: + return 0 if self.passed else 1 diff --git a/tests/test_commander_parity.py b/tests/test_commander_parity.py new file mode 100644 index 0000000000..a99a489b3f --- /dev/null +++ b/tests/test_commander_parity.py @@ -0,0 +1,57 @@ +import json +import os +import subprocess +import sys +from pathlib import Path + +import pytest + +from agent_comms.commander_parity_cli import ParityMatrix +from agent_comms.field_codec import FieldCodec +from agent_comms.parity_checks import CommanderParityCheck, CommanderParityReport + + +PLAN = Path(__file__).resolve().parents[1] / "plans/commander-native-plan.md" + + +def test_actual_native_current_comparisons_never_pass_unverified_rows(commander_script): + order, _ = commander_script + environment = {**os.environ, "PYTHONPATH": str(PLAN.parents[1] / "src")} + completed = subprocess.run( + (sys.executable, "-m", "agent_comms.cli", "--root", str(order.COMMS.root), + "commander-parity-observe", "--plan", str(PLAN)), + env=environment, capture_output=True, text=True, timeout=30, + ) + assert completed.stdout, completed.stderr + report = FieldCodec.decode(CommanderParityReport, json.loads(completed.stdout)) + declarations = CommanderParityCheck.members_with(CommanderParityCheck) + assert declarations + assert {row.check for row in report.comparisons} == set(declarations) + observed = tuple(row for row in report.comparisons if row.observations) + assert observed and all(row.passed for row in observed) + assert all(row.phases == row.check.phases and row.phases for row in report.comparisons) + assert {check.number for check in declarations} == set(ParityMatrix.read(PLAN).rows) + assert not CommanderParityCheck.all_done(()) + complete_numbers = {row.check.number for row in report.comparisons + if row.coverage.complete and row.observations} + assert report.unverified_rows == tuple( + number for number in ParityMatrix.read(PLAN).rows if number not in complete_numbers) + assert report.unverified_rows + assert not report.passed + assert report.failing_rows == () + assert completed.returncode == report.exit_code() == 1 + assert FieldCodec.decode(CommanderParityReport, report.to_wire()) == report + + +def test_matrix_numbers_are_not_titles_and_invalid_or_duplicate_numbers_are_refused(tmp_path): + plan = tmp_path / "plan.md" + plan.write_text("## Parity matrix\n\n| # | Capability |\n|---|---|\n" + "| 9 | Renamed freely |\n| 10 | Another name |\n") + assert ParityMatrix.read(plan).rows == (9, 10) + plan.write_text("## Parity matrix\n\n| # | Capability |\n|---|---|\n" + "| 9 | First |\n| 9 | Second |\n") + with pytest.raises(ValueError, match="unique positive"): + ParityMatrix.read(plan) + plan.write_text("## Parity matrix\n\n| # | Capability |\n|---|---|\n| x | Invalid |\n") + with pytest.raises(ValueError): + ParityMatrix.read(plan) diff --git a/work-dispatch-followup.patch b/work-dispatch-followup.patch new file mode 100644 index 0000000000..af5f18949a --- /dev/null +++ b/work-dispatch-followup.patch @@ -0,0 +1,36 @@ +diff --git a/src/agent_comms/work_dispatch.py b/src/agent_comms/work_dispatch.py +index 67cccd250..e002df055 100644 +--- a/src/agent_comms/work_dispatch.py ++++ b/src/agent_comms/work_dispatch.py +@@ -7,6 +7,7 @@ acceptance check. Failed startup state stays retained for explicit disposition. + + from __future__ import annotations + ++import math + import subprocess + import time + from abc import ABC, abstractmethod +@@ -116,8 +117,8 @@ class CheckedPhaseTask(PhaseTask): + instruction = "Core starts the declared checked task in its own unit checkout" + + def __post_init__(self) -> None: +- if not 0 < self.deadline_seconds <= 24 * 60 * 60: +- raise ValueError("A phase task needs a positive deadline of at most one day.") ++ if not math.isfinite(self.deadline_seconds) or self.deadline_seconds <= 0: ++ raise ValueError("A phase task needs a finite positive deadline interval.") + + def start(self, comms: Comms, commander: Thread, phase: type[PlanPhase]) -> None: + from .goal_actions import CreateProblemGoalAction +@@ -126,9 +127,9 @@ class CheckedPhaseTask(PhaseTask): + history = comms.goals.todos.history_for_commander(commander.incarnation) + if not phase.ready(history) or phase.ordered(history): + return +- # An existing unowned row belongs to UnownedWork, which reissues that +- # row's actual order instead of replacing it with a phase template. +- if any(todo.state.open for todo in phase.serving(history)): ++ # This task's existing row belongs to UnownedWork. Other phase subtasks ++ # neither supply this task's check nor replace its explicit identity. ++ if any(todo.id == self.problem for todo in history): + return + now = time.time() + order = replace(self.order.reissued(now), serves=phase) From 0963f5563f9b755178234c289614cae7b87a8732 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 14:32:49 -0400 Subject: [PATCH 05/12] Record retired parity scratch and integrated-source checks --- docs/refactor/commander-parity-progress.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/refactor/commander-parity-progress.md b/docs/refactor/commander-parity-progress.md index fe9758310a..5cbe09dbb3 100644 --- a/docs/refactor/commander-parity-progress.md +++ b/docs/refactor/commander-parity-progress.md @@ -79,8 +79,10 @@ board record was created. Consumer staging depends on pi-cn-phase7 #1932; its native dispatch proof is not whole-phase acceptance. Scratch owner: pi-plan-claims. Only scratch root: -/home/ts/.cache/agent-scratch/pi-plan-claims (13 MiB at checkpoint); source checkout +/home/ts/.cache/agent-scratch/pi-plan-claims (8 KiB after cleanup); source checkout 27 MiB; free filesystem 25 GiB. Test subdirectories parity-worker-check, -parity-final-check and parity-confirm are disposable after active borrower -checks. Real-copy directories were already retired by CheckRoot. Patch handoffs -stay in /home/ts/wt/pi-plan-claims--P1. B shared GitHub claims remains unstarted. +parity-final-check and parity-confirm were removed after lsof found no active +borrowers. Real-copy directories were already retired by CheckRoot. Patch +handoffs stay in /home/ts/wt/pi-plan-claims--P1. B shared GitHub claims remains +unstarted. The user's concurrent remote main merge was integrated normally; +all 13 focused checks passed again afterward. No PR/main merge was performed. From 392799ece845a58a182ec46b84e24f653656ca73 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 15:04:22 -0400 Subject: [PATCH 06/12] Move the parity declarations out of #1928 The parity rows restate the Markdown matrix's row numbers and capability text in each class, and five .patch files were committed at the repo root. They return in a follow-up PR with the rows as the single source. Co-Authored-By: Claude Opus 5.5 --- docs/refactor/commander-parity-progress.md | 88 --------- parity-gate.patch | 22 --- phase-task-consumers.patch | 33 ---- phase-task-tests.patch | 36 ---- phase7-task-declarations.patch | 97 --------- src/agent_comms/commander_parity/__init__.py | 1 - src/agent_comms/commander_parity/rows.py | 191 ------------------ src/agent_comms/commander_parity/text.py | 44 ----- src/agent_comms/commander_parity_cli.py | 110 ----------- src/agent_comms/parity_checks.py | 196 ------------------- tests/test_commander_parity.py | 57 ------ work-dispatch-followup.patch | 36 ---- 12 files changed, 911 deletions(-) delete mode 100644 docs/refactor/commander-parity-progress.md delete mode 100644 parity-gate.patch delete mode 100644 phase-task-consumers.patch delete mode 100644 phase-task-tests.patch delete mode 100644 phase7-task-declarations.patch delete mode 100644 src/agent_comms/commander_parity/__init__.py delete mode 100644 src/agent_comms/commander_parity/rows.py delete mode 100644 src/agent_comms/commander_parity/text.py delete mode 100644 src/agent_comms/commander_parity_cli.py delete mode 100644 src/agent_comms/parity_checks.py delete mode 100644 tests/test_commander_parity.py delete mode 100644 work-dispatch-followup.patch diff --git a/docs/refactor/commander-parity-progress.md b/docs/refactor/commander-parity-progress.md deleted file mode 100644 index 5cbe09dbb3..0000000000 --- a/docs/refactor/commander-parity-progress.md +++ /dev/null @@ -1,88 +0,0 @@ -# Commander parity checkpoint (2026-10-19) - -## Working source application paths - -`CheckRoot._session` incorrectly called `NativeRecord.project` on an already -normalized `ExternalRecord`. The projection boundary already yields events. -`ImportedSessionWriter.import_events` now consumes those events, retaining each -original timestamp; the single caller no longer assumes a native record. -The replaced `import_record` method and its ImportBuffer/ImportLimits dependency -are deleted, not kept as an alternate path. Full session import still uses -`writer.append` and is unchanged. - -Actual source CLI over the live saved `pi-plan-claims` native history: -`check-root --threads pi-plan-claims --into /native-history-live-01 --- python -c {root}` returned 0. -177 conversation events retained timestamps present in the original native -source. The disposable root was removed. No input was submitted or replayed; -original sessions, custody and uncertain inputs were untouched. - -Actual saved `claude-code` external startup now refuses precisely: -`Copied conversation event has no original timestamp`. Read-only inspection of -its latest 200 source records found 164 ToolStartTranscript and 164 -ToolEndTranscript events with no recorded time, plus 35 dated AssistantTranscript -events. No original time exists to preserve for those tool events. They are not -retimed to hide the mismatch. Full saved-external startup remains unaccepted. - -## Partial parity, never full acceptance - -Core declares all 33 matrix rows as classes, with native owning phases. Identity -comes from each declaration, not the prose title. The class registry drives the -runner and computed completion; the command decodes the Markdown matrix and -checks its row identities against the declarations. A class-owned checked task -must be accepted through the existing Todo order before that row is done. -Unspecified tasks and partial coverage cannot complete a row. No parity state -column, parity_done boolean or parallel evidence store was added. - -Real isolated CLI worker compared native FieldCodec decoding with current -`commander.order.refuse`: 11 text cases across rows 9, 10 and 25 agree with their -expected outcomes. These are explicitly PARTIAL comparisons, not three finished -rows. The other 30 declarations have no observations. All 33 rows remain -unverified; no observed case failed. Report exit is 1. Each row reports its -class identity, number, native owning phases and coverage. Native owning phases -are attribution, not prerequisites on view completion. - -`CommanderParityCheck.all_done` consumes accepted existing checked tasks; it -returns false for the current declarations. No script deletion is authorized. -The actual Cutover/ParityPhase source edit was denied by stale claims: -`Resource already claimed by 'pi-plan-claims'`. `parity-gate.patch` contains the -exact requested dependency and computed phase; it passes `git apply --check` -but has NOT been applied. The source after-line ratchet still returns 0. - -## Confirmation and staging - -After the coherent source changes: 13 focused tests passed serially -(test_check_root.py, test_commander_parity.py, test_plan_phases.py). These include -native clock preservation, dated external projection without text truncation, -refusal of undated source, actual subprocess worker, codec round trip and all -matrix declarations. Pytest reports its existing unknown `timeout` config warning. -No merged or installed build is claimed. - -Before edits, refactor-audit Package.load parsed 380 production / 477 test / 34 -tool modules with no omissions. AST evidence found one import_record declaration -and one caller; there are now zero of either, one import_events declaration in -native_entries.py and one caller in check_root.py. Runtime source dispatch is -polymorphic; actual native and external paths above corroborate its resolution. - -Pending patches are source under this persistent checkout, never shell-applied -to denied files: -- parity-gate.patch: computed ParityPhase and exact four-part Cutover dependency. -- phase7-task-declarations.patch: peer-authored Record/Control task() factories. -- work-dispatch-followup.patch: finite positive deadlines and exact task identity. -- phase-task-consumers.patch: PlanIdle/proof consumers, against peer #1932. -- phase-task-tests.patch: fixture factory migration; state-only commander fixtures - explicitly disable production starts. Do not run them with live factories. - -P900003/P900007 were absent in the real global Todo store at this checkpoint; -this is not a reservation or permission to overwrite a later collision. No live -board record was created. Consumer staging depends on pi-cn-phase7 #1932; its -native dispatch proof is not whole-phase acceptance. - -Scratch owner: pi-plan-claims. Only scratch root: -/home/ts/.cache/agent-scratch/pi-plan-claims (8 KiB after cleanup); source checkout -27 MiB; free filesystem 25 GiB. Test subdirectories parity-worker-check, -parity-final-check and parity-confirm were removed after lsof found no active -borrowers. Real-copy directories were already retired by CheckRoot. Patch -handoffs stay in /home/ts/wt/pi-plan-claims--P1. B shared GitHub claims remains -unstarted. The user's concurrent remote main merge was integrated normally; -all 13 focused checks passed again afterward. No PR/main merge was performed. diff --git a/parity-gate.patch b/parity-gate.patch deleted file mode 100644 index 07d28697c7..0000000000 --- a/parity-gate.patch +++ /dev/null @@ -1,22 +0,0 @@ ---- a/src/agent_comms/plan_phases.py -+++ b/src/agent_comms/plan_phases.py -@@ -93,8 +93,16 @@ -+class ParityPhase(PlanPhase): -+ after = (RecordPlanePhase, ReviewQueuePhase, ControlPlanePhase) -+ -+ @classmethod -+ def done(cls, todos: Iterable[Todo]) -> bool: -+ from .parity_checks import CommanderParityCheck -+ -+ return CommanderParityCheck.all_done(todos) -+ -+ - class CutoverPhase(PlanPhase): -- # The parity gate in the Markdown plan has no executable declaration yet. -- # These dependencies do not establish parity or authorize deleting scripts. -- after = (RecordPlanePhase, ReviewQueuePhase, ControlPlanePhase) -+ after = (RecordPlanePhase, ReviewQueuePhase, ControlPlanePhase, ParityPhase) - - - class StoryPhase(PlanPhase): - """A view of the native records, not a prerequisite for their cutover.""" diff --git a/phase-task-consumers.patch b/phase-task-consumers.patch deleted file mode 100644 index 7072ab06a6..0000000000 --- a/phase-task-consumers.patch +++ /dev/null @@ -1,33 +0,0 @@ ---- a/src/agent_comms/standing_rules.py -+++ b/src/agent_comms/standing_rules.py -@@ -218,12 +218,12 @@ - ) - - def enforce(self, comms: Comms, commander: str) -> None: -- self.phase.task.start(comms, comms.registry.require(commander), self.phase) -+ self.phase.task().start(comms, comms.registry.require(commander), self.phase) - - def line(self) -> str: - return ( - f"PLAN IDLE: phase {self.phase.declared_name} ready, no open order" -- f" -> {self.phase.task.instruction}" -+ f" -> {self.phase.task().instruction}" - ) - - ---- a/tools/prove_commander_dispatch.py -+++ b/tools/prove_commander_dispatch.py -@@ -94,11 +94,12 @@ - at=RecordedEventTime(1), first_call_deadline=RecordedEventTime(121), - run=MechanicalOrderRun(), - ) -- RecordPlanePhase.task = CheckedPhaseTask( -+ checked_task = CheckedPhaseTask( - problem, Repo.of(project).github, - "Verify rotation starts a native unit and receives its first real reply.", - order, deadline_seconds=300, - ) -+ RecordPlanePhase.task = classmethod(lambda cls: checked_task) - from agent_comms.commander import rotate - - print(f"ROOT {root}", flush=True) diff --git a/phase-task-tests.patch b/phase-task-tests.patch deleted file mode 100644 index 556152f1ad..0000000000 --- a/phase-task-tests.patch +++ /dev/null @@ -1,36 +0,0 @@ ---- a/tests/test_plan_phases.py -+++ b/tests/test_plan_phases.py -@@ -48,7 +48,7 @@ - todo = create(commander, f"P{index}") - CommsOrderServeTool(problem=todo.id, serves=phase).apply(comms, caller) - todo = comms.goals.todos.get(todo.id) -- monkeypatch.setattr(phase, "task", task(todo)) -+ monkeypatch.setattr(phase, "task", classmethod(lambda cls, value=task(todo): value)) - todos.append(todo) - - record_done = tuple( -@@ -98,7 +98,7 @@ - whole = create(commander, "P999") - CommsOrderServeTool(problem=whole.id, serves=StoryPhase).apply(comms, caller) - whole = comms.goals.todos.get(whole.id) -- monkeypatch.setattr(StoryPhase, "task", task(whole)) -+ monkeypatch.setattr(StoryPhase, "task", classmethod(lambda cls: task(whole))) - assert not StoryPhase.done((subtask, whole)) - assert StoryPhase.done((subtask, accepted(whole))) - ---- a/tests/conftest.py -+++ b/tests/conftest.py -@@ -262,8 +262,13 @@ - from agent_comms.goals import RequestGoalOrder - from agent_comms.todos import PullRequests - from agent_comms.tool_caller import ThreadCaller -+ from agent_comms.plan_phases import PlanPhase -+ from agent_comms.work_dispatch import UnspecifiedPhaseTask - - monkeypatch.setattr(PullRequests, "read", lambda self, repo, state, order: ()) -+ # This fixture observes board state; it never opts into production task starts. -+ for phase in PlanPhase.members_with(PlanPhase): -+ monkeypatch.setattr(phase, "task", classmethod(lambda cls: UnspecifiedPhaseTask())) - - for created, name in enumerate(("lead", "worker"), 1): - comms.registry.declare(comms.registry.new_thread(name, frozenset({'comms'}), str(tmp_path), created_at=float(created))) diff --git a/phase7-task-declarations.patch b/phase7-task-declarations.patch deleted file mode 100644 index 7211d37ef2..0000000000 --- a/phase7-task-declarations.patch +++ /dev/null @@ -1,97 +0,0 @@ ---- a/src/agent_comms/plan_phases.py -+++ b/src/agent_comms/plan_phases.py -@@ -30,7 +30,9 @@ - - class PlanPhase(OrderPhase, declared_name=None): - after: ClassVar[tuple[type[PlanPhase], ...]] = () -- task: ClassVar[PhaseTask] = UnspecifiedPhaseTask() -+ @classmethod -+ def task(cls) -> PhaseTask: -+ return UnspecifiedPhaseTask() - - @classmethod - def serving(cls, todos: Iterable[Todo]) -> tuple[Todo, ...]: -@@ -38,7 +40,7 @@ - - @classmethod - def done(cls, todos: Iterable[Todo]) -> bool: -- return cls.task.done(cls.serving(todos)) -+ return cls.task().done(cls.serving(todos)) - - @classmethod - def ordered(cls, todos: Iterable[Todo]) -> bool: -@@ -80,6 +82,36 @@ - class RecordPlanePhase(PlanPhase): - after = (RetainedStorePhase,) - -+ @classmethod -+ def task(cls) -> PhaseTask: -+ import shlex -+ import sys -+ from pathlib import Path -+ from .change_report import FileLine -+ from .goals import OrderCheck -+ from .recorded_time import RecordedEventTime -+ from .todos import ApplicationOrderRun, PendingProblemOrder -+ from .work_dispatch import CheckedPhaseTask -+ -+ checkout = Path(__file__).resolve().parents[2] -+ check = "timeout 300 " + shlex.join((sys.executable, "-X", "faulthandler", -+ "-m", "pytest", "-o", "addopts=", "-n", "0", -+ "tests/test_commander_problem_orders.py", "tests/test_commander_run_rules.py")) -+ return CheckedPhaseTask( -+ problem="P900003", repo="OpenHCSDev/agent-comms", -+ text="Complete and verify the full native commander record plane.", -+ deadline_seconds=7200, -+ order=PendingProblemOrder( -+ id="record-plane-full-task", owner=FileLine(str(Path(__file__).with_name("todos.py")), 1066), -+ check=OrderCheck(str(checkout), check), -+ edits=("Finish native verdict, strike, transfer, delivery and build-bound closing through the existing Todo and Goal owners.",), -+ banned=("No private verdict or strike files; no fixture pass claimed as native application acceptance.",), -+ report="Report the delivered commit and the copied-root native record-plane observations with their build revisions.", -+ run=ApplicationOrderRun("On a copied root, a real send-back, missed deadline and false completion each produce one record; a second strike fires the unit once; missing Works and wrong-build evidence are refused; a delivered checked order cannot transfer; renaming a deletion target cannot pass; only real installed evidence closes the whole task."), -+ at=RecordedEventTime(1), first_call_deadline=RecordedEventTime(121), -+ ), -+ ) -+ - - class ReviewQueuePhase(MergedPhase): - after = (RecordPlanePhase,) -@@ -88,6 +120,37 @@ - - class ControlPlanePhase(PlanPhase): - after = (RecordPlanePhase,) -+ -+ @classmethod -+ def task(cls) -> PhaseTask: -+ import shlex -+ import sys -+ from pathlib import Path -+ from .change_report import FileLine -+ from .goals import OrderCheck -+ from .recorded_time import RecordedEventTime -+ from .todos import ApplicationOrderRun, PendingProblemOrder -+ from .work_dispatch import CheckedPhaseTask -+ -+ checkout = Path(__file__).resolve().parents[2] -+ check = "timeout 300 " + shlex.join((sys.executable, "-X", "faulthandler", -+ "-m", "pytest", "-o", "addopts=", "-n", "0", -+ "tests/test_work_dispatch.py", "tests/test_standing_rules.py", -+ "tests/test_storage_rules.py", "tests/test_turn_digests.py")) -+ return CheckedPhaseTask( -+ problem="P900007", repo="OpenHCSDev/agent-comms", -+ text="Complete and verify the full native commander control plane.", -+ deadline_seconds=7200, -+ order=PendingProblemOrder( -+ id="control-plane-full-task", owner=FileLine(str(Path(__file__).with_name("standing_rules.py")), 25), -+ check=OrderCheck(str(checkout), check), -+ edits=("Move the minute wake into Core scheduling; dispatch checked ready work through native order and lifecycle owners; derive standing rules, load, growth, storage and time from their original owners; delete the replaced script and private-state decisions.",), -+ banned=("No live-bus proof writes, phase-name dispatch map, second codec, private heartbeat files or fixture pass claimed as native application acceptance.",), -+ report="Name the started scratch unit, retained order, first reply and build; report native session parity, duplicate decisions, time totals and every removed competing path.", -+ run=ApplicationOrderRun("Scratch rotation starts a real Pi unit for a declared ready task and receives its first reply; a second rotation duplicates neither unit nor order; a full native commander session uses no script process; replay of a preserved hour produces each required decision once and UnitTime classes sum to elapsed time; stopping the commander ends its wake."), -+ at=RecordedEventTime(1), first_call_deadline=RecordedEventTime(121), -+ ), -+ ) - - - class CutoverPhase(PlanPhase): diff --git a/src/agent_comms/commander_parity/__init__.py b/src/agent_comms/commander_parity/__init__.py deleted file mode 100644 index 8d925713b5..0000000000 --- a/src/agent_comms/commander_parity/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Commander parity checks, discovered from their own declarations.""" diff --git a/src/agent_comms/commander_parity/rows.py b/src/agent_comms/commander_parity/rows.py deleted file mode 100644 index 6d80abe987..0000000000 --- a/src/agent_comms/commander_parity/rows.py +++ /dev/null @@ -1,191 +0,0 @@ -"""The rest of the matrix, explicitly unverified until real comparisons exist. - -Each declaration owns its identity and native integration phases. Owning a row -is not completing that phase; view phases never become phase prerequisites. -""" - -from ..parity_checks import UnimplementedParityCheck -from ..plan_phases import ( - CommanderPagePhase, ControlPlanePhase, CutoverPhase, OneOrderOwnerPhase, - RecordPlanePhase, ReviewQueuePhase, StoryPhase, -) - - -class CreateProblemParityCheck(UnimplementedParityCheck): - number = 1 - capability = "Create a problem with owner, order and deadline" - phases = (OneOrderOwnerPhase,) - - -class AssignOrderParityCheck(UnimplementedParityCheck): - number = 2 - capability = "Assign, reassign and drop an order" - phases = (OneOrderOwnerPhase,) - - -class DeliveredCommitParityCheck(UnimplementedParityCheck): - number = 3 - capability = "Complete only on the delivered commit" - phases = (OneOrderOwnerPhase, RecordPlanePhase) - - -class InstalledEvidenceParityCheck(UnimplementedParityCheck): - number = 4 - capability = "Close with installed evidence" - phases = (OneOrderOwnerPhase,) - - -class BoardParityCheck(UnimplementedParityCheck): - number = 5 - capability = "Board, overdue, silent and spinning" - phases = (OneOrderOwnerPhase,) - - -class BranchHygieneParityCheck(UnimplementedParityCheck): - number = 6 - capability = "Branch hygiene" - phases = (OneOrderOwnerPhase,) - - -class StrikeLimitParityCheck(UnimplementedParityCheck): - number = 7 - capability = "Strikes and the strike limit" - phases = (RecordPlanePhase,) - - -class VerdictParityCheck(UnimplementedParityCheck): - number = 8 - capability = "Verdicts on a unit's tool calls" - phases = (RecordPlanePhase,) - - -class OrderWorkParityCheck(UnimplementedParityCheck): - number = 11 - capability = "What a unit did during its order" - phases = (ReviewQueuePhase,) - - -class UnjudgedParityCheck(UnimplementedParityCheck): - number = 12 - capability = "What the commander has not judged yet" - phases = (ReviewQueuePhase,) - - -class PublicStoryParityCheck(UnimplementedParityCheck): - number = 13 - capability = "Public story and roasts" - phases = (StoryPhase,) - - -class ScheduledWakeParityCheck(UnimplementedParityCheck): - number = 14 - capability = "Heartbeat: next unit every minute" - phases = (ControlPlanePhase,) - - -class StandingRulesParityCheck(UnimplementedParityCheck): - number = 15 - capability = "Standing rules broken right now" - phases = (ControlPlanePhase,) - - -class RuleReviewParityCheck(UnimplementedParityCheck): - number = 16 - capability = "Rule review" - phases = (ControlPlanePhase,) - - -class LoadForkingParityCheck(UnimplementedParityCheck): - number = 17 - capability = "Load and forking" - phases = (ControlPlanePhase,) - - -class GrowthAlarmParityCheck(UnimplementedParityCheck): - number = 18 - capability = "Growth alarm" - phases = (ControlPlanePhase,) - - -class StorageHygieneParityCheck(UnimplementedParityCheck): - number = 19 - capability = "Storage hygiene" - phases = (ControlPlanePhase,) - - -class OrderOnlyContactParityCheck(UnimplementedParityCheck): - number = 20 - capability = "Commander reaches units only through orders" - phases = (ControlPlanePhase,) - - -class ConfigurationParityCheck(UnimplementedParityCheck): - number = 21 - capability = "Paths, repositories and commander identity" - phases = (CutoverPhase,) - - -class CommanderPageParityCheck(UnimplementedParityCheck): - number = 22 - capability = "Seeing the fleet: board, strikes, queue, story and alarms" - phases = (CommanderPagePhase,) - - -class ApplicationRunParityCheck(UnimplementedParityCheck): - number = 23 - capability = "An order states the user-visible run that closes it" - phases = (RecordPlanePhase,) - - -class TracedTransferParityCheck(UnimplementedParityCheck): - number = 24 - capability = "Reassigning past two owners needs the commander's trace" - phases = (RecordPlanePhase,) - - -class DecisionAlertsParityCheck(UnimplementedParityCheck): - number = 26 - capability = "Alerts carry only decisions" - phases = (ControlPlanePhase,) - - -class MomentumParityCheck(UnimplementedParityCheck): - number = 27 - capability = "Momentum prediction per unit compared with the outcome" - phases = (CommanderPagePhase,) - - -class MeasuredLoadParityCheck(UnimplementedParityCheck): - number = 28 - capability = "Measured load and self-report" - phases = (ControlPlanePhase,) - - -class FinishedWorkTransferParityCheck(UnimplementedParityCheck): - number = 29 - capability = "No handoff of finished, checked work" - phases = (RecordPlanePhase,) - - -class BuildBoundProofParityCheck(UnimplementedParityCheck): - number = 30 - capability = "A proof is bound to the build it ran on" - phases = (RecordPlanePhase,) - - -class DeletionTargetParityCheck(UnimplementedParityCheck): - number = 31 - capability = "An order states its intent as a deletion target" - phases = (RecordPlanePhase,) - - -class UnitTimeParityCheck(UnimplementedParityCheck): - number = 32 - capability = "Where units' time goes, classified" - phases = (CommanderPagePhase, ControlPlanePhase) - - -class OrderProgressParityCheck(UnimplementedParityCheck): - number = 33 - capability = "Order progress beside its deadline; the unit sees no due time" - phases = (OneOrderOwnerPhase, ControlPlanePhase) diff --git a/src/agent_comms/commander_parity/text.py b/src/agent_comms/commander_parity/text.py deleted file mode 100644 index 26bce229d2..0000000000 --- a/src/agent_comms/commander_parity/text.py +++ /dev/null @@ -1,44 +0,0 @@ -"""The native field codec and current commander's real wording refusals.""" - -from ..parity_checks import TextParityCheck, TextSample -from ..plan_phases import RecordPlanePhase -from ..todos import OrderText, PlainText, StrikeReason - - -class PlainWordsParityCheck(TextParityCheck): - number = 9 - capability = "Plain words in verdicts and strikes" - phases = (RecordPlanePhase,) - samples = ( - TextSample(PlainText, "The file now reads the order from its original row.", True), - TextSample(PlainText, "The receipt certifies the authority.", False), - TextSample(PlainText, "Remove the quoted `authority` from that message.", True), - TextSample(StrikeReason, "The unit changed another file and left the ordered repair broken.", True), - TextSample(StrikeReason, "The edit broke.", False), - ) - - -class OrderWordingParityCheck(TextParityCheck): - number = 10 - capability = "Refuse soft or vague orders" - phases = (RecordPlanePhase,) - samples = ( - TextSample(OrderText, "Repair the existing reader and remove the duplicate store.", True), - TextSample(OrderText, "Could you repair the existing reader when you get a chance?", False), - TextSample(OrderText, "Consider keeping both stores if possible.", False), - TextSample(OrderText, "Remove the literal `please` from the help message now.", True), - ) - - -class UnitWordingParityCheck(TextParityCheck): - number = 25 - capability = "Unit wording gets a correction, not a strike" - phases = (RecordPlanePhase,) - samples = ( - TextSample(StrikeReason, - "The unit wrote `canonical` instead of explaining the file that actually failed.", - False), - TextSample(StrikeReason, - "The unit edited `/project/reader.py` but left the requested repair completely broken.", - True), - ) diff --git a/src/agent_comms/commander_parity_cli.py b/src/agent_comms/commander_parity_cli.py deleted file mode 100644 index 0de76d13e9..0000000000 --- a/src/agent_comms/commander_parity_cli.py +++ /dev/null @@ -1,110 +0,0 @@ -"""Run comparisons through the actual application, with isolated script configuration.""" - -from __future__ import annotations - -import os -import subprocess -import sys -from dataclasses import dataclass -from pathlib import Path -from typing import Annotated - -from .check_root import CheckRoot -from .cli_commands import CliCommand, option -from .commander.config import CommanderConfig -from .comms import Comms -from .field_codec import FieldCodec, PathText -from .launch_runtime import RestartEnvironment, ThreadLaunch -from .parity_checks import CommanderComparison, CommanderParityCheck, CommanderParityReport - - -@dataclass(frozen=True) -class ParityMatrix: - """Decode the existing Markdown matrix once; never identify a row by its title.""" - - rows: tuple[int, ...] - - @classmethod - def read(cls, path: Path) -> ParityMatrix: - text = path.read_text() - try: - section = text.split("## Parity matrix\n", 1)[1].split("\n## ", 1)[0] - except IndexError as error: - raise ValueError(f"No parity matrix in {path}") from error - rows = [] - for line in section.splitlines(): - if not line.startswith("|"): - continue - first = line.split("|")[1].strip() - if first == "#" or first and set(first) <= {"-", ":"}: - continue - rows.append(int(first)) - if not rows or any(number <= 0 for number in rows) or len(set(rows)) != len(rows): - raise ValueError("A parity matrix needs unique positive row numbers") - return cls(tuple(rows)) - - def compare(self, context: CommanderComparison) -> CommanderParityReport: - checks = CommanderParityCheck.members_with(CommanderParityCheck) - numbers = tuple(check.number for check in checks) - if len(set(numbers)) != len(numbers) or set(numbers) - set(self.rows): - raise ValueError("Parity declarations duplicate a row or name a row outside the matrix") - comparisons = tuple( - check.compare(context) for check in sorted(checks, key=lambda check: check.number)) - complete = {row.check.number for row in comparisons - if row.coverage.complete and row.observations} - return CommanderParityReport( - comparisons, tuple(number for number in self.rows if number not in complete)) - - -@dataclass(frozen=True, kw_only=True) -class CommanderParityCliCommand(CliCommand, declared_name="commander-parity"): - help = "Compare native/current commander behaviour in an isolated saved-history root" - plan: Annotated[Path, PathText] = option(help="Markdown plan containing the numbered parity matrix") - into: Annotated[Path, PathText] = option(help="New directory under your agent scratch directory") - - def apply(self, ctx: Comms) -> CommanderParityReport: - config = CommanderConfig.installed() - if config.root.resolve() != ctx.root.resolve(): - raise ValueError("The current commander configuration belongs to another root") - with CheckRoot(ctx, (config.commander,), self.into).open() as copied: - home = copied.root / "home" - config.model_copy(update={ - "root": copied.root, "state": copied.root / "state", - "board": copied.root / "board.md", "narrative": copied.root / "narrative.md", - "scratch": copied.root / "scratch", "disk": copied.root, - }).write(home) - environment = ThreadLaunch.strip(dict(os.environ)) - RestartEnvironment.strip(environment, "root") - environment.update(HOME=str(home), PYTHONPATH=os.pathsep.join(sys.path)) - # A separate interpreter prevents the current scripts' import-time - # globals from ever binding this check to the original live root. - result = subprocess.run( - (sys.executable, "-m", "agent_comms.cli", "--root", str(copied.root), - "commander-parity-observe", "--plan", str(self.plan.resolve())), - env=environment, capture_output=True, text=True, timeout=60, - ) - try: - report = FieldCodec.decode(CommanderParityReport, FieldCodec.parse(result.stdout)) - except ValueError as error: - raise ValueError( - f"Parity worker produced no report (exit {result.returncode}): " - f"{result.stdout}\n{result.stderr}" - ) from error - if result.returncode != report.exit_code(): - raise ValueError(f"Parity worker exit disagrees with its report: {result.stderr}") - return report - - -@dataclass(frozen=True, kw_only=True) -class ObserveCommanderParityCliCommand(CliCommand, declared_name="commander-parity-observe"): - help = "Observe declared comparisons; commander-parity supplies an isolated root" - plan: Annotated[Path, PathText] = option(help="Markdown plan containing the numbered parity matrix") - - def apply(self, ctx: Comms) -> CommanderParityReport: - # This is the real current commander's function, not a reimplementation - # of its refusals or a mocked transport. Its globals are isolated above. - from .commander import order - - if order.COMMS.root.resolve() != ctx.root.resolve(): - raise ValueError("The current scripts opened another root; no comparison ran") - return ParityMatrix.read(self.plan).compare(CommanderComparison(order.refuse)) diff --git a/src/agent_comms/parity_checks.py b/src/agent_comms/parity_checks.py deleted file mode 100644 index b9e9b00094..0000000000 --- a/src/agent_comms/parity_checks.py +++ /dev/null @@ -1,196 +0,0 @@ -"""Declared commander comparisons and their actual observations. - -A missing comparison is unverified, never a passing feature. The phase owns this -check family; row identity, numbering and reporting derive from its declarations. -""" - -from __future__ import annotations - -from abc import abstractmethod -from collections.abc import Callable, Iterable -from dataclasses import dataclass -from typing import TYPE_CHECKING, ClassVar - -from .cli_result import CliResult -from .declared_family import DeclaredFamily -from .field_codec import FieldCodec, projected -from .plan_phases import PlanPhase - -if TYPE_CHECKING: - from .todos import OrderWords, Todo - from .work_dispatch import PhaseTask - - -class ProbeObservation(DeclaredFamily, affix="Observation"): - accepted: ClassVar[bool] - - -@dataclass(frozen=True) -class AcceptedObservation(ProbeObservation): - accepted = True - - -@dataclass(frozen=True) -class RefusedObservation(ProbeObservation): - reason: str - accepted = False - - -@dataclass(frozen=True) -class TextSample: - family: type[OrderWords] - text: str - accepted: bool - - -class ParityObservation(DeclaredFamily, affix="Observation"): - @property - @abstractmethod - def passed(self) -> bool: ... - - -@dataclass(frozen=True) -class TextProbeResult(ParityObservation): - family: str - text: str - expected_acceptance: bool - native: ProbeObservation - current: ProbeObservation - - @projected(view="wire") - def passed(self) -> bool: - return self.native.accepted == self.current.accepted == self.expected_acceptance - - -@dataclass(frozen=True) -class CommanderComparison: - """The current script's real boundary, supplied only in an isolated process.""" - - refuse: Callable[[str, type[OrderWords]], None] - - def text(self, sample: TextSample) -> TextProbeResult: - try: - FieldCodec.decode(sample.family, sample.text) - except ValueError as error: - native = RefusedObservation(str(error)) - else: - native = AcceptedObservation() - try: - self.refuse(sample.text, sample.family) - except SystemExit as error: - current = RefusedObservation(str(error)) - else: - current = AcceptedObservation() - return TextProbeResult(sample.family.__name__, sample.text, sample.accepted, - native, current) - - -class ComparisonCoverage(DeclaredFamily, affix="Coverage"): - complete: ClassVar[bool] - - -@dataclass(frozen=True) -class PartialCoverage(ComparisonCoverage): - missing: tuple[str, ...] - complete = False - - def __post_init__(self) -> None: - if not self.missing: - raise ValueError("A partial comparison names what it did not exercise") - - -@dataclass(frozen=True) -class CompleteCoverage(ComparisonCoverage): - complete = True - - -class CommanderParityCheck( - DeclaredFamily, affix="ParityCheck", discovery_package="agent_comms.commander_parity" -): - number: ClassVar[int] - phases: ClassVar[tuple[type[PlanPhase], ...]] - capability: ClassVar[str] - coverage: ClassVar[ComparisonCoverage] - - @classmethod - @abstractmethod - def compare(cls, context: CommanderComparison) -> ParityRowResult: ... - - @classmethod - def task(cls) -> PhaseTask: - from .work_dispatch import UnspecifiedPhaseTask - - return UnspecifiedPhaseTask() - - @classmethod - def done(cls, todos: Iterable[Todo]) -> bool: - # Probe agreement alone never accepts a row. Its checked task must also - # have passed and received the existing order's application acceptance. - return cls.coverage.complete and cls.task().done(todos) - - @classmethod - def all_done(cls, todos: Iterable[Todo]) -> bool: - checks = cls.members_with(cls) - history = tuple(todos) - return bool(checks) and all(check.done(history) for check in checks) - - -class UnimplementedParityCheck(CommanderParityCheck, declared_name=None): - coverage = PartialCoverage(("The native/current workflow comparison is not implemented.",)) - - @classmethod - def compare(cls, context: CommanderComparison) -> ParityRowResult: - return ParityRowResult(cls, ()) - - -class TextParityCheck(CommanderParityCheck, declared_name=None): - samples: ClassVar[tuple[TextSample, ...]] - coverage = PartialCoverage(( - "Only native field decoding and current script refusal samples ran; " - "the row's complete verdict/order/correction workflow has not run.", - )) - - @classmethod - def compare(cls, context: CommanderComparison) -> ParityRowResult: - return ParityRowResult(cls, tuple(context.text(sample) for sample in cls.samples)) - - -@dataclass(frozen=True) -class ParityRowResult: - check: type[CommanderParityCheck] - observations: tuple[ParityObservation, ...] - - @projected(view="wire") - def number(self) -> int: - return self.check.number - - @projected(view="wire") - def coverage(self) -> ComparisonCoverage: - return self.check.coverage - - @projected(view="wire") - def phases(self) -> tuple[type[PlanPhase], ...]: - return self.check.phases - - @projected(view="wire") - def passed(self) -> bool: - return bool(self.observations) and all(item.passed for item in self.observations) - - -@dataclass(frozen=True) -class CommanderParityReport(CliResult): - comparisons: tuple[ParityRowResult, ...] - unverified_rows: tuple[int, ...] - - @projected(view="wire") - def passed(self) -> bool: - return bool(self.comparisons) and not self.unverified_rows and all( - item.passed for item in self.comparisons) - - @projected(view="wire") - def failing_rows(self) -> tuple[int, ...]: - return tuple(item.check.number for item in self.comparisons - if item.observations and not item.passed) - - def exit_code(self) -> int: - return 0 if self.passed else 1 diff --git a/tests/test_commander_parity.py b/tests/test_commander_parity.py deleted file mode 100644 index a99a489b3f..0000000000 --- a/tests/test_commander_parity.py +++ /dev/null @@ -1,57 +0,0 @@ -import json -import os -import subprocess -import sys -from pathlib import Path - -import pytest - -from agent_comms.commander_parity_cli import ParityMatrix -from agent_comms.field_codec import FieldCodec -from agent_comms.parity_checks import CommanderParityCheck, CommanderParityReport - - -PLAN = Path(__file__).resolve().parents[1] / "plans/commander-native-plan.md" - - -def test_actual_native_current_comparisons_never_pass_unverified_rows(commander_script): - order, _ = commander_script - environment = {**os.environ, "PYTHONPATH": str(PLAN.parents[1] / "src")} - completed = subprocess.run( - (sys.executable, "-m", "agent_comms.cli", "--root", str(order.COMMS.root), - "commander-parity-observe", "--plan", str(PLAN)), - env=environment, capture_output=True, text=True, timeout=30, - ) - assert completed.stdout, completed.stderr - report = FieldCodec.decode(CommanderParityReport, json.loads(completed.stdout)) - declarations = CommanderParityCheck.members_with(CommanderParityCheck) - assert declarations - assert {row.check for row in report.comparisons} == set(declarations) - observed = tuple(row for row in report.comparisons if row.observations) - assert observed and all(row.passed for row in observed) - assert all(row.phases == row.check.phases and row.phases for row in report.comparisons) - assert {check.number for check in declarations} == set(ParityMatrix.read(PLAN).rows) - assert not CommanderParityCheck.all_done(()) - complete_numbers = {row.check.number for row in report.comparisons - if row.coverage.complete and row.observations} - assert report.unverified_rows == tuple( - number for number in ParityMatrix.read(PLAN).rows if number not in complete_numbers) - assert report.unverified_rows - assert not report.passed - assert report.failing_rows == () - assert completed.returncode == report.exit_code() == 1 - assert FieldCodec.decode(CommanderParityReport, report.to_wire()) == report - - -def test_matrix_numbers_are_not_titles_and_invalid_or_duplicate_numbers_are_refused(tmp_path): - plan = tmp_path / "plan.md" - plan.write_text("## Parity matrix\n\n| # | Capability |\n|---|---|\n" - "| 9 | Renamed freely |\n| 10 | Another name |\n") - assert ParityMatrix.read(plan).rows == (9, 10) - plan.write_text("## Parity matrix\n\n| # | Capability |\n|---|---|\n" - "| 9 | First |\n| 9 | Second |\n") - with pytest.raises(ValueError, match="unique positive"): - ParityMatrix.read(plan) - plan.write_text("## Parity matrix\n\n| # | Capability |\n|---|---|\n| x | Invalid |\n") - with pytest.raises(ValueError): - ParityMatrix.read(plan) diff --git a/work-dispatch-followup.patch b/work-dispatch-followup.patch deleted file mode 100644 index af5f18949a..0000000000 --- a/work-dispatch-followup.patch +++ /dev/null @@ -1,36 +0,0 @@ -diff --git a/src/agent_comms/work_dispatch.py b/src/agent_comms/work_dispatch.py -index 67cccd250..e002df055 100644 ---- a/src/agent_comms/work_dispatch.py -+++ b/src/agent_comms/work_dispatch.py -@@ -7,6 +7,7 @@ acceptance check. Failed startup state stays retained for explicit disposition. - - from __future__ import annotations - -+import math - import subprocess - import time - from abc import ABC, abstractmethod -@@ -116,8 +117,8 @@ class CheckedPhaseTask(PhaseTask): - instruction = "Core starts the declared checked task in its own unit checkout" - - def __post_init__(self) -> None: -- if not 0 < self.deadline_seconds <= 24 * 60 * 60: -- raise ValueError("A phase task needs a positive deadline of at most one day.") -+ if not math.isfinite(self.deadline_seconds) or self.deadline_seconds <= 0: -+ raise ValueError("A phase task needs a finite positive deadline interval.") - - def start(self, comms: Comms, commander: Thread, phase: type[PlanPhase]) -> None: - from .goal_actions import CreateProblemGoalAction -@@ -126,9 +127,9 @@ class CheckedPhaseTask(PhaseTask): - history = comms.goals.todos.history_for_commander(commander.incarnation) - if not phase.ready(history) or phase.ordered(history): - return -- # An existing unowned row belongs to UnownedWork, which reissues that -- # row's actual order instead of replacing it with a phase template. -- if any(todo.state.open for todo in phase.serving(history)): -+ # This task's existing row belongs to UnownedWork. Other phase subtasks -+ # neither supply this task's check nor replace its explicit identity. -+ if any(todo.id == self.problem for todo in history): - return - now = time.time() - order = replace(self.order.reissued(now), serves=phase) From 778cc508f97adef97406b6bc70b91c0b1e2d5096 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 15:08:36 -0400 Subject: [PATCH 07/12] Keep #1928 to plan phases and order claims The check-history clock copy goes with the parity work in its own PR; its missing source clocks are the desktop's #1940. Reword two comments that used banned vocabulary. Co-Authored-By: Claude Opus 5.5 --- src/agent_comms/work_dispatch.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/agent_comms/work_dispatch.py b/src/agent_comms/work_dispatch.py index 67cccd2507..7d105589e0 100644 --- a/src/agent_comms/work_dispatch.py +++ b/src/agent_comms/work_dispatch.py @@ -24,7 +24,7 @@ @dataclass(frozen=True) class WorkDispatch: - """One checked native order: allocate its unit, publish custody, then start it.""" + """One checked native order: allocate its unit, record its claim, then start it.""" action: ProblemOrderGoalAction revision: int @@ -62,7 +62,7 @@ def start(self, comms: Comms) -> Thread: replace(candidate, session_file=location.read_key), StoppedThreadStatus(), ) # A failed publication leaves a stopped unit and its checkout, not an - # untracked running task. A failed/uncertain start never releases custody. + # untracked running task. A failed/uncertain start never releases the claim. comms.goals.update_goal(child.name, self.action) comms.owners.start(child.name) return comms.registry.require(child.name) From 60f63372fb5c853a05404c1ea8f9c60dbb803b42 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 15:09:37 -0400 Subject: [PATCH 08/12] Keep the plan-claims report out of the tree Co-Authored-By: Claude Opus 5.5 --- docs/refactor/shared-plan-claims-A.md | 222 -------------------------- 1 file changed, 222 deletions(-) delete mode 100644 docs/refactor/shared-plan-claims-A.md diff --git a/docs/refactor/shared-plan-claims-A.md b/docs/refactor/shared-plan-claims-A.md deleted file mode 100644 index 1d55cf68d8..0000000000 --- a/docs/refactor/shared-plan-claims-A.md +++ /dev/null @@ -1,222 +0,0 @@ -# Shared plan claims: A dependency checkpoint - -The original A dependency design below is superseded by the A2 correction at -this file's end: views wait on their source phases, not cutover. A2's completion -rule is now corrected; its executable parity gate remains in progress. B has not -started. - -Owner: pi-plan-claims. Branch: feat/shared-plan-claims. Base: 011084fc5. -This checkpoint changes commander-native dependency declarations only. Shared -claims are the next, separate commit. Nothing is installed or merged here. - -## Change and limit - -`CutoverPhase.after` now names `RecordPlanePhase`, `ReviewQueuePhase` and -`ControlPlanePhase`. `StoryPhase` and `CommanderPagePhase` follow cutover, whose -prerequisites own their source records. No phase depends on either view. -`SkillPhase` continues to follow cutover. - -**There is no declared executable commander parity gate.** The three requirements -in `plans/commander-native-plan.md:199-203` are prose: native owners on main, a -shadow session comparing native behaviour to scripts, and no private-state writes. -No phase or check implements that comparison. `PlanPhase.done()` reads order -closure, not parity. This change does not satisfy or encode the missing parity -prerequisite and does not authorize deleting the scripts. - -Consequently there is no real parity-check output from which to enumerate failing -matrix rows for phases 3, 4 and 7, map each to a check class, or assign each -measured failure. Those results are **unavailable**, not passing. The commander -must identify an existing external check if one was intended, or commission its -implementation separately. No substitute parity check was invented in A. - -## Actual Core output, before and after - -Both reads used this checkout's `Todo.select`, `PlanPhase.ready` and -`PlanPhase.ordered` against a SQLite read-only connection to -`/home/ts/.agent-comms/todos.sqlite3`, selecting `creator='claude-code'`. Each -returned 214 retained orders. No synthetic completion state was used here. -This is the proposed Core source evaluating real retained state, not an installed -runtime update. "Next startable" is the same ready-and-not-ordered predicate -used by `PlanIdle.violations`. - -Before: - -```text -BEFORE: live commander claude-code; todos: 214 -Core ready: record_plane, commander_page -Core next startable (ready, no open order): commander_page -record_plane done= False ordered= True after= retained_store open= P107,P167,P195,P197,P198,P204,P208,P213 -review_queue done= True ordered= False after= record_plane open= -story done= True ordered= False after= review_queue open= -commander_page done= False ordered= False after= story open= -control_plane done= False ordered= True after= record_plane open= P100,P199 -cutover done= False ordered= True after= review_queue,story,commander_page,control_plane open= P207 -skill done= False ordered= False after= cutover open= -``` - -After: - -```text -AFTER: live commander claude-code; todos: 214 -Core ready: record_plane -Core next startable (ready, no open order): (none) -record_plane done= False ordered= True after= retained_store open= P107,P167,P195,P197,P198,P204,P208,P213 -review_queue done= True ordered= False after= record_plane open= -control_plane done= False ordered= True after= record_plane open= P100,P199 -cutover done= False ordered= True after= record_plane,review_queue,control_plane open= P207 -story done= True ordered= False after= cutover open= -commander_page done= False ordered= False after= cutover open= -skill done= False ordered= False after= cutover open= -``` - -The retained state contradicts the initial description that nobody started the -story: Core reports `story` and `review_queue` done. Its unfinished native work -is in record plane and control plane. These are order-state observations, not -parity failures. - -## Source ownership evidence - -Reused refactor-audit's `audit.findings.Package.load` AST substrate at the base: -379 production modules, 476 test modules and 34 tool modules parsed, no omissions. -The first attempt to use NRA's parser could not import `tree_sitter` in the -available Python environment; the existing refactor-audit parser succeeded. - -- `plan_phases.py` alone declares phase classes, dependency writes, completion, - readiness and ordering decisions. No second dependency list was introduced. -- Production consumers: `standing_rules.py` (`PlanIdle`), `todos.py` - (`ProblemOrder.serves` and serve command), `tools.py` (serve tool), - `commander/order.py` (order parsing and serve verb). -- Test consumers at the base: `test_standing_rules.py` and `test_tool_replies.py`. -- Before: cutover depended on story and commander page; page depended on story. - After: cutover depends on the three native phases; both views depend on cutover; - no dependency names either view. Wire names and all consumer APIs are unchanged. -- Dynamic membership is derived through `DeclaredFamily.members_with`; static - name enumeration does not prove absence of arbitrary external runtime mutation. -- Search for `parity` across production, tests, tools and CI found no commander - comparison check. Unrelated Pi prompt-byte and entry-index parity tests do not - compare commander behaviour. - -## Verification - -The existing native test environment for this checkout was verified and reused; -no runtime package was built or installed. One sequential, bounded test run: - -```text -PYTHONPATH=src timeout 120 /home/ts/code/projects/agent-comms/.venv/bin/python -X faulthandler -m pytest --native-cache /home/ts/.cache/agent-comms/native -n 0 --no-cov -p no:cacheprovider -q tests/test_plan_phases.py tests/test_standing_rules.py -k 'cutover_waits or plan_idle' -2 passed, 16 deselected, 14 warnings in 4.63s -``` - -The new family test uses real checked orders and the serve tool. It verifies -cutover waits for each native prerequisite independently, unfinished views cannot -block cutover, and the views and skill become ready after cutover. The existing -PlanIdle test still passes. Warnings were an existing unknown pytest timeout -option and pytest cleanup trying to retire unrelated busy DQDock snapshot paths; -no manual cleanup of those paths was attempted. - -`/home/ts/bin/agent-resource-check` is absent on this machine. Before this small -run, available RAM was 3.7 GiB and disk free was 26 GiB. No parallel workers or -large build was launched. - -## Next identity choice - -B will use one shared phase family with an unnamed parent per plan. Existing -commander-native phase names remain stable; the remaining-slop declarations get -unambiguous names derived from their classes. A plan parent groups discovery and -rendering without copying phase readiness/claim behaviour into a second family. -Markdown/board references identify declarations, never titles or new UUIDs. - -Assumed: the missing parity comparison is reported, not invented; its absence -blocks parity acceptance and script deletion, but does not prevent reviewing this -dependency-only checkpoint. Both machines can write the approved shared claim -store. pi-cn-phase7 owns dispatch and will propose any checked-task capability; -this checkpoint does not fabricate runnable task bodies from phase names. - -## A2: corrected view prerequisites and false story completion - -Owner correction 1753: story waits on record plane; commander page waits on -record plane and control plane. Neither waits on cutover, and no phase waits -on either view. The earlier dependency on cutover was my mistake. - -Actual source evaluation of the same 214 retained commander orders: - -```text -Before A2 (A commit): -Core ready: record_plane -Core next startable (ready, no open order): (none) -story done= True after= cutover -commander_page done= False after= cutover - -After the A2 view dependency correction: -Core ready: record_plane -Core next startable (ready, no open order): (none) -cutover done= False after= record_plane,review_queue,control_plane -story done= True after= record_plane -commander_page done= False after= record_plane,control_plane -``` - -Story's only serving order is P149, revision 8, installed/done, order -`b258d208cd26`, serving `story`. Its text is: - -> every send-back wakes all units because verdicts declare no audience - -Its actual Works record is: - -> Merged to main as 74176e31a (#1678, delivered commit f72e47943); closed on merge evidence by owner directive 1520, run not re-observed: a send-back reaches only its unit and its order's thread, so other units take no turn for it, while a verdict every unit must act on still reaches all of them - -This is legitimately related to the story phase, but it is not acceptance of the -whole phase's order/fleet story tools. The wrong rule is `PlanPhase.done`: all -currently known serving subtasks closed does not mean the whole phase passed. -The retained record was not edited or reopened. The completion-rule repair will -use the phase's explicitly declared full-phase checked task, not infer completion -from this serving set. pi-cn-phase7 owns that task type and its accepted-order -semantics; I own its use in PlanPhase. Owner-confirmed review queue delivery is -PR #1654, merge 41e4ec7319432755d75dcd8dbbab35839a4eb005. - -The expanded family test confirms story can start after record plane while -control plane and cutover are still open; page starts after record and control -plane without waiting for cutover. Both focused tests passed (2 passed, 16 -deselected, 1 pre-existing timeout-option warning, 5.37 seconds). The run used -`--basetemp /home/ts/.cache/agent-scratch/pi-plan-claims/a2-view-check/pytest`; -that owned disposable directory is retired after the run. - -A2's executable per-matrix-row comparison is newly authorized. It is not yet -implemented. No failing parity rows or passing parity acceptance are asserted -by this view-dependency checkpoint. B remains after A2. - -### A2 completion-rule repair - -Integrated `work_dispatch.py` exactly from pi-cn-phase7's d123d18e2 (Git blob -67cccd2507bf12f5f69748a81c10675677912309), its imports/task declaration, and the -`ProblemOrder.accepted` declarations. No phase-7 enforcement or trace changes -were imported. pi-cn-phase7 retains ownership of dispatch implementation and its -verification. No production task briefs or automatic launches were added here. - -`PlanPhase.done` now asks its declared task about acceptance of the tasks serving -that phase. An unspecified full-phase task returns false. `CheckedPhaseTask` -matches the exact declared problem identity and requires a closed installed -order; a withdrawn/folded order is not accepted. Review queue is explicitly a -previously delivered phase, owner-confirmed as #1654, not inferred from a closed -subset of its subtasks. The Markdown plan now describes this same rule. - -Actual Core output after the completion-rule correction: - -```text -A2 completion correction; retained commander orders: 214 -Core ready: record_plane -Core next startable (ready, no open order): (none) -review_queue done= True after= record_plane -cutover done= False after= record_plane,review_queue,control_plane -story done= False after= record_plane -commander_page done= False after= record_plane,control_plane -P149 accepted= True -Story task= UnspecifiedPhaseTask -``` - -P149 is still legitimately closed; the story phase no longer falsely reads done. -Three focused tests passed (3 passed, 16 deselected, one existing timeout-option -warning, 7.26 seconds), covering phase/view prerequisites, exact whole-phase -acceptance versus closed subtasks/withdrawal/wrong-phase records, and PlanIdle. -The owned run directory was -`/home/ts/.cache/agent-scratch/pi-plan-claims/a2-completion-check/pytest`; disposable -run output is removed after acceptance of this check. No original records changed. -The full parity comparison remains unimplemented, so A2 as a whole is unfinished. From 984dd38534304aaf2e7d03e3321940b29859df0f Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 15:11:54 -0400 Subject: [PATCH 09/12] Format work_dispatch.py Co-Authored-By: Claude Opus 5.5 --- src/agent_comms/work_dispatch.py | 54 ++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 13 deletions(-) diff --git a/src/agent_comms/work_dispatch.py b/src/agent_comms/work_dispatch.py index 7d105589e0..b1a45bc934 100644 --- a/src/agent_comms/work_dispatch.py +++ b/src/agent_comms/work_dispatch.py @@ -42,7 +42,10 @@ def start(self, comms: Comms) -> Thread: storage = comms.registry.storage_rule checkout = storage.order_checkout(name, self.action.problem) candidate = comms.registry.new_thread( - name, frozenset(), str(checkout), model=defaults.model, + name, + frozenset(), + str(checkout), + model=defaults.model, thinking_level=defaults.thinking_level, ) self.action.request_ref(GoalActionContext(comms.goals, candidate)) @@ -50,16 +53,32 @@ def start(self, comms: Comms) -> Thread: CheckoutStorageEffect(checkout).check(storage, candidate) subprocess.run( ("git", "-C", self.action.order.check.directory, "fetch", "origin", "main"), - check=True, capture_output=True, text=True, timeout=60, + check=True, + capture_output=True, + text=True, + timeout=60, ) subprocess.run( - ("git", "-C", self.action.order.check.directory, "worktree", "add", "-b", - f"auto/{name}", str(checkout), "origin/main"), - check=True, capture_output=True, text=True, timeout=60, + ( + "git", + "-C", + self.action.order.check.directory, + "worktree", + "add", + "-b", + f"auto/{name}", + str(checkout), + "origin/main", + ), + check=True, + capture_output=True, + text=True, + timeout=60, ) location = candidate.runtime.new_location(comms.root, checkout) child = comms.registry.declare( - replace(candidate, session_file=location.read_key), StoppedThreadStatus(), + replace(candidate, session_file=location.read_key), + StoppedThreadStatus(), ) # A failed publication leaves a stopped unit and its checkout, not an # untracked running task. A failed/uncertain start never releases the claim. @@ -77,9 +96,12 @@ def reissue(cls, comms: Comms, commander: Thread, todo: Todo) -> None: if current.holder or not current.pending: return action = OrderGoalAction( - problem=current.id, expected_revision=current.revision, - order=current.order.reissued(time.time()), request=current.goal.request(), - deadline=current.deadline, commander=commander.name, + problem=current.id, + expected_revision=current.revision, + order=current.order.reissued(time.time()), + request=current.goal.request(), + deadline=current.deadline, + commander=commander.name, ) cls(action, revision=current.revision).start(comms) @@ -133,12 +155,18 @@ def start(self, comms: Comms, commander: Thread, phase: type[PlanPhase]) -> None now = time.time() order = replace(self.order.reissued(now), serves=phase) action = CreateProblemGoalAction( - problem=self.problem, repo=self.repo, text=self.text, order=order, - request=order.request(commander, (self.problem,)), commander=commander.name, + problem=self.problem, + repo=self.repo, + text=self.text, + order=order, + request=order.request(commander, (self.problem,)), + commander=commander.name, deadline=RecordedEventTime(now + self.deadline_seconds), ) WorkDispatch(action, revision=0).start(comms) def done(self, todos: Iterable[Todo]) -> bool: - return any(todo.id == self.problem and not todo.state.open and todo.order.accepted - for todo in todos) + return any( + todo.id == self.problem and not todo.state.open and todo.order.accepted + for todo in todos + ) From 926878002a924e4fa396b4faee907334612dcf0a Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 16:23:50 -0400 Subject: [PATCH 10/12] Break phase task import cycle at its shared capability owner --- src/agent_comms/phase_tasks.py | 33 +++++++++++++++++++ src/agent_comms/plan_phases.py | 3 +- src/agent_comms/todos.py | 4 +++ src/agent_comms/work_dispatch.py | 46 ++++++++------------------- tests/test_phase_task_dependencies.py | 27 ++++++++++++++++ 5 files changed, 78 insertions(+), 35 deletions(-) create mode 100644 src/agent_comms/phase_tasks.py create mode 100644 tests/test_phase_task_dependencies.py diff --git a/src/agent_comms/phase_tasks.py b/src/agent_comms/phase_tasks.py new file mode 100644 index 0000000000..31d380dfd8 --- /dev/null +++ b/src/agent_comms/phase_tasks.py @@ -0,0 +1,33 @@ +"""Phase task capabilities, independent of order storage and work dispatch.""" + +from __future__ import annotations + +from abc import ABC, abstractmethod +from collections.abc import Iterable +from typing import TYPE_CHECKING, ClassVar + +if TYPE_CHECKING: + from .comms import Comms + from .plan_phases import PlanPhase + from .threads import Thread + from .todos import Todo + + +class PhaseTask(ABC): + instruction: ClassVar[str] + + @abstractmethod + def start(self, comms: Comms, commander: Thread, phase: type[PlanPhase]) -> None: ... + + @abstractmethod + def done(self, todos: Iterable[Todo]) -> bool: ... + + +class UnspecifiedPhaseTask(PhaseTask): + instruction = "no checked task declared; supply its source, check and brief" + + def start(self, comms: Comms, commander: Thread, phase: type[PlanPhase]) -> None: + """Readiness alone cannot authorize a fabricated order.""" + + def done(self, todos: Iterable[Todo]) -> bool: + return False diff --git a/src/agent_comms/plan_phases.py b/src/agent_comms/plan_phases.py index d93984f5f0..bbb309bd26 100644 --- a/src/agent_comms/plan_phases.py +++ b/src/agent_comms/plan_phases.py @@ -13,11 +13,10 @@ from typing import TYPE_CHECKING, ClassVar from .declared_family import DeclaredFamily -from .work_dispatch import UnspecifiedPhaseTask +from .phase_tasks import PhaseTask, UnspecifiedPhaseTask if TYPE_CHECKING: from .todos import Todo - from .work_dispatch import PhaseTask class OrderPhase(DeclaredFamily, affix="Phase"): diff --git a/src/agent_comms/todos.py b/src/agent_comms/todos.py index af62b2719f..874d9c8a12 100644 --- a/src/agent_comms/todos.py +++ b/src/agent_comms/todos.py @@ -1334,6 +1334,10 @@ def checked(self) -> CheckedProblemOrder: def require_transfer(self, todo: Todo) -> None: raise ValueError("This problem cannot receive another order.") + def reissued(self, at: float) -> PendingProblemOrder: + """This order sent again at ``at`` to a new owner, its first-call window kept.""" + raise ValueError("Only an undelivered order can change owner.") + def require_close(self): raise ValueError("Closing requires recorded installed application evidence.") diff --git a/src/agent_comms/work_dispatch.py b/src/agent_comms/work_dispatch.py index b1a45bc934..e8d10f4404 100644 --- a/src/agent_comms/work_dispatch.py +++ b/src/agent_comms/work_dispatch.py @@ -9,14 +9,24 @@ import subprocess import time -from abc import ABC, abstractmethod from collections.abc import Iterable from dataclasses import dataclass, replace -from typing import TYPE_CHECKING, ClassVar +from typing import TYPE_CHECKING + +from .goal_actions import ( + CreateProblemGoalAction, + GoalActionContext, + OrderGoalAction, + ProblemOrderGoalAction, +) +from .native_settings import NativeThreadDefaults +from .phase_tasks import PhaseTask +from .recorded_time import RecordedEventTime +from .storage_rules import CheckoutStorageEffect +from .thread_status import StoppedThreadStatus if TYPE_CHECKING: from .comms import Comms - from .goal_actions import ProblemOrderGoalAction from .plan_phases import PlanPhase from .threads import Thread from .todos import PendingProblemOrder, Todo @@ -30,11 +40,6 @@ class WorkDispatch: revision: int def start(self, comms: Comms) -> Thread: - from .goal_actions import GoalActionContext - from .native_settings import NativeThreadDefaults - from .storage_rules import CheckoutStorageEffect - from .thread_status import StoppedThreadStatus - defaults = NativeThreadDefaults() # A concurrent rotation of this same row contends on one git checkout, # not on two newly generated unit names. Never adopt a partial attempt. @@ -89,8 +94,6 @@ def start(self, comms: Comms) -> Thread: @classmethod def reissue(cls, comms: Comms, commander: Thread, todo: Todo) -> None: """Retained checked work needs no already-idle unit.""" - from .goal_actions import OrderGoalAction - current = comms.goals.todos.get(todo.id) current.require_revision(todo.revision) if current.holder or not current.pending: @@ -106,26 +109,6 @@ def reissue(cls, comms: Comms, commander: Thread, todo: Todo) -> None: cls(action, revision=current.revision).start(comms) -class PhaseTask(ABC): - instruction: ClassVar[str] - - @abstractmethod - def start(self, comms: Comms, commander: Thread, phase: type[PlanPhase]) -> None: ... - - @abstractmethod - def done(self, todos: Iterable[Todo]) -> bool: ... - - -class UnspecifiedPhaseTask(PhaseTask): - instruction = "no checked task declared; supply its source, check and brief" - - def start(self, comms: Comms, commander: Thread, phase: type[PlanPhase]) -> None: - """Readiness alone cannot authorize a fabricated order.""" - - def done(self, todos: Iterable[Todo]) -> bool: - return False - - @dataclass(frozen=True) class CheckedPhaseTask(PhaseTask): """An explicitly authored task; its order owns source, check, edits and run.""" @@ -142,9 +125,6 @@ def __post_init__(self) -> None: raise ValueError("A phase task needs a positive deadline of at most one day.") def start(self, comms: Comms, commander: Thread, phase: type[PlanPhase]) -> None: - from .goal_actions import CreateProblemGoalAction - from .recorded_time import RecordedEventTime - history = comms.goals.todos.history_for_commander(commander.incarnation) if not phase.ready(history) or phase.ordered(history): return diff --git a/tests/test_phase_task_dependencies.py b/tests/test_phase_task_dependencies.py new file mode 100644 index 0000000000..02fd3986b7 --- /dev/null +++ b/tests/test_phase_task_dependencies.py @@ -0,0 +1,27 @@ +"""Phase declarations stay usable before action and storage modules are loaded.""" + +from pathlib import Path +import subprocess +import sys + +import pytest + +from agent_comms.phase_tasks import UnspecifiedPhaseTask +from agent_comms.todos import UnorderedProblemOrder + + +@pytest.mark.parametrize("module", ("plan_phases", "work_dispatch", "goal_actions", "todos")) +def test_import_order_does_not_reintroduce_dispatch_cycle(module): + checkout = Path(__file__).resolve().parents[1] + result = subprocess.run( + (sys.executable, "-c", f"import agent_comms.{module}"), + cwd=checkout, + capture_output=True, text=True, timeout=30, + ) + assert result.returncode == 0, result.stderr + + +def test_unspecified_phase_and_unordered_problem_cannot_start_checked_work(): + assert not UnspecifiedPhaseTask().done(()) + with pytest.raises(ValueError, match="Only an undelivered order"): + UnorderedProblemOrder().reissued(1) From ab315464bd0df8db4662c22444e5e6d39a5051e0 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 19:07:13 -0400 Subject: [PATCH 11/12] Preserve phase identity when an installed merge retains its checked order --- src/agent_comms/plan_phases.py | 4 ++-- src/agent_comms/todos.py | 20 ++++++++++++----- tests/test_installed_merge.py | 7 +++++- tests/test_plan_phases.py | 41 ++++++++++++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 8 deletions(-) diff --git a/src/agent_comms/plan_phases.py b/src/agent_comms/plan_phases.py index bbb309bd26..c42d40d3c8 100644 --- a/src/agent_comms/plan_phases.py +++ b/src/agent_comms/plan_phases.py @@ -2,7 +2,7 @@ This family is the one owner of the plan's order and dependencies; ``plans/commander-native-plan.md`` points here. An order names the phase it -serves in ``ProblemOrder.serves``; an order outside the plan serves +serves through ``ProblemOrder.phase``; an order outside the plan serves ``UnplannedPhase``. A phase is done only when its declared full-phase task is accepted; closing related subtasks does not establish completion of the phase. """ @@ -33,7 +33,7 @@ class PlanPhase(OrderPhase, declared_name=None): @classmethod def serving(cls, todos: Iterable[Todo]) -> tuple[Todo, ...]: - return tuple(todo for todo in todos if todo.order.serves is cls) + return tuple(todo for todo in todos if todo.order.phase is cls) @classmethod def done(cls, todos: Iterable[Todo]) -> bool: diff --git a/src/agent_comms/todos.py b/src/agent_comms/todos.py index 874d9c8a12..ae859510d7 100644 --- a/src/agent_comms/todos.py +++ b/src/agent_comms/todos.py @@ -1331,13 +1331,16 @@ def pull_request_ids(cls, body: str) -> tuple[str, ...]: def checked(self) -> CheckedProblemOrder: raise ValueError("This problem has no checked order.") + @property + def phase(self) -> type[OrderPhase]: + return UnplannedPhase + + def for_phase(self, phase: type[OrderPhase]) -> ProblemOrder: + raise ValueError("Only a checked order can declare a plan phase.") + def require_transfer(self, todo: Todo) -> None: raise ValueError("This problem cannot receive another order.") - def reissued(self, at: float) -> PendingProblemOrder: - """This order sent again at ``at`` to a new owner, its first-call window kept.""" - raise ValueError("Only an undelivered order can change owner.") - def require_close(self): raise ValueError("Closing requires recorded installed application evidence.") @@ -1418,6 +1421,13 @@ def checked(self) -> CheckedProblemOrder: default_factory=NoOrderDeletion, metadata={"wire_omit_default": True}) serves: type[OrderPhase] = field(default=UnplannedPhase, metadata={"wire_omit_default": True}) + @property + def phase(self) -> type[OrderPhase]: + return self.serves + + def for_phase(self, phase: type[OrderPhase]) -> CheckedProblemOrder: + return replace(self, serves=phase) + def __post_init__(self): problems = [] if not self.edits or not self.banned: @@ -2173,7 +2183,7 @@ class ServeTodo(TodoOperation): def apply(self, current: Todo) -> Todo: current.require_revision(self.expected_revision) current.require_commander(self.actor) - return replace(current, order=replace(current.order, serves=self.serves), + return replace(current, order=current.order.for_phase(self.serves), revision=current.revision + 1) diff --git a/tests/test_installed_merge.py b/tests/test_installed_merge.py index 4e435dbdbf..f075327787 100644 --- a/tests/test_installed_merge.py +++ b/tests/test_installed_merge.py @@ -7,9 +7,10 @@ from agent_comms.repo import Repo from agent_comms.field_codec import FieldCodec +from agent_comms.plan_phases import RecordPlanePhase from agent_comms.todos import (CheckedProblemOrder, DoneTodoState, MergedProblemOrder, OperationDelivery, MergedPullRequest, PullRequests, UnorderedProblemOrder) -from agent_comms.tools import CommsOrderDoneTool +from agent_comms.tools import CommsOrderDoneTool, CommsOrderServeTool from agent_comms.recorded_time import RecordedEventTime from cutover.carry_merged_orders import MergedOrders from test_commander_problem_orders import working @@ -29,6 +30,8 @@ def test_installed_merge_closes_pending_or_delivered_order_once( delivery=OperationDelivery(at=RecordedEventTime(500)), claims=working(comms)).apply(comms, caller) todo = comms.goals.todos.get(todo.id) + CommsOrderServeTool(problem=todo.id, serves=RecordPlanePhase).apply(comms, caller) + todo = comms.goals.todos.get(todo.id) original = todo.order commit(unit) git(unit, 'switch', '-q', 'main') @@ -70,6 +73,8 @@ def test_installed_merge_closes_pending_or_delivered_order_once( for item in fields(CheckedProblemOrder)) assert closed.order.id == original.id assert "original" not in FieldCodec.encode(closed.order) + assert closed.order.phase is RecordPlanePhase + assert RecordPlanePhase.serving((closed,)) == (closed,) assert closed.strikes == todo.strikes assert FieldCodec.decode(type(closed), FieldCodec.encode(closed)) == closed diff --git a/tests/test_plan_phases.py b/tests/test_plan_phases.py index dc17a3e3e8..670b334b83 100644 --- a/tests/test_plan_phases.py +++ b/tests/test_plan_phases.py @@ -1,5 +1,9 @@ from dataclasses import replace +import pytest + +from agent_comms.field_codec import FieldCodec +from agent_comms.standing_rules import PlanIdle from agent_comms.plan_phases import ( CommanderPagePhase, ControlPlanePhase, @@ -8,10 +12,13 @@ RecordPlanePhase, SkillPhase, StoryPhase, + UnplannedPhase, ) from agent_comms.todos import ( DoneTodoState, + FoldedProblemOrder, InstalledProblemOrder, + ProblemOrder, OperationDelivery, UnorderedProblemOrder, WithdrawnProblemOrder, @@ -88,6 +95,40 @@ def test_views_wait_for_native_work_but_never_block_cutover(commander, monkeypat ) +def test_unordered_findings_have_no_invented_phase_declaration(commander): + comms, caller = commander + todo = create(commander, "P1") + finding = replace(todo, assignment=None, order=UnorderedProblemOrder()) + with comms.goals.todos.writing() as db: + finding.upsert(db) + assert finding.order.phase is UnplannedPhase + assert not RecordPlanePhase.serving((finding,)) + assert [rule.phase for rule in PlanIdle.violations(comms, caller.name)] == [RecordPlanePhase] + with pytest.raises(ValueError, match="Only a checked order"): + CommsOrderServeTool(problem=finding.id, serves=StoryPhase).apply(comms, caller) + assert comms.goals.todos.get(finding.id) == finding + + +def test_retained_phase_is_derived_and_round_trips_without_a_second_field(commander): + todo = create(commander, "P1") + original = todo.order.for_phase(RecordPlanePhase) + retained = ( + original.merged("a" * 40), + FoldedProblemOrder(original=original, into="P2", why="Keep one owner"), + ) + for order in retained: + saved = replace(todo, order=order, state=DoneTodoState, assignment=None) + assert RecordPlanePhase.serving((saved,)) == (saved,) + wire = FieldCodec.encode(order) + assert "serves" not in wire + restored = FieldCodec.decode(ProblemOrder, wire) + assert restored == order and restored.phase is RecordPlanePhase + changed = replace(order, original=original.for_phase(ControlPlanePhase)) + assert changed.phase is ControlPlanePhase + assert original.phase is RecordPlanePhase + assert not order.accepted + + def test_closed_related_subtask_does_not_accept_a_whole_phase(commander, monkeypatch): comms, caller = commander subtask = create(commander, "P149") From 21f5780a3a6b22a07ff140c6b39e2e478141e791 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 21:24:28 -0400 Subject: [PATCH 12/12] Integrate checked phase dispatch with current commander and merge owners --- src/agent_comms/work_dispatch.py | 1 + tests/test_installed_merge.py | 16 ++++++++------- tests/test_plan_phases.py | 35 ++++++++++++++------------------ 3 files changed, 25 insertions(+), 27 deletions(-) diff --git a/src/agent_comms/work_dispatch.py b/src/agent_comms/work_dispatch.py index e8d10f4404..e9e880a3ec 100644 --- a/src/agent_comms/work_dispatch.py +++ b/src/agent_comms/work_dispatch.py @@ -105,6 +105,7 @@ def reissue(cls, comms: Comms, commander: Thread, todo: Todo) -> None: request=current.goal.request(), deadline=current.deadline, commander=commander.name, + assigner=commander.incarnation, ) cls(action, revision=current.revision).start(comms) diff --git a/tests/test_installed_merge.py b/tests/test_installed_merge.py index f075327787..7618ebd849 100644 --- a/tests/test_installed_merge.py +++ b/tests/test_installed_merge.py @@ -2,6 +2,7 @@ from dataclasses import fields, replace import json import subprocess +import time import pytest @@ -46,8 +47,7 @@ def test_installed_merge_closes_pending_or_delivered_order_once( git(installed, 'fetch', '-q', 'origin', 'main') git(installed, 'checkout', '-q', '-B', 'main', base) repo = Repo(checkout=installed, github=todo.repo) - config = script.CONFIG.model_copy(update={'repos': (repo,)}) - monkeypatch.setattr(script, 'CONFIG', config) + actor = comms.registry.require(caller.name).incarnation merge = MergedPullRequest(1, 'fix/p1', sha, merge=sha, merged_at=500, updated_at=500, orders=(original.id,)) monkeypatch.setattr(PullRequests, 'read', lambda self, repo, state, order: @@ -55,14 +55,15 @@ def test_installed_merge_closes_pending_or_delivered_order_once( # The object being available locally is not installation: HEAD must contain it. assert not repo.contains(sha) - assert script.COMMANDER.orders(script.CONFIG.repos) == (todo,) + comms.goals.todos.reconcile_merges(actor, (repo,), comms.registry.storage_rule) + assert script.held_orders() == (todo,) assert comms.goals.todos.get(todo.id) == todo if not delivered: assert 'waiting on install' in deadline_lines(script)[0] git(installed, 'checkout', '-q', '-B', 'main', sha) assert repo.contains(sha) - assert script.COMMANDER.orders(script.CONFIG.repos) == () + comms.goals.todos.reconcile_merges(actor, (repo,), comms.registry.storage_rule) assert script.held_orders() == () closed = comms.goals.todos.get(todo.id) assert closed.state is DoneTodoState and closed.assignment is None @@ -98,9 +99,10 @@ def no_child(*args, **kwargs): with monkeypatch.context() as repeat: repeat.setattr(subprocess, 'run', no_child) - assert script.COMMANDER.orders(script.CONFIG.repos) == () + comms.goals.todos.reconcile_merges(actor, (repo,), comms.registry.storage_rule) + assert script.held_orders() == () assert comms.goals.todos.get(todo.id) == closed - script.board() + script.write_board() assert f'| {todo.id}:' not in (tmp_path / 'board.md').read_text() assert deadline_lines(script) == [] assert comms.goals.todos.get(todo.id) == closed @@ -111,7 +113,7 @@ class TickComplete(Exception): def next_tick(seconds): raise TickComplete - monkeypatch.setattr(script.time, 'sleep', next_tick) + monkeypatch.setattr(time, 'sleep', next_tick) with pytest.raises(TickComplete): script.overdue() assert 'OVERDUE:' not in capsys.readouterr().out diff --git a/tests/test_plan_phases.py b/tests/test_plan_phases.py index 670b334b83..669ccccd83 100644 --- a/tests/test_plan_phases.py +++ b/tests/test_plan_phases.py @@ -21,7 +21,6 @@ ProblemOrder, OperationDelivery, UnorderedProblemOrder, - WithdrawnProblemOrder, ) from agent_comms.tools import CommsOrderServeTool from agent_comms.work_dispatch import CheckedPhaseTask @@ -109,24 +108,22 @@ def test_unordered_findings_have_no_invented_phase_declaration(commander): assert comms.goals.todos.get(finding.id) == finding -def test_retained_phase_is_derived_and_round_trips_without_a_second_field(commander): +def test_merged_phase_is_inherited_and_round_trips_without_a_wrapper(commander): todo = create(commander, "P1") original = todo.order.for_phase(RecordPlanePhase) - retained = ( - original.merged("a" * 40), - FoldedProblemOrder(original=original, into="P2", why="Keep one owner"), - ) - for order in retained: - saved = replace(todo, order=order, state=DoneTodoState, assignment=None) - assert RecordPlanePhase.serving((saved,)) == (saved,) - wire = FieldCodec.encode(order) - assert "serves" not in wire - restored = FieldCodec.decode(ProblemOrder, wire) - assert restored == order and restored.phase is RecordPlanePhase - changed = replace(order, original=original.for_phase(ControlPlanePhase)) - assert changed.phase is ControlPlanePhase - assert original.phase is RecordPlanePhase - assert not order.accepted + order = original.merged("a" * 40) + saved = replace(todo, order=order, state=DoneTodoState, assignment=None) + assert RecordPlanePhase.serving((saved,)) == (saved,) + wire = FieldCodec.encode(order) + assert "original" not in wire + restored = FieldCodec.decode(ProblemOrder, wire) + assert restored == order and restored.phase is RecordPlanePhase + changed = order.for_phase(ControlPlanePhase) + assert changed.phase is ControlPlanePhase + assert original.phase is RecordPlanePhase + assert not order.accepted + folded = FoldedProblemOrder(original=original, into="P2", why="Keep one owner") + assert folded.phase is UnplannedPhase def test_closed_related_subtask_does_not_accept_a_whole_phase(commander, monkeypatch): @@ -143,9 +140,7 @@ def test_closed_related_subtask_does_not_accept_a_whole_phase(commander, monkeyp assert not StoryPhase.done((subtask, whole)) assert StoryPhase.done((subtask, accepted(whole))) - withdrawn = whole.order.copy_as( - WithdrawnProblemOrder, previous=whole.held(), why="The order was sent in error.", - ) + withdrawn = whole.order.withdrawn(whole.held(), "The order was sent in error.", None) assert not StoryPhase.done((replace(whole, state=DoneTodoState, order=withdrawn),)) assert not UnorderedProblemOrder().accepted wrong_phase = replace(accepted(whole), order=replace(accepted(whole).order, serves=ControlPlanePhase))