From 3de86bc82d79c09cb6a96cf2e8d753eea22f0ff1 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 19:51:21 -0400 Subject: [PATCH 1/5] P270: merged orders inherit the checked contract; carry saved wrappers once --- src/agent_comms/todos.py | 14 +++---- tests/test_installed_merge.py | 9 +++-- tools/cutover/carry.py | 10 +++++ tools/cutover/carry_merged_orders.py | 56 ++++++++++++++++++++++++++++ 4 files changed, 77 insertions(+), 12 deletions(-) create mode 100644 tools/cutover/carry_merged_orders.py diff --git a/src/agent_comms/todos.py b/src/agent_comms/todos.py index 7ac53b9284..ea68165c7f 100644 --- a/src/agent_comms/todos.py +++ b/src/agent_comms/todos.py @@ -1297,10 +1297,11 @@ def changed(name: str) -> bool: if (named := self.NAMED_FILE.findall(edit)) and not any(map(changed, named))) def copy_as[Kind: ProblemOrder](self, kind: type[Kind], **evidence) -> Kind: - return kind(**{item.name: getattr(self, item.name) for item in fields(self)}, **evidence) + return kind(**{item.name: getattr(self, item.name) for item in fields(kind) + if item.name not in evidence}, **evidence) def merged(self, sha: str) -> ProblemOrder: - return MergedProblemOrder(original=self, sha=sha) + return self.copy_as(MergedProblemOrder, sha=sha) @property def pull_request_reference(self) -> str: @@ -1375,16 +1376,11 @@ def waiting_on_install(self, todo: Todo, read: dict[str, OrderProgress]) -> tupl @dataclass(frozen=True, kw_only=True) -class MergedProblemOrder(ProblemOrder): - """An installed merge, retaining its original order without inventing a check or app proof.""" - original: CheckedProblemOrder +class MergedProblemOrder(CheckedProblemOrder): + """The checked order installed by a merge, with its actual merge identity.""" sha: str line: ClassVar[str] = "Merged and installed" - @property - def id(self) -> str: - return self.original.id - def require_close(self) -> None: pass diff --git a/tests/test_installed_merge.py b/tests/test_installed_merge.py index 53399af943..369d5b4039 100644 --- a/tests/test_installed_merge.py +++ b/tests/test_installed_merge.py @@ -1,12 +1,12 @@ """Merge observation closes the real stored order, without a completion request.""" -from dataclasses import replace +from dataclasses import fields, replace import subprocess import pytest from agent_comms.commander.config import Repo from agent_comms.field_codec import FieldCodec -from agent_comms.todos import (DoneTodoState, MergedProblemOrder, OperationDelivery, +from agent_comms.todos import (CheckedProblemOrder, DoneTodoState, MergedProblemOrder, OperationDelivery, PullRequest, PullRequests, UnorderedProblemOrder) from agent_comms.tools import CommsOrderDoneTool from test_commander_problem_orders import working @@ -58,8 +58,11 @@ def test_installed_merge_closes_pending_or_delivered_order_once( assert closed.state is DoneTodoState and closed.assignment is None assert closed.revision == todo.revision + 1 and not closed.pending assert isinstance(closed.order, MergedProblemOrder) - assert closed.order.sha == sha and closed.order.original == original + assert closed.order.sha == sha + assert all(getattr(closed.order, item.name) == getattr(original, item.name) + for item in fields(CheckedProblemOrder)) assert closed.order.id == original.id + assert "original" not in FieldCodec.encode(closed.order) assert closed.strikes == todo.strikes assert FieldCodec.decode(type(closed), FieldCodec.encode(closed)) == closed diff --git a/tools/cutover/carry.py b/tools/cutover/carry.py index 5ad8f9c414..0dd3c37978 100644 --- a/tools/cutover/carry.py +++ b/tools/cutover/carry.py @@ -600,6 +600,16 @@ def retire(self) -> None: self.backup.retire() +class TodoOrderShape(StoreCarry): + """Reshape saved orders before any step decodes rows as today's Todo.""" + + store = TodoStore + + @classmethod + def precedes(cls, later: type[Carry]) -> bool: + return issubclass(later, TodoRows) and not issubclass(later, TodoColumnCarry) + + class TodoColumnCarry(TodoRows): """Todo rows gain one declared record column, empty; every other column stays as it was.""" diff --git a/tools/cutover/carry_merged_orders.py b/tools/cutover/carry_merged_orders.py new file mode 100644 index 0000000000..6384f7ba83 --- /dev/null +++ b/tools/cutover/carry_merged_orders.py @@ -0,0 +1,56 @@ +"""Flatten saved merged orders into their checked-order contract plus merge SHA. + +Only this one-time carry understands the old original wrapper. Nested folded +orders are traversed too; production readers accept only the flat merged shape. +""" +from __future__ import annotations + +import json +from contextlib import closing +from pathlib import Path + +from agent_comms.coordination_database import CoordinationStore +from agent_comms.field_codec import FieldCodec +from agent_comms.todos import CheckedProblemOrder, MergedProblemOrder, Todo + +from .carry import Carried, TodoOrderShape + + +class MergedOrders(TodoOrderShape): + @classmethod + def flattened(cls, order: dict) -> tuple[dict, int]: + if "original" not in order: + return order, 0 + original, changed = cls.flattened(order["original"]) + if order["kind"] != MergedProblemOrder.declared_name: + return {**order, "original": original}, changed + checked = {FieldCodec.wire_key(item) for item, _ in + FieldCodec.record_fields(CheckedProblemOrder)} + flat = {key: value for key, value in original.items() if key in checked} + flat.update(kind=MergedProblemOrder.declared_name, sha=order["sha"]) + # Validate the changed payload, not unrelated rows awaiting earlier + # column/run carries. No old-shape decoder is retained in production. + FieldCodec.decode(MergedProblemOrder, flat) + return flat, changed + 1 + + @classmethod + def carry(cls, root: Path, *, apply: bool) -> Carried: + path = cls.store.path_in(root) + if not path.exists(): + return Carried(0) + changed = 0 + with closing(CoordinationStore.writer(path)) as db: + db.execute("BEGIN IMMEDIATE") + for rowid, encoded in db.execute(f'SELECT rowid, "order" FROM "{Todo.declared_name}"'): + order, count = cls.flattened(json.loads(encoded)) + if count: + db.execute(f'UPDATE "{Todo.declared_name}" SET "order"=? WHERE rowid=?', + (json.dumps(order), rowid)) + changed += count + db.commit() if apply else db.rollback() + return Carried(changed, {"merged_orders": changed}, + (path.name,) if apply and changed else ()) + + +if __name__ == "__main__": + MergedOrders.main() From 9d10ce7c39ac9eae6da9215db0a63928384080e4 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 20:14:32 -0400 Subject: [PATCH 2/5] P270: decode saved SQLite orders once and use their merged transition --- tools/cutover/carry_merged_orders.py | 65 +++++++++++++++------------- 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/tools/cutover/carry_merged_orders.py b/tools/cutover/carry_merged_orders.py index 6384f7ba83..c176ba6722 100644 --- a/tools/cutover/carry_merged_orders.py +++ b/tools/cutover/carry_merged_orders.py @@ -1,55 +1,62 @@ -"""Flatten saved merged orders into their checked-order contract plus merge SHA. +"""Carry retired merged wrappers through the checked order's own transition.""" -Only this one-time carry understands the old original wrapper. Nested folded -orders are traversed too; production readers accept only the flat merged shape. -""" from __future__ import annotations import json from contextlib import closing +from dataclasses import dataclass from pathlib import Path from agent_comms.coordination_database import CoordinationStore from agent_comms.field_codec import FieldCodec from agent_comms.todos import CheckedProblemOrder, MergedProblemOrder, Todo +from agent_comms.typed_table import TypedRow from .carry import Carried, TodoOrderShape -class MergedOrders(TodoOrderShape): - @classmethod - def flattened(cls, order: dict) -> tuple[dict, int]: - if "original" not in order: - return order, 0 - original, changed = cls.flattened(order["original"]) - if order["kind"] != MergedProblemOrder.declared_name: - return {**order, "original": original}, changed - checked = {FieldCodec.wire_key(item) for item, _ in - FieldCodec.record_fields(CheckedProblemOrder)} - flat = {key: value for key, value in original.items() if key in checked} - flat.update(kind=MergedProblemOrder.declared_name, sha=order["sha"]) - # Validate the changed payload, not unrelated rows awaiting earlier - # column/run carries. No old-shape decoder is retained in production. - FieldCodec.decode(MergedProblemOrder, flat) - return flat, changed + 1 +@dataclass(frozen=True) +class SavedMergedOrder(TypedRow): + """The old wrapper projected and decoded once at the SQLite boundary.""" + + id: str + path: str + checked: CheckedProblemOrder + sha: str + +class MergedOrders(TodoOrderShape): @classmethod def carry(cls, root: Path, *, apply: bool) -> Carried: path = cls.store.path_in(root) if not path.exists(): return Carried(0) - changed = 0 with closing(CoordinationStore.writer(path)) as db: db.execute("BEGIN IMMEDIATE") - for rowid, encoded in db.execute(f'SELECT rowid, "order" FROM "{Todo.declared_name}"'): - order, count = cls.flattened(json.loads(encoded)) - if count: - db.execute(f'UPDATE "{Todo.declared_name}" SET "order"=? WHERE rowid=?', - (json.dumps(order), rowid)) - changed += count + # Folded provenance can contain a merged order too. The transition + # belongs to CheckedProblemOrder, not a second field list here. + saved = SavedMergedOrder.read( + db.execute( + f"SELECT t.id, n.path AS path," + f" json_extract(t.\"order\", n.path || '.original') AS checked," + f" json_extract(t.\"order\", n.path || '.sha') AS sha" + f' FROM "{Todo.declared_name}" t, json_tree(t."order") n' + " WHERE n.key='kind' AND n.value=?" + " AND json_type(t.\"order\", n.path || '.original')='object'" + " ORDER BY length(n.path) DESC", + (MergedProblemOrder.declared_name,), + ) + ) + for row in saved: + merged = row.checked.merged(row.sha) + db.execute( + f'UPDATE "{Todo.declared_name}" SET "order"=json_set("order", ?, json(?)) WHERE id=?', + (row.path, json.dumps(FieldCodec.encode(merged)), row.id), + ) db.commit() if apply else db.rollback() - return Carried(changed, {"merged_orders": changed}, - (path.name,) if apply and changed else ()) + return Carried( + len(saved), {"merged_orders": len(saved)}, (path.name,) if apply and saved else () + ) if __name__ == "__main__": From bf579a3c359f62f06d2fc3c7e21c1e530ef0c0d4 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 20:16:21 -0400 Subject: [PATCH 3/5] P270: keep the retired wire tag at its SQLite decoding boundary --- tools/cutover/carry_merged_orders.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tools/cutover/carry_merged_orders.py b/tools/cutover/carry_merged_orders.py index c176ba6722..590d684337 100644 --- a/tools/cutover/carry_merged_orders.py +++ b/tools/cutover/carry_merged_orders.py @@ -9,7 +9,7 @@ from agent_comms.coordination_database import CoordinationStore from agent_comms.field_codec import FieldCodec -from agent_comms.todos import CheckedProblemOrder, MergedProblemOrder, Todo +from agent_comms.todos import CheckedProblemOrder, Todo from agent_comms.typed_table import TypedRow from .carry import Carried, TodoOrderShape @@ -41,16 +41,18 @@ def carry(cls, root: Path, *, apply: bool) -> Carried: f" json_extract(t.\"order\", n.path || '.original') AS checked," f" json_extract(t.\"order\", n.path || '.sha') AS sha" f' FROM "{Todo.declared_name}" t, json_tree(t."order") n' - " WHERE n.key='kind' AND n.value=?" + " WHERE n.key='kind' AND n.value='merged'" " AND json_type(t.\"order\", n.path || '.original')='object'" - " ORDER BY length(n.path) DESC", - (MergedProblemOrder.declared_name,), + " ORDER BY length(n.path) DESC" + # 'merged' is the retired input tag; the checked order's + # transition and codec own the current output declaration. ) ) for row in saved: merged = row.checked.merged(row.sha) db.execute( - f'UPDATE "{Todo.declared_name}" SET "order"=json_set("order", ?, json(?)) WHERE id=?', + f'UPDATE "{Todo.declared_name}"' + ' SET "order"=json_set("order", ?, json(?)) WHERE id=?', (row.path, json.dumps(FieldCodec.encode(merged)), row.id), ) db.commit() if apply else db.rollback() From a95e2d7d322fd322612d96958affd363fbeecea3 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 20:18:47 -0400 Subject: [PATCH 4/5] P270: carry typed saved orders through their owner; cover rollback and folded provenance --- tests/test_installed_merge.py | 25 +++++++++++++++++++++++-- tools/cutover/carry_merged_orders.py | 10 +++++----- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/tests/test_installed_merge.py b/tests/test_installed_merge.py index 369d5b4039..1ae145c677 100644 --- a/tests/test_installed_merge.py +++ b/tests/test_installed_merge.py @@ -1,5 +1,6 @@ """Merge observation closes the real stored order, without a completion request.""" from dataclasses import fields, replace +import json import subprocess import pytest @@ -9,19 +10,24 @@ from agent_comms.todos import (CheckedProblemOrder, DoneTodoState, MergedProblemOrder, OperationDelivery, PullRequest, PullRequests, UnorderedProblemOrder) from agent_comms.tools import CommsOrderDoneTool +from agent_comms.recorded_time import RecordedEventTime +from cutover.carry_merged_orders import MergedOrders from test_commander_problem_orders import working from test_order_progress import checkout, commit, deadline_lines, git @pytest.mark.parametrize('delivered', (False, True)) +@pytest.mark.parametrize('legacy_depth', (0, 1, 2)) def test_installed_merge_closes_pending_or_delivered_order_once( - commander, commander_script, checkout, tmp_path, monkeypatch, capsys, delivered): + commander, commander_script, checkout, tmp_path, monkeypatch, capsys, + delivered, legacy_depth): comms, caller = commander script, _ = commander_script todo, unit = checkout if delivered: CommsOrderDoneTool(problem=todo.id, order_id=todo.order.id, - delivery=OperationDelivery(), claims=working(comms)).apply(comms, caller) + delivery=OperationDelivery(at=RecordedEventTime(500)), + claims=working(comms)).apply(comms, caller) todo = comms.goals.todos.get(todo.id) original = todo.order commit(unit) @@ -66,6 +72,21 @@ def test_installed_merge_closes_pending_or_delivered_order_once( assert closed.strikes == todo.strikes assert FieldCodec.decode(type(closed), FieldCodec.encode(closed)) == closed + if legacy_depth: + legacy = {'kind': 'merged', 'original': FieldCodec.encode(original), 'sha': sha} + if legacy_depth == 2: + closed = replace(closed, order=closed.order.folded('P2', 'Same root cause.')) + legacy = {**FieldCodec.encode(closed.order), 'original': legacy} + encoded = json.dumps(legacy) + with comms.goals.todos.writing() as db: + db.execute('UPDATE todo SET "order"=? WHERE id=?', (encoded, todo.id)) + assert MergedOrders.carry(comms.root, apply=False).changed == 1 + with comms.goals.todos.writing() as db: + assert db.execute('SELECT "order" FROM todo WHERE id=?', (todo.id,)).fetchone()[0] == encoded + assert MergedOrders.carry(comms.root, apply=True).changed == 1 + assert MergedOrders.carry(comms.root, apply=True).changed == 0 + assert comms.goals.todos.get(todo.id) == closed + def no_child(*args, **kwargs): pytest.fail('A repeated closed-order read launched a subprocess') diff --git a/tools/cutover/carry_merged_orders.py b/tools/cutover/carry_merged_orders.py index 590d684337..249fe96847 100644 --- a/tools/cutover/carry_merged_orders.py +++ b/tools/cutover/carry_merged_orders.py @@ -9,7 +9,7 @@ from agent_comms.coordination_database import CoordinationStore from agent_comms.field_codec import FieldCodec -from agent_comms.todos import CheckedProblemOrder, Todo +from agent_comms.todos import CheckedProblemOrder, MergedProblemOrder, Todo from agent_comms.typed_table import TypedRow from .carry import Carried, TodoOrderShape @@ -31,6 +31,7 @@ def carry(cls, root: Path, *, apply: bool) -> Carried: path = cls.store.path_in(root) if not path.exists(): return Carried(0) + parameters = (MergedProblemOrder.declared_name,) with closing(CoordinationStore.writer(path)) as db: db.execute("BEGIN IMMEDIATE") # Folded provenance can contain a merged order too. The transition @@ -41,11 +42,10 @@ def carry(cls, root: Path, *, apply: bool) -> Carried: f" json_extract(t.\"order\", n.path || '.original') AS checked," f" json_extract(t.\"order\", n.path || '.sha') AS sha" f' FROM "{Todo.declared_name}" t, json_tree(t."order") n' - " WHERE n.key='kind' AND n.value='merged'" + " WHERE n.key='kind' AND n.value=?" " AND json_type(t.\"order\", n.path || '.original')='object'" - " ORDER BY length(n.path) DESC" - # 'merged' is the retired input tag; the checked order's - # transition and codec own the current output declaration. + " ORDER BY length(n.path) DESC", + parameters, ) ) for row in saved: From 9d3f4af4cfdcf9596354727f84fdb3e9ae4827f6 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 20:33:46 -0400 Subject: [PATCH 5/5] =?UTF-8?q?P270:=20use=20the=20installed=20installer?= =?UTF-8?q?=E2=80=99s=20existing=20StoreCarry=20parent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tools/cutover/carry.py | 10 ---------- tools/cutover/carry_merged_orders.py | 8 +++++--- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/tools/cutover/carry.py b/tools/cutover/carry.py index 0dd3c37978..5ad8f9c414 100644 --- a/tools/cutover/carry.py +++ b/tools/cutover/carry.py @@ -600,16 +600,6 @@ def retire(self) -> None: self.backup.retire() -class TodoOrderShape(StoreCarry): - """Reshape saved orders before any step decodes rows as today's Todo.""" - - store = TodoStore - - @classmethod - def precedes(cls, later: type[Carry]) -> bool: - return issubclass(later, TodoRows) and not issubclass(later, TodoColumnCarry) - - class TodoColumnCarry(TodoRows): """Todo rows gain one declared record column, empty; every other column stays as it was.""" diff --git a/tools/cutover/carry_merged_orders.py b/tools/cutover/carry_merged_orders.py index 249fe96847..33a3964384 100644 --- a/tools/cutover/carry_merged_orders.py +++ b/tools/cutover/carry_merged_orders.py @@ -9,10 +9,10 @@ from agent_comms.coordination_database import CoordinationStore from agent_comms.field_codec import FieldCodec -from agent_comms.todos import CheckedProblemOrder, MergedProblemOrder, Todo +from agent_comms.todos import CheckedProblemOrder, MergedProblemOrder, Todo, TodoStore from agent_comms.typed_table import TypedRow -from .carry import Carried, TodoOrderShape +from .carry import Carried, StoreCarry @dataclass(frozen=True) @@ -25,7 +25,9 @@ class SavedMergedOrder(TypedRow): sha: str -class MergedOrders(TodoOrderShape): +class MergedOrders(StoreCarry): + store = TodoStore + @classmethod def carry(cls, root: Path, *, apply: bool) -> Carried: path = cls.store.path_in(root)