From 78ee7cc9c375ecfad7590036db4600b2d6f82927 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 13:51:42 -0400 Subject: [PATCH 01/22] Record delivery integration on checked orders and decode named PR rows --- docs/evidence/cn-phase3-record.md | 52 +++++++++++ src/agent_comms/todos.py | 144 +++++++++++++++++++++++++++--- 2 files changed, 183 insertions(+), 13 deletions(-) create mode 100644 docs/evidence/cn-phase3-record.md diff --git a/docs/evidence/cn-phase3-record.md b/docs/evidence/cn-phase3-record.md new file mode 100644 index 0000000000..8bbc7a4dee --- /dev/null +++ b/docs/evidence/cn-phase3-record.md @@ -0,0 +1,52 @@ +# Commander record plane + +Owner: pi-cn-phase3. Branch: feat/cn-phase3-record. +Baseline: 5cdac561d498ece25142a8d2e5cf9b06002b6c8a. + +## Checklist + +- [x] Read the plan and inspect both repositories' open PR file lists. +- [x] Confirm existing verdict, strike limit, fire and void owners on main. +- [x] Add delivery integration to the canonical checked delivery. +- [x] Decode PR query rows into named records, not positional tuples. +- [ ] Confirm real scratch-bus order, verdict, strikes, fire and void through Core tools. +- [ ] Confirm delivery through the CLI and run bounded local checks. +- [ ] Publish a reviewed draft PR; do not merge or install. + +## Source relationships + +Before: `OrderDelivery.verified` ran the check and deletion counts; `CommitDelivery.require_source` +proved ancestry and publication only. Completion returned no integration fact. `PullRequests.read` +decoded a positional tuple of seven unrelated fields. `DeliveredProblemOrder.line` was a constant. + +After: completion passes its canonical Todo and registry to the delivery owner. A checked commit +records a `DeliveryMerge`: main, another PR target, or linked but unmerged source branches. +`DeliveredProblemOrder.line` derives its text from that delivery. `WorkBranches` still owns the +open-PR/open-issue branch-link rule. PR lookup still uses the immutable order reference, now decoded +through named query records including the target branch. No verdict, strike, delivery or branch store +was added. The integration is the observation at completion, not a later claim of installation. + +AST evidence used refactor-audit's `Package.load` with Python 3.14: 382 production modules parsed, +zero omissions. The only production `OrderDelivery.verified` caller is `DeliverTodo.prepare`; +creation is in `commander.order.done` and `CommsOrderDoneTool`. Goal summaries consume the order's +`line`. The installed Toad source has no order-delivery consumer; its unrelated input-delivery types +are not this family. A preliminary NRA run under its old Python 3.12 omitted Python 3.14 syntax; +it is not acceptance evidence. NRA under Python 3.14 is blocked by that checkout's incompatible +metaclass-registry/tree-sitter dependencies; the existing refactor-audit parser supplied the census. +Dynamic tool invocation is generated from ToolRequest declarations/FieldCodec, not statically +resolved by this census. + +## Overlaps + +P603 is merged (#1757). Open #1903 and #1873 do not edit these record owners. Recent #1756 edits +FairText, not the delivery family; #1925 removes DeadlineText. Recent #1878 changes TodoStore's +owner lookup, which this change leaves alone. Older #1737 changes claims and closing, not integration; +older #1733 adds printed check output and must compose that output with the checked delivery here. +#1912 moves the commander functions into an explicit Commander context, including `board` and `done`. +Those functions are left untouched pending the integration owner's conflict decision. + +## Resource boundary + +`tools/agent_resource_check.py --assert-headroom` warned: about 4 GiB available RAM and 3.8 GiB +free on /tmp. No parallel agents or large suite. The prescribed /home/ts/bin launcher is absent. +Owned verification state and logs will remain under this checkout's evidence directory, not /tmp. diff --git a/src/agent_comms/todos.py b/src/agent_comms/todos.py index 19c45babcb..8f83d57b36 100644 --- a/src/agent_comms/todos.py +++ b/src/agent_comms/todos.py @@ -43,11 +43,13 @@ from .standing_rules import UndatedStandingRule from .storage_rules import StorageRule from .plan_phases import OrderPhase, UnplannedPhase +from .work_branches import WorkBranches if TYPE_CHECKING: from .commander.config import Repo from .comms import Comms from .goal_management import Goals + from .registration import Registration @dataclass(frozen=True) @@ -87,9 +89,10 @@ async def run(self, check, *command): raise ValueError(result.stderr.decode(errors="replace") or "Delivery source check failed.") return result.stdout.decode(errors="replace") - async def verified(self, check: OrderCheck, deletes: OrderDeletion) -> OrderDelivery: + async def verified(self, todo: Todo, registry: Registration) -> OrderDelivery: """The order's check and every count its deletion declares run on the delivered source.""" - for each in (check, *deletes.checks): + check = todo.order.check + for each in (check, *todo.order.deletes.checks): await self.require_source(each) each.result.require(await self.checked(each)) return replace(self, at=await self.delivery_time(check)) @@ -176,6 +179,95 @@ async def require_source(self, check: OrderCheck) -> None: raise ValueError( f"Delivered commit {self.delivered} is on no origin branch; push it first.") + async def verified(self, todo: Todo, registry: Registration) -> OrderDelivery: + merge = await self.merge_state(todo, registry) + verified = await super().verified(todo, registry) + return CheckedCommitDelivery(commit=self.commit, delivered_commit=self.delivered, + at=verified.at, evidence=self.evidence, merge=merge) + + async def merge_state(self, todo: Todo, registry: Registration) -> DeliveryMerge: + """Observe integration, not merely publication, at the time of completion. + + Git proves ordinary merges to main. The order's merged PR also proves squash merges + and names a non-main target. Otherwise the pushed source must still have an open link, + whose owner is WorkBranches, not a second branch policy in the delivery tool. + """ + check = todo.order.check + branches = (await self.run( + check, "git", "for-each-ref", f"--contains={self.delivered}", + "--format=%(refname:strip=3)", "refs/remotes/origin/")).splitlines() + if WorkBranches.main in branches: + return MainDeliveryMerge() + requests = await asyncio.to_thread(PullRequests((todo,)).listed, + todo.repo, "merged", todo.order) + for request in requests: + if request.merge and await self.contains(check, request.head): + return (MainDeliveryMerge() if request.base == WorkBranches.main + else BranchDeliveryMerge(request.base, request.merge)) + linked = await asyncio.to_thread( + WorkBranches(Path(check.directory)).read, registry.snapshot()) + sources = tuple(sorted(branch.name for branch in linked + if branch.name in branches and branch.linked)) + if not sources: + raise ValueError( + f"Delivered commit {self.delivered} is not merged and has no branch linked " + "to an open PR or issue.") + return UnmergedDeliveryMerge(sources) + + async def contains(self, check: OrderCheck, head: str) -> bool: + if head == self.delivered: + return True + result = await BoundedRun.run( + ("git", "merge-base", "--is-ancestor", self.delivered, head), + cwd=check.directory, timeout=120) + if result.outcome not in (ExitedOutcome(0), ExitedOutcome(1)): + raise ValueError(result.stderr.decode(errors="replace") or + "Could not check the delivered commit against its PR head.") + return result.outcome == ExitedOutcome(0) + + +class DeliveryMerge(DeclaredFamily, affix="DeliveryMerge"): + """The integration observed when Core checked a commit delivery; never a claim of install.""" + + @property + @abstractmethod + def line(self) -> str: ... + + +@dataclass(frozen=True) +class MainDeliveryMerge(DeliveryMerge): + @property + def line(self) -> str: + return f"merged to {WorkBranches.main}" + + +@dataclass(frozen=True) +class BranchDeliveryMerge(DeliveryMerge): + branch: str + commit: str + + @property + def line(self) -> str: + return f"merged to {self.branch} as {self.commit}" + + +@dataclass(frozen=True) +class UnmergedDeliveryMerge(DeliveryMerge): + branches: tuple[str, ...] + + @property + def line(self) -> str: + return f"not merged; linked branches: {', '.join(self.branches)}" + + +@dataclass(frozen=True, kw_only=True) +class CheckedCommitDelivery(CommitDelivery): + merge: DeliveryMerge + + @property + def work(self) -> str: + return f"{super().work}; {self.merge.line}" + @dataclass(frozen=True, kw_only=True) class OperationDelivery(OrderDelivery): @@ -198,21 +290,46 @@ class PullRequest: merged_at: float | None = None updated_at: float = 0.0 orders: tuple[str, ...] = () + base: str = field(kw_only=True) @classmethod @boundary_decoder def listed(cls, number: int, branch: str, head: str, merge: str, merged: str, - updated: str, body: str) -> Self: + updated: str, body: str, base: str) -> Self: """One row of ``gh pr list``; GitHub leaves an unmerged PR's merge commit and time empty.""" return cls(int(number), branch, head, merge or None, _github_time(merged) if merged else None, _github_time(updated), - ProblemOrder.pull_request_ids(body)) + ProblemOrder.pull_request_ids(body), base=base) def _github_time(text: str) -> float: return calendar.timegm(time.strptime(text, "%Y-%m-%dT%H:%M:%SZ")) +@dataclass(frozen=True) +class PullRequestCommit: + """GitHub's optional mergeCommit object, decoded at the query boundary.""" + oid: str + + +@dataclass(frozen=True) +class ListedPullRequest: + """The external query row; conversion lives here, never in its consumers.""" + number: int + branch: str + head: str + merge: PullRequestCommit | None + merged: str | None + updated: str + body: str + base: str + + def parsed(self) -> PullRequest: + return PullRequest.listed( + self.number, self.branch, self.head, self.merge.oid if self.merge else "", + self.merged or "", self.updated, self.body, self.base) + + class PullRequests: """Pull requests queried by immutable order identity, cached within one board read.""" @@ -239,20 +356,19 @@ def read(self, repo: str, state: str, order: ProblemOrder) -> tuple[PullRequest, f'"{candidate.pull_request_reference}" sort:updated-desc') selections.append( f'{alias}: search(query: {search}, type: ISSUE, first: 100) {{ nodes {{ ' - '... on PullRequest { number headRefName headRefOid body ' - 'mergeCommit { oid } mergedAt updatedAt } } }') + '... on PullRequest { number branch: headRefName head: headRefOid body ' + 'base: baseRefName merge: mergeCommit { oid } merged: mergedAt ' + 'updated: updatedAt } } }') listed = subprocess.run( ("gh", "api", "graphql", "-f", "query=query {" + " ".join(selections) + "}", - "--jq", '.data | map_values(.nodes | map([.number, .headRefName, .headRefOid,' - ' .mergeCommit.oid // "", .mergedAt // "", .updatedAt, .body]))'), + "--jq", '.data | map_values(.nodes)'), capture_output=True, text=True, timeout=60) if listed.returncode: raise ValueError(listed.stderr.strip() or f"GitHub order lookup in {repo} failed.") - rows = FieldCodec.decode(dict[str, tuple[tuple[int, str, str, str, str, str, str], ...]], + rows = FieldCodec.decode(dict[str, tuple[ListedPullRequest, ...]], FieldCodec.parse(listed.stdout)) for alias, candidate in aliases.items(): - self._read[repo, state, candidate.id] = tuple( - PullRequest.listed(*row) for row in rows[alias]) + self._read[repo, state, candidate.id] = tuple(row.parsed() for row in rows[alias]) return self._read[repo, state, order.id] @@ -1368,7 +1484,9 @@ class DroppedProblemOrder(UnorderedProblemOrder, CheckedProblemOrder): class DeliveredProblemOrder(CheckedProblemOrder): delivery: OrderDelivery pending: ClassVar[bool] = False - line: ClassVar[str] = "Delivered, waiting on install" + @property + def line(self) -> str: + return f"Delivered: {self.delivery.work}, waiting on install" def require_transfer(self, todo: Todo) -> None: owner = (todo.assignment or todo.released[-1].previous).owner @@ -1962,7 +2080,7 @@ def prepare(self, goals, current): current.require_commander(self.actor) current.state.require_open(current) current.order.require_delivery(self.order_id) - verified = self.delivery.verified(current.order.check, current.order.deletes) + verified = self.delivery.verified(current, goals.registry) return replace(self, delivery=asyncio.run(verified)) def apply(self, current): From 1979f5e9d736427491d8b069340dc9b3d5fd107d Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 14:08:21 -0400 Subject: [PATCH 02/22] Verify real order records and delivery states through the CLI --- docs/evidence/cn-phase3-record-results.jsonl | 12 ++ docs/evidence/cn-phase3-record.md | 73 +++++++++- src/agent_comms/todos.py | 25 ++-- tests/test_commander_problem_orders.py | 5 +- tests/test_delivery_merge.py | 30 ++++ tests/test_standing_rules.py | 6 +- tools/proofs/cn_phase3_record.py | 144 +++++++++++++++++++ 7 files changed, 277 insertions(+), 18 deletions(-) create mode 100644 docs/evidence/cn-phase3-record-results.jsonl create mode 100644 tests/test_delivery_merge.py create mode 100644 tools/proofs/cn_phase3_record.py diff --git a/docs/evidence/cn-phase3-record-results.jsonl b/docs/evidence/cn-phase3-record-results.jsonl new file mode 100644 index 0000000000..a525dd2535 --- /dev/null +++ b/docs/evidence/cn-phase3-record-results.jsonl @@ -0,0 +1,12 @@ +{"goal":{"text":"Complete the assigned problems.","id":"record-work","published_seq":0,"progress":"","revision":2,"reported_turn":null,"mention_source":{"goal_id":"record-work","text_revision":2,"text_digest":"816861a4f5b6cbf17b6bf4e4a3d9f87e1b8bdcd32bcf98c2b447f2a3a9be80b7","owner_name":"record-worker","owner_created_at":1792432385.2149153,"bindings":[]},"state":{"kind":"active"},"order":{"kind":"problem"}},"todo":{"row":"todo","id":"P1","repo":"openhcsdev/agent-comms","text":"Show the real verdict and strike records without a second source","creator":"record-lead","creator_created":1792432385.1369143,"state":"open","revision":1,"goal":{"owner":"record-lead","owner_created":1792432385.1369143,"goal_id":"record-request"},"assignment":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"order":{"kind":"pending","id":"record-order-P1","owner":"/home/ts/wt/pi-cn-phase3--P1/src/agent_comms/todos.py:69","check":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf 1","result":{"kind":"count","expected":1}},"edits":["Decode pull request target branches through the existing record owner"],"banned":["No second order store"],"report":"Report the actual Core tool response","at":{"kind":"recorded","at":1792432466.497828},"first_call_deadline":{"kind":"recorded","at":1792432586.497828},"first_call":{"kind":"awaiting"},"run":{"kind":"mechanical"}},"deadline":{"kind":"recorded","at":1792436066.497828},"strikes":[],"voided":[],"released":[],"verdicts":[],"corrections":[],"messages":[]}} +{"todo":{"row":"todo","id":"P1","repo":"openhcsdev/agent-comms","text":"Show the real verdict and strike records without a second source","creator":"record-lead","creator_created":1792432385.1369143,"state":"open","revision":2,"goal":{"owner":"record-lead","owner_created":1792432385.1369143,"goal_id":"record-request"},"assignment":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"order":{"kind":"pending","id":"record-order-P1","owner":"/home/ts/wt/pi-cn-phase3--P1/src/agent_comms/todos.py:69","check":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf 1","result":{"kind":"count","expected":1}},"edits":["Decode pull request target branches through the existing record owner"],"banned":["No second order store"],"report":"Report the actual Core tool response","at":{"kind":"recorded","at":1792432466.497828},"first_call_deadline":{"kind":"recorded","at":1792432586.497828},"first_call":{"kind":"awaiting"},"run":{"kind":"mechanical"}},"deadline":{"kind":"recorded","at":1792436066.497828},"strikes":[],"voided":[],"released":[],"verdicts":[{"kind":"accepted_verdict","expected_revision":1,"actor":{"name":"record-lead","created_at":1792432385.1369143},"previous":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"tool_call_id":"call_a3e3f7350f4e44faa55a72bebf904fa6|fc_01744e18603d1183016ad659dcd6c081a3a769ec0e1880e266","at":{"kind":"recorded","at":1792432628.8483949},"quote":"Decode pull request target branches"}],"corrections":[],"messages":[]},"strike_count":0} +{"todo":{"row":"todo","id":"P1","repo":"openhcsdev/agent-comms","text":"Show the real verdict and strike records without a second source","creator":"record-lead","creator_created":1792432385.1369143,"state":"open","revision":3,"goal":{"owner":"record-lead","owner_created":1792432385.1369143,"goal_id":"record-request"},"assignment":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"order":{"kind":"pending","id":"record-order-P1","owner":"/home/ts/wt/pi-cn-phase3--P1/src/agent_comms/todos.py:69","check":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf 1","result":{"kind":"count","expected":1}},"edits":["Decode pull request target branches through the existing record owner"],"banned":["No second order store"],"report":"Report the actual Core tool response","at":{"kind":"recorded","at":1792432466.497828},"first_call_deadline":{"kind":"recorded","at":1792432586.497828},"first_call":{"kind":"awaiting"},"run":{"kind":"mechanical"}},"deadline":{"kind":"recorded","at":1792436066.497828},"strikes":[{"kind":"strike_todo","expected_revision":2,"actor":{"name":"record-lead","created_at":1792432385.1369143},"previous":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"reason":"The assigned change left the target branch out of the saved order record","at":{"kind":"recorded","at":1792432640.0348916}}],"voided":[],"released":[],"verdicts":[{"kind":"accepted_verdict","expected_revision":1,"actor":{"name":"record-lead","created_at":1792432385.1369143},"previous":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"tool_call_id":"call_a3e3f7350f4e44faa55a72bebf904fa6|fc_01744e18603d1183016ad659dcd6c081a3a769ec0e1880e266","at":{"kind":"recorded","at":1792432628.8483949},"quote":"Decode pull request target branches"}],"corrections":[],"messages":[]},"strike_count":1} +{"todo":{"row":"todo","id":"P1","repo":"openhcsdev/agent-comms","text":"Show the real verdict and strike records without a second source","creator":"record-lead","creator_created":1792432385.1369143,"state":"open","revision":4,"goal":{"owner":"record-lead","owner_created":1792432385.1369143,"goal_id":"record-request"},"assignment":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"order":{"kind":"pending","id":"record-order-P1","owner":"/home/ts/wt/pi-cn-phase3--P1/src/agent_comms/todos.py:69","check":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf 1","result":{"kind":"count","expected":1}},"edits":["Decode pull request target branches through the existing record owner"],"banned":["No second order store"],"report":"Report the actual Core tool response","at":{"kind":"recorded","at":1792432466.497828},"first_call_deadline":{"kind":"recorded","at":1792432586.497828},"first_call":{"kind":"awaiting"},"run":{"kind":"mechanical"}},"deadline":{"kind":"recorded","at":1792436066.497828},"strikes":[{"kind":"strike_todo","expected_revision":2,"actor":{"name":"record-lead","created_at":1792432385.1369143},"previous":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"reason":"The assigned change left the target branch out of the saved order record","at":{"kind":"recorded","at":1792432640.0348916}},{"kind":"strike_todo","expected_revision":3,"actor":{"name":"record-lead","created_at":1792432385.1369143},"previous":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"reason":"The second review found the target branch still missing from the saved record","at":{"kind":"recorded","at":1792432652.476716}}],"voided":[],"released":[],"verdicts":[{"kind":"accepted_verdict","expected_revision":1,"actor":{"name":"record-lead","created_at":1792432385.1369143},"previous":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"tool_call_id":"call_a3e3f7350f4e44faa55a72bebf904fa6|fc_01744e18603d1183016ad659dcd6c081a3a769ec0e1880e266","at":{"kind":"recorded","at":1792432628.8483949},"quote":"Decode pull request target branches"}],"corrections":[],"messages":[]},"strike_count":2} +{"todos":[{"row":"todo","id":"P1","repo":"openhcsdev/agent-comms","text":"Show the real verdict and strike records without a second source","creator":"record-lead","creator_created":1792432385.1369143,"state":"open","revision":5,"goal":{"owner":"record-lead","owner_created":1792432385.1369143,"goal_id":"record-request"},"assignment":null,"order":{"kind":"pending","id":"record-order-P1","owner":"/home/ts/wt/pi-cn-phase3--P1/src/agent_comms/todos.py:69","check":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf 1","result":{"kind":"count","expected":1}},"edits":["Decode pull request target branches through the existing record owner"],"banned":["No second order store"],"report":"Report the actual Core tool response","at":{"kind":"recorded","at":1792432466.497828},"first_call_deadline":{"kind":"recorded","at":1792432586.497828},"first_call":{"kind":"awaiting"},"run":{"kind":"mechanical"}},"deadline":{"kind":"recorded","at":1792436066.497828},"strikes":[{"kind":"strike_todo","expected_revision":2,"actor":{"name":"record-lead","created_at":1792432385.1369143},"previous":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"reason":"The assigned change left the target branch out of the saved order record","at":{"kind":"recorded","at":1792432640.0348916}},{"kind":"strike_todo","expected_revision":3,"actor":{"name":"record-lead","created_at":1792432385.1369143},"previous":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"reason":"The second review found the target branch still missing from the saved record","at":{"kind":"recorded","at":1792432652.476716}}],"voided":[],"released":[{"kind":"release_todo","expected_revision":4,"actor":{"name":"record-lead","created_at":1792432385.1369143},"previous":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"reason":"The unit left the required target branch absent after both recorded reviews","at":{"kind":"recorded","at":1792432675.6006632}}],"verdicts":[{"kind":"accepted_verdict","expected_revision":1,"actor":{"name":"record-lead","created_at":1792432385.1369143},"previous":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"tool_call_id":"call_a3e3f7350f4e44faa55a72bebf904fa6|fc_01744e18603d1183016ad659dcd6c081a3a769ec0e1880e266","at":{"kind":"recorded","at":1792432628.8483949},"quote":"Decode pull request target branches"}],"corrections":[],"messages":[]}],"progress":{}} +{"todo":{"row":"todo","id":"P1","repo":"openhcsdev/agent-comms","text":"Show the real verdict and strike records without a second source","creator":"record-lead","creator_created":1792432385.1369143,"state":"open","revision":6,"goal":{"owner":"record-lead","owner_created":1792432385.1369143,"goal_id":"record-request"},"assignment":null,"order":{"kind":"pending","id":"record-order-P1","owner":"/home/ts/wt/pi-cn-phase3--P1/src/agent_comms/todos.py:69","check":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf 1","result":{"kind":"count","expected":1}},"edits":["Decode pull request target branches through the existing record owner"],"banned":["No second order store"],"report":"Report the actual Core tool response","at":{"kind":"recorded","at":1792432466.497828},"first_call_deadline":{"kind":"recorded","at":1792432586.497828},"first_call":{"kind":"awaiting"},"run":{"kind":"mechanical"}},"deadline":{"kind":"recorded","at":1792436066.497828},"strikes":[{"kind":"strike_todo","expected_revision":2,"actor":{"name":"record-lead","created_at":1792432385.1369143},"previous":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"reason":"The assigned change left the target branch out of the saved order record","at":{"kind":"recorded","at":1792432640.0348916}},{"kind":"strike_todo","expected_revision":3,"actor":{"name":"record-lead","created_at":1792432385.1369143},"previous":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"reason":"The second review found the target branch still missing from the saved record","at":{"kind":"recorded","at":1792432652.476716}}],"voided":[{"kind":"void_strike_todo","expected_revision":5,"actor":{"name":"record-lead","created_at":1792432385.1369143},"strike":{"kind":"strike_todo","expected_revision":3,"actor":{"name":"record-lead","created_at":1792432385.1369143},"previous":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"reason":"The second review found the target branch still missing from the saved record","at":{"kind":"recorded","at":1792432652.476716}},"reason":"The second strike repeated the first finding rather than describing another failed change","at":{"kind":"recorded","at":1792432680.216849}}],"released":[{"kind":"release_todo","expected_revision":4,"actor":{"name":"record-lead","created_at":1792432385.1369143},"previous":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"reason":"The unit left the required target branch absent after both recorded reviews","at":{"kind":"recorded","at":1792432675.6006632}}],"verdicts":[{"kind":"accepted_verdict","expected_revision":1,"actor":{"name":"record-lead","created_at":1792432385.1369143},"previous":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"tool_call_id":"call_a3e3f7350f4e44faa55a72bebf904fa6|fc_01744e18603d1183016ad659dcd6c081a3a769ec0e1880e266","at":{"kind":"recorded","at":1792432628.8483949},"quote":"Decode pull request target branches"}],"corrections":[],"messages":[]},"strike_count":0} +{"goal":{"text":"Complete the assigned problems.","id":"record-work","published_seq":0,"progress":"","revision":3,"reported_turn":null,"mention_source":{"goal_id":"record-work","text_revision":3,"text_digest":"816861a4f5b6cbf17b6bf4e4a3d9f87e1b8bdcd32bcf98c2b447f2a3a9be80b7","owner_name":"record-worker","owner_created_at":1792432385.2149153,"bindings":[]},"state":{"kind":"active"},"order":{"kind":"problem"}},"todo":{"row":"todo","id":"P2","repo":"openhcsdev/agent-comms","text":"Show the real verdict and strike records without a second source","creator":"record-lead","creator_created":1792432385.1369143,"state":"open","revision":1,"goal":{"owner":"record-lead","owner_created":1792432385.1369143,"goal_id":"record-request"},"assignment":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"order":{"kind":"pending","id":"record-order-P2","owner":"/home/ts/wt/pi-cn-phase3--P1/src/agent_comms/todos.py:69","check":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf 1","result":{"kind":"count","expected":1}},"edits":["Decode pull request target branches through the existing record owner"],"banned":["No second order store"],"report":"Report the actual Core tool response","at":{"kind":"recorded","at":1792432735.6061842},"first_call_deadline":{"kind":"recorded","at":1792432855.6061842},"first_call":{"kind":"awaiting"},"run":{"kind":"mechanical"}},"deadline":{"kind":"recorded","at":1792436335.6061842},"strikes":[],"voided":[],"released":[],"verdicts":[],"corrections":[],"messages":[]}} +{"todo":{"row":"todo","id":"P2","repo":"openhcsdev/agent-comms","text":"Show the real verdict and strike records without a second source","creator":"record-lead","creator_created":1792432385.1369143,"state":"open","revision":2,"goal":{"owner":"record-lead","owner_created":1792432385.1369143,"goal_id":"record-request"},"assignment":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"order":{"kind":"delivered","id":"record-order-P2","owner":"/home/ts/wt/pi-cn-phase3--P1/src/agent_comms/todos.py:69","check":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf 1","result":{"kind":"count","expected":1}},"edits":["Decode pull request target branches through the existing record owner"],"banned":["No second order store"],"report":"Report the actual Core tool response","at":{"kind":"recorded","at":1792432735.6061842},"first_call_deadline":{"kind":"recorded","at":1792432855.6061842},"first_call":{"kind":"awaiting"},"run":{"kind":"mechanical"},"delivery":{"kind":"checked_commit","at":{"kind":"recorded","at":1792431763.0},"commit":"e0ca0150042587e7494184b5ab2f6701f3487076","delivered_commit":"5cdac561d498ece25142a8d2e5cf9b06002b6c8a","merge":{"kind":"main"}}},"deadline":{"kind":"recorded","at":1792436335.6061842},"strikes":[],"voided":[],"released":[],"verdicts":[],"corrections":[],"messages":[]},"strike_count":0} +{"goal":{"text":"Complete the assigned problems.","id":"record-work","published_seq":0,"progress":"","revision":4,"reported_turn":null,"mention_source":{"goal_id":"record-work","text_revision":4,"text_digest":"816861a4f5b6cbf17b6bf4e4a3d9f87e1b8bdcd32bcf98c2b447f2a3a9be80b7","owner_name":"record-worker","owner_created_at":1792432385.2149153,"bindings":[]},"state":{"kind":"active"},"order":{"kind":"problem"}},"todo":{"row":"todo","id":"P3","repo":"openhcsdev/agent-comms","text":"Show the real verdict and strike records without a second source","creator":"record-lead","creator_created":1792432385.1369143,"state":"open","revision":1,"goal":{"owner":"record-lead","owner_created":1792432385.1369143,"goal_id":"record-request"},"assignment":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"order":{"kind":"pending","id":"record-order-P3","owner":"/home/ts/wt/pi-cn-phase3--P1/src/agent_comms/todos.py:69","check":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf 1","result":{"kind":"count","expected":1}},"edits":["Decode pull request target branches through the existing record owner"],"banned":["No second order store"],"report":"Report the actual Core tool response","at":{"kind":"recorded","at":1792432771.201293},"first_call_deadline":{"kind":"recorded","at":1792432891.201293},"first_call":{"kind":"awaiting"},"run":{"kind":"mechanical"}},"deadline":{"kind":"recorded","at":1792436371.201293},"strikes":[],"voided":[],"released":[],"verdicts":[],"corrections":[],"messages":[]}} +{"todo":{"row":"todo","id":"P3","repo":"openhcsdev/agent-comms","text":"Show the real verdict and strike records without a second source","creator":"record-lead","creator_created":1792432385.1369143,"state":"open","revision":2,"goal":{"owner":"record-lead","owner_created":1792432385.1369143,"goal_id":"record-request"},"assignment":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"order":{"kind":"delivered","id":"record-order-P3","owner":"/home/ts/wt/pi-cn-phase3--P1/src/agent_comms/todos.py:69","check":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf 1","result":{"kind":"count","expected":1}},"edits":["Decode pull request target branches through the existing record owner"],"banned":["No second order store"],"report":"Report the actual Core tool response","at":{"kind":"recorded","at":1792432771.201293},"first_call_deadline":{"kind":"recorded","at":1792432891.201293},"first_call":{"kind":"awaiting"},"run":{"kind":"mechanical"},"delivery":{"kind":"checked_commit","at":{"kind":"recorded","at":1792432302.0},"commit":"e0ca0150042587e7494184b5ab2f6701f3487076","delivered_commit":"e0ca01500","merge":{"kind":"unmerged","branches":["feat/cn-phase3-record"]}}},"deadline":{"kind":"recorded","at":1792436371.201293},"strikes":[],"voided":[],"released":[],"verdicts":[],"corrections":[],"messages":[]},"strike_count":0} +{"goal":{"text":"Complete the assigned problems.","id":"record-work","published_seq":0,"progress":"","revision":5,"reported_turn":null,"mention_source":{"goal_id":"record-work","text_revision":5,"text_digest":"816861a4f5b6cbf17b6bf4e4a3d9f87e1b8bdcd32bcf98c2b447f2a3a9be80b7","owner_name":"record-worker","owner_created_at":1792432385.2149153,"bindings":[]},"state":{"kind":"active"},"order":{"kind":"problem"}},"todo":{"row":"todo","id":"P4","repo":"openhcsdev/agent-comms","text":"Show the real verdict and strike records without a second source","creator":"record-lead","creator_created":1792432385.1369143,"state":"open","revision":1,"goal":{"owner":"record-lead","owner_created":1792432385.1369143,"goal_id":"record-request"},"assignment":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"order":{"kind":"pending","id":"record-order-P4","owner":"/home/ts/wt/pi-cn-phase3--P1/src/agent_comms/todos.py:69","check":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf 1","result":{"kind":"count","expected":1}},"edits":["Decode pull request target branches through the existing record owner"],"banned":["No second order store"],"report":"Report the actual Core tool response","at":{"kind":"recorded","at":1792433118.0605557},"first_call_deadline":{"kind":"recorded","at":1792433238.0605557},"first_call":{"kind":"awaiting"},"run":{"kind":"mechanical"}},"deadline":{"kind":"recorded","at":1792436718.0605557},"strikes":[],"voided":[],"released":[],"verdicts":[],"corrections":[],"messages":[]}} +{"error":"Delivered commit 2874a87ada914add901c7f318ea46c3c88aa555a is not merged and has no branch linked to an open PR or issue.","type":"failed"} diff --git a/docs/evidence/cn-phase3-record.md b/docs/evidence/cn-phase3-record.md index 8bbc7a4dee..f9f9efc930 100644 --- a/docs/evidence/cn-phase3-record.md +++ b/docs/evidence/cn-phase3-record.md @@ -9,9 +9,14 @@ Baseline: 5cdac561d498ece25142a8d2e5cf9b06002b6c8a. - [x] Confirm existing verdict, strike limit, fire and void owners on main. - [x] Add delivery integration to the canonical checked delivery. - [x] Decode PR query rows into named records, not positional tuples. -- [ ] Confirm real scratch-bus order, verdict, strikes, fire and void through Core tools. -- [ ] Confirm delivery through the CLI and run bounded local checks. -- [ ] Publish a reviewed draft PR; do not merge or install. +- [x] Confirm real scratch-bus order, verdict, strikes, explicit fire and void through Core tools. +- [x] Confirm main/unmerged delivery and unlinked refusal through the CLI. +- [x] Pass the two bounded record/codec sanity checks. Pytest is unavailable. +- [x] Publish draft PR #1935; do not merge or install. +- [ ] Automatic firing at the strike limit, with all strike sources in the same write. +- [ ] Retain original typed transitions and the state each operation replaces. +- [ ] Connect commander board/done rendering after the recent shared-function overlap is settled. +- [ ] Verify a real order merged to a non-main target (codec checked, not live-proven). ## Source relationships @@ -22,7 +27,8 @@ decoded a positional tuple of seven unrelated fields. `DeliveredProblemOrder.lin After: completion passes its canonical Todo and registry to the delivery owner. A checked commit records a `DeliveryMerge`: main, another PR target, or linked but unmerged source branches. `DeliveredProblemOrder.line` derives its text from that delivery. `WorkBranches` still owns the -open-PR/open-issue branch-link rule. PR lookup still uses the immutable order reference, now decoded +open-PR/open-issue branch-link rule. `PullRequests.listed` now owns exact order-reference matching; +`ProgressSource` no longer repeats that identity decision. PR lookup still uses the immutable order reference, now decoded through named query records including the target branch. No verdict, strike, delivery or branch store was added. The integration is the observation at completion, not a later claim of installation. @@ -49,4 +55,61 @@ Those functions are left untouched pending the integration owner's conflict deci `tools/agent_resource_check.py --assert-headroom` warned: about 4 GiB available RAM and 3.8 GiB free on /tmp. No parallel agents or large suite. The prescribed /home/ts/bin launcher is absent. -Owned verification state and logs will remain under this checkout's evidence directory, not /tmp. +Owned verification state is `/home/ts/wt/pi-cn-phase3--P1/.proof-record-plane` (real scratch bus, +copy of this unit's native transcript and temporary check trees). Logs are the checkout's `.proof-*.log`; +`.proof-test-state` is the empty intended pytest scratch directory. The check commands set TMPDIR +under the owned persistent root. These paths remain for the unfinished review; remove them after +acceptance, preserving the concise receipt below. The proof does not own other agents' /tmp worktrees. + +## Actual CLI proof + +Original response bodies: `docs/evidence/cn-phase3-record-results.jsonl` (12 actual responses, +normalized only for whitespace). Driver: `tools/proofs/cn_phase3_record.py`. The driver initializes +real Core stores and calls `python -m agent_comms.cli tool --from ...`. It uses no fake Comms, +fixture events or model replies. Judgment names a real edit from this unit's native session, +read through `OrderEditsRead` after copying the session to the scratch root. No original session +was changed and no live unit received these deliberate scratch-only strikes. + +| Actual operation | P1 revision | verdicts | retained strikes | voids | assignment | +|---|---:|---:|---:|---:|---| +| create | 1 | 0 | 0 | 0 | record-worker | +| accepted verdict | 2 | 1 | 0 | 0 | record-worker | +| first strike | 3 | 1 | 1 | 0 | record-worker | +| second strike | 4 | 1 | 2 | 0 | record-worker | +| explicit comms_fire | 5 | 1 | 2 | 0 | null | +| void second strike | 6 | 1 | 2 | 1 | null | + +After voiding, Core's `live_strikes(record-worker)` returns one. A void does not restore a released +assignment. The tool's assignee-only `strike_count` is zero once there is no assignment; the original +strikes and void are still retained on the Todo. + +P2's real completion returned `todo.order.delivery.merge = {"kind":"main"}`. P3's real completion +on this published draft branch returned `{"kind":"unmerged","branches":["feat/cn-phase3-record"]}`. +P4 used an existing published unlinked commit, without editing or publishing that branch. Core refused: + +``` +Delivered commit 2874a87ada914add901c7f318ea46c3c88aa555a is not merged and has no branch linked to an open PR or issue. +``` + +P4 remained pending at revision 1. The check can still leave its normal Git check note even when +completion is refused. These are source-CLI checks of the candidate, not an installed runtime claim. +Nothing was merged, published as a runtime, restarted or installed. + +## Local checks and precise blockers + +- The requested background `timeout 300 python -X faulthandler -m pytest ...` failed before + collection: the shared interpreter has no pytest. An isolated-venv attempt was refused by the + existing StorageRule; no alternate installation was attempted. A shared test interpreter with + pytest, or a reviewed dev-dependency installation by its owner, is needed for the suite. +- The two standalone record/codec sanity functions passed under the same bounded/background + command form. They check every DeliveryMerge member and named external non-main target decoding; + they are not substituted for the actual CLI proof. +- The real second-strike response still held P1. Main has a limit predicate and an explicit fire tool, + not automatic firing. The shared TodoStore/Goals write-path work overlaps recent #1878 and is raised + with claude-code. Source inspection also finds destructive order/assignment replacement, missing Core + recording timestamps and direct fold/correction writes. pi-cn-phase3 retains this implementation. +- pi-cn-phase5 owns SendBackVerdict/Goals.send_back publication, OrderPost rows/insertion and the + canonical order channel. Those changes must remain in the same transaction as retained operations; + this checkpoint does not edit their classes or TodoStore.rows/apply. Their exact published commit + is a dependency for that continuing write-path change. + diff --git a/src/agent_comms/todos.py b/src/agent_comms/todos.py index 8f83d57b36..3dcab05eb0 100644 --- a/src/agent_comms/todos.py +++ b/src/agent_comms/todos.py @@ -83,6 +83,10 @@ class OrderDelivery(DeclaredFamily, affix="Delivery"): def work(self) -> str: return "delivered work" + @property + def line(self) -> str: + return self.work + async def run(self, check, *command): result = await BoundedRun.run(tuple(command), cwd=check.directory, timeout=120) if result.outcome != ExitedOutcome(0): @@ -180,8 +184,8 @@ async def require_source(self, check: OrderCheck) -> None: f"Delivered commit {self.delivered} is on no origin branch; push it first.") async def verified(self, todo: Todo, registry: Registration) -> OrderDelivery: - merge = await self.merge_state(todo, registry) verified = await super().verified(todo, registry) + merge = await self.merge_state(todo, registry) return CheckedCommitDelivery(commit=self.commit, delivered_commit=self.delivered, at=verified.at, evidence=self.evidence, merge=merge) @@ -265,12 +269,16 @@ class CheckedCommitDelivery(CommitDelivery): merge: DeliveryMerge @property - def work(self) -> str: - return f"{super().work}; {self.merge.line}" + def line(self) -> str: + return f"{self.work}; {self.merge.line}" @dataclass(frozen=True, kw_only=True) class OperationDelivery(OrderDelivery): + @property + def line(self) -> str: + return "operation checked" + async def require_source(self, check): pass @@ -290,12 +298,12 @@ class PullRequest: merged_at: float | None = None updated_at: float = 0.0 orders: tuple[str, ...] = () - base: str = field(kw_only=True) + base: str = field(default=WorkBranches.main, kw_only=True) @classmethod @boundary_decoder def listed(cls, number: int, branch: str, head: str, merge: str, merged: str, - updated: str, body: str, base: str) -> Self: + updated: str, body: str, base: str = WorkBranches.main) -> Self: """One row of ``gh pr list``; GitHub leaves an unmerged PR's merge commit and time empty.""" return cls(int(number), branch, head, merge or None, _github_time(merged) if merged else None, _github_time(updated), @@ -344,7 +352,7 @@ def listed(self, repo: str, state: str, order: ProblemOrder) -> tuple[PullReques key = (repo, state, order.id) if key not in self._read: self._read[key] = self.read(repo, state, order) - return self._read[key] + return tuple(request for request in self._read[key] if order.id in request.orders) def read(self, repo: str, state: str, order: ProblemOrder) -> tuple[PullRequest, ...]: orders = self._orders.setdefault(repo, {}) @@ -530,7 +538,7 @@ def since(self) -> float: def requests(self, state: str) -> list[PullRequest]: """Explicit PR references survive checkout removal or branch reuse.""" return [pr for pr in self.pulls.listed(self.todo.repo, state, self.todo.order) - if self.todo.order.id in pr.orders and pr.updated_at >= self.since] + if pr.updated_at >= self.since] def commits(self, *excluded: str) -> Iterator[tuple[str, float]]: return self.checkout.commits(self.since, *excluded) @@ -1484,9 +1492,10 @@ class DroppedProblemOrder(UnorderedProblemOrder, CheckedProblemOrder): class DeliveredProblemOrder(CheckedProblemOrder): delivery: OrderDelivery pending: ClassVar[bool] = False + @property def line(self) -> str: - return f"Delivered: {self.delivery.work}, waiting on install" + return f"Delivered: {self.delivery.line}, waiting on install" def require_transfer(self, todo: Todo) -> None: owner = (todo.assignment or todo.released[-1].previous).owner diff --git a/tests/test_commander_problem_orders.py b/tests/test_commander_problem_orders.py index 6052ac9a2a..48aa48cc4a 100644 --- a/tests/test_commander_problem_orders.py +++ b/tests/test_commander_problem_orders.py @@ -234,7 +234,7 @@ def git(*args): git('init', '-q', '-b', 'main') git('commit', '-q', '--allow-empty', '-m', 'Delivered fix') commit = git('rev-parse', 'HEAD') - git('update-ref', 'refs/remotes/origin/fix', commit) + git('update-ref', 'refs/remotes/origin/main', commit) marker = comms.root / 'check-calls' create(commander, 'P1', f"printf x >> '{marker}'; printf 1") create(commander, 'P2') @@ -288,6 +288,7 @@ def git(*args, at=None): with pytest.raises(ValueError, match='no origin branch'): CommsOrderDoneTool(problem='P1', order_id=comms.goals.todos.get('P1').order.id, delivery=CommitDelivery(commit=merge, delivered_commit=delivered), claims=working(comms)).apply(comms, caller) git('update-ref', 'refs/remotes/origin/work', delivered) + git('update-ref', 'refs/remotes/origin/main', merge) script.done('P1', delivered, seen=working(comms)[0].screenshot) met = comms.goals.todos.get('P1') assert (met.order.delivery.commit, met.order.delivery.delivered, met.order.delivery.at) == ( @@ -1430,7 +1431,7 @@ def test_a_problem_goal_shows_only_its_live_orders_through_convert_deliver_send_ todo.assignment, 'toolu_3', reason=StrikeReason(why))).apply(comms, caller) worker = comms.registry.require('worker') progress = GoalSegment.capture(worker, worker.goal, comms.goals).progress - assert progress.splitlines() == ['P1: Delivered, waiting on install', f'P2: Open · Sent back: {why}'] + assert progress.splitlines() == ['P1: Delivered: operation checked, waiting on install', f'P2: Open · Sent back: {why}'] assert (worker.goal.text, worker.goal.progress) == (ProblemGoalOrder.text, '') with pytest.raises(ValueError, match='block the problem.s Todo, or standby with wait_for'): comms.goals.update_goal('worker', BlockedGoalAction(block_reason='Waiting on lead.'), actor=ModelInvocable) diff --git a/tests/test_delivery_merge.py b/tests/test_delivery_merge.py new file mode 100644 index 0000000000..45f060de7c --- /dev/null +++ b/tests/test_delivery_merge.py @@ -0,0 +1,30 @@ +"""Small record/codec checks; the real CLI proof is recorded separately.""" +from agent_comms.field_codec import FieldCodec +from agent_comms.todos import ( + BranchDeliveryMerge, CheckedCommitDelivery, DeliveryMerge, ListedPullRequest, + MainDeliveryMerge, OrderDelivery, UnmergedDeliveryMerge, +) + + +def test_delivery_integration_members_keep_their_data_through_the_only_codec(): + for state in (MainDeliveryMerge(), BranchDeliveryMerge('release', 'b' * 40), + UnmergedDeliveryMerge(('fix/order',))): + delivery = CheckedCommitDelivery(commit='a' * 40, delivered_commit='a' * 40, merge=state) + restored = FieldCodec.decode(OrderDelivery, FieldCodec.encode(delivery)) + assert restored == delivery + assert FieldCodec.decode(DeliveryMerge, FieldCodec.encode(state)) == state + assert state.line in restored.line + assert restored.work == f'delivered commit {"a" * 40}' + + +def test_named_forge_row_preserves_the_actual_nonmain_merge_target(): + row = FieldCodec.decode(ListedPullRequest, FieldCodec.parse('''{ + "number": 42, "branch": "fix/order", "head": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "merge": {"oid": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"}, + "merged": "2026-10-19T12:00:00Z", "updated": "2026-10-19T12:00:01Z", + "body": "Agent-Comms-Order: proof-order", "base": "release" + }''')) + request = row.parsed() + assert request.base == 'release' + assert request.merge == 'b' * 40 + assert request.orders == ('proof-order',) diff --git a/tests/test_standing_rules.py b/tests/test_standing_rules.py index 18162603fa..ff6f4adf20 100644 --- a/tests/test_standing_rules.py +++ b/tests/test_standing_rules.py @@ -57,11 +57,11 @@ def test_board_self_check_uses_order_progress_and_its_linked_merge( owned, pending, foreign = (create(commander, pid) for pid in ("P77", "P109", "P117")) stamp = "1970-01-01T00:08:20Z" merged = PullRequest.listed(1581, "docs/module-layers", "b" * 40, "d" * 40, - stamp, stamp, owned.order.pull_request_body("Module layer map")) + stamp, stamp, owned.order.pull_request_body("Module layer map"), 'main') opened = PullRequest.listed(1624, "ratchet/none-absence", "c" * 40, "", "", - stamp, pending.order.pull_request_body("Declared measure")) + stamp, pending.order.pull_request_body("Declared measure"), 'main') other = PullRequest.listed(1629, "ratchet/decoder-marker", "e" * 40, "f" * 40, - stamp, stamp, "Agent-Comms-Order: another-order\n") + stamp, stamp, "Agent-Comms-Order: another-order\n", 'main') def forge(self, repo, state, order): return (merged, other) if state == "merged" else (opened,) diff --git a/tools/proofs/cn_phase3_record.py b/tools/proofs/cn_phase3_record.py new file mode 100644 index 0000000000..d8577e3503 --- /dev/null +++ b/tools/proofs/cn_phase3_record.py @@ -0,0 +1,144 @@ +"""Exercise real Core records on an isolated bus; never start or replay a model turn. + +Run setup, perform a real tool edit in the owning native session, then run record with that +session path. All observations below are CLI tool results, not hand-constructed events. +""" +from __future__ import annotations + +import argparse +import json +import os +from pathlib import Path +import shutil +import subprocess +import sys +import time + +from agent_comms.change_report import FileLine +from agent_comms.comms import Comms +from agent_comms.field_codec import FieldCodec +from agent_comms.goal_actions import CreateProblemGoalAction, SetGoalAction +from agent_comms.goals import CountCheckResult, OrderCheck, RequestGoalOrder +from agent_comms.recorded_time import RecordedEventTime +from agent_comms.tools import (CommsFireTool, CommsOrderDoneTool, CommsTodoCreateTool, + CommsTodoTool, CommsVerdictTool) +from agent_comms.todos import (AcceptedVerdict, CommitDelivery, MechanicalOrderRun, + PendingProblemOrder, StrikeReason, StrikeTodo, VoidStrikeTodo) +from agent_comms.progress_claims import MergedClaim + +CHECKOUT = Path(__file__).resolve().parents[2] +ROOT = CHECKOUT / ".proof-record-plane" +RECEIPT = CHECKOUT / "docs/evidence/cn-phase3-record-results.jsonl" + + +def cli(caller, tool): + arguments = FieldCodec.encode(tool) + del arguments[tool.family_discriminator] + command = [sys.executable, "-m", "agent_comms.cli", "--root", str(ROOT), "tool", + "--from", caller, "--tool", tool.declared_name, + "--arguments", json.dumps(arguments)] + result = subprocess.run(command, capture_output=True, text=True, timeout=180, + env=os.environ | {"PYTHONPATH": str(CHECKOUT / "src")}) + print(result.stdout, end="", flush=True) + if result.returncode: + raise RuntimeError(result.stderr or result.stdout) + with RECEIPT.open("a") as output: + output.write(result.stdout) + return json.loads(result.stdout) + + +def setup(): + if ROOT.exists(): + raise RuntimeError(f"Refusing to reuse an existing proof root: {ROOT}") + comms = Comms(ROOT) + comms.messaging.initialize_private_initial_protocol() + for name in ("record-lead", "record-worker"): + comms.registry.declare(comms.registry.new_thread( + name, frozenset(), str(CHECKOUT), created_at=time.time())) + comms.goals.update_goal("record-lead", SetGoalAction( + text="Confirm the order record through the actual Core tools on this isolated bus", + goal_id="record-request", order=RequestGoalOrder())) + comms.goals.update_goal("record-worker", SetGoalAction( + text="Complete the assigned record-plane proof without touching any live session", + goal_id="record-work")) + create_order() + + +def create_order(problem="P1"): + now = time.time() + cli("record-lead", CommsTodoCreateTool(owner="record-worker", order=CreateProblemGoalAction( + problem=problem, repo="OpenHCSDev/agent-comms", + text="Show the real verdict and strike records without a second source", + request="record-request", commander="record-lead", + deadline=RecordedEventTime(now + 3600), order=PendingProblemOrder( + id=f"record-order-{problem}", owner=FileLine(str(CHECKOUT / "src/agent_comms/todos.py"), 69), + check=OrderCheck(str(CHECKOUT), "printf 1", CountCheckResult(1)), + edits=("Decode pull request target branches through the existing record owner",), + banned=("No second order store",), report="Report the actual Core tool response", + run=MechanicalOrderRun(), at=RecordedEventTime(now), + first_call_deadline=RecordedEventTime(now + 120))))) + + +def record(session: Path): + from dataclasses import replace + from agent_comms.turn_digests import OrderEditsRead, TurnsSince + + retained = ROOT / "record-worker.jsonl" + shutil.copy2(session, retained) + comms = Comms(ROOT) + worker = comms.registry.require("record-worker") + comms.registry.register(replace(worker, session_file=str(retained)), + comms.registry.status(worker.name)) + todo = comms.goals.todos.get("P1") + since = todo.order.at.recorded_at("An order") + calls = OrderEditsRead(ROOT, comms.registry.require(worker.name), TurnsSince(since), todo).read() + if not calls: + raise RuntimeError("No actual native edit call followed the order; do not invent one") + judged = calls[0].id + cli("record-lead", CommsVerdictTool(id="P1", operation=AcceptedVerdict( + todo.revision, comms.registry.require("record-lead").incarnation, todo.held(), judged, + quote="Decode pull request target branches"))) + for reason in ("The assigned change left the target branch out of the saved order record", + "The second review found the target branch still missing from the saved record"): + todo = comms.goals.todos.get("P1") + cli("record-lead", CommsTodoTool(id="P1", operation=StrikeTodo( + todo.revision, comms.registry.require("record-lead").incarnation, todo.held(), reason))) + todo = comms.goals.todos.get("P1") + assert todo.struck_out(worker.name) + cli("record-lead", CommsFireTool(unit=worker.name, reason=StrikeReason( + "The unit left the required target branch absent after both recorded reviews"))) + todo = comms.goals.todos.get("P1") + cli("record-lead", CommsTodoTool(id="P1", operation=VoidStrikeTodo( + todo.revision, comms.registry.require("record-lead").incarnation, todo.strikes[-1], + "The second strike repeated the first finding rather than describing another failed change"))) + todo = comms.goals.todos.get("P1") + assert len(todo.strikes) == 2 and len(todo.voided) == 1 + assert len(todo.live_strikes(worker.name)) == 1 and todo.assignment is None + + +def deliver(problem: str, delivered: str, checked: str | None): + create_order(problem) + checked = checked or subprocess.check_output( + ["git", "rev-parse", "HEAD"], cwd=CHECKOUT, text=True).strip() + cli("record-lead", CommsOrderDoneTool( + problem=problem, order_id=f"record-order-{problem}", + delivery=CommitDelivery(commit=checked, delivered_commit=delivered), + claims=(MergedClaim(delivered),))) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("step", choices=("setup", "order", "record", "deliver")) + parser.add_argument("--session", type=Path) + parser.add_argument("--problem") + parser.add_argument("--delivered") + parser.add_argument("--checked") + args = parser.parse_args() + if args.step == "setup": + setup() + elif args.step == "order": + create_order() + elif args.step == "deliver": + deliver(args.problem, args.delivered, args.checked) + else: + record(args.session) From 3cb866fd9e4b7f4bcdd0f169cc5691fec8c5d8f2 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 14:10:23 -0400 Subject: [PATCH 03/22] Record final candidate checks and outstanding record-plane blockers --- docs/evidence/cn-phase3-record.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/evidence/cn-phase3-record.md b/docs/evidence/cn-phase3-record.md index f9f9efc930..5bfcdef34f 100644 --- a/docs/evidence/cn-phase3-record.md +++ b/docs/evidence/cn-phase3-record.md @@ -32,8 +32,8 @@ open-PR/open-issue branch-link rule. `PullRequests.listed` now owns exact order- through named query records including the target branch. No verdict, strike, delivery or branch store was added. The integration is the observation at completion, not a later claim of installation. -AST evidence used refactor-audit's `Package.load` with Python 3.14: 382 production modules parsed, -zero omissions. The only production `OrderDelivery.verified` caller is `DeliverTodo.prepare`; +Before/after AST evidence used refactor-audit's `Package.load` with Python 3.14: 382 Core production +modules parsed, zero omissions. The final installed-Toad syntax pass parsed 283 modules, zero omissions. The only production `OrderDelivery.verified` caller is `DeliverTodo.prepare`; creation is in `commander.order.done` and `CommsOrderDoneTool`. Goal summaries consume the order's `line`. The installed Toad source has no order-delivery consumer; its unrelated input-delivery types are not this family. A preliminary NRA run under its old Python 3.12 omitted Python 3.14 syntax; @@ -101,7 +101,8 @@ Nothing was merged, published as a runtime, restarted or installed. collection: the shared interpreter has no pytest. An isolated-venv attempt was refused by the existing StorageRule; no alternate installation was attempted. A shared test interpreter with pytest, or a reviewed dev-dependency installation by its owner, is needed for the suite. -- The two standalone record/codec sanity functions passed under the same bounded/background +- Final checks on source checkpoint ec5f94568: Core/Toad AST passes above, the candidate CLI entrypoint, + and both standalone record/codec sanity functions passed under the same bounded/background command form. They check every DeliveryMerge member and named external non-main target decoding; they are not substituted for the actual CLI proof. - The real second-strike response still held P1. Main has a limit predicate and an explicit fire tool, @@ -112,4 +113,3 @@ Nothing was merged, published as a runtime, restarted or installed. canonical order channel. Those changes must remain in the same transaction as retained operations; this checkpoint does not edit their classes or TodoStore.rows/apply. Their exact published commit is a dependency for that continuing write-path change. - From 1b8eca35f76a5bb54de64a4d041c74dbe48ebf2b Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 14:15:21 -0400 Subject: [PATCH 04/22] Decode named PR rows once and retain post-main CLI acceptance --- docs/evidence/cn-phase3-record-results.jsonl | 2 ++ docs/evidence/cn-phase3-record.md | 10 +++++++--- src/agent_comms/todos.py | 17 ++++------------- tests/test_delivery_merge.py | 1 + tests/test_order_progress.py | 4 ++-- tests/test_standing_rules.py | 13 ++++++------- 6 files changed, 22 insertions(+), 25 deletions(-) diff --git a/docs/evidence/cn-phase3-record-results.jsonl b/docs/evidence/cn-phase3-record-results.jsonl index a525dd2535..bc9ea0c04d 100644 --- a/docs/evidence/cn-phase3-record-results.jsonl +++ b/docs/evidence/cn-phase3-record-results.jsonl @@ -10,3 +10,5 @@ {"todo":{"row":"todo","id":"P3","repo":"openhcsdev/agent-comms","text":"Show the real verdict and strike records without a second source","creator":"record-lead","creator_created":1792432385.1369143,"state":"open","revision":2,"goal":{"owner":"record-lead","owner_created":1792432385.1369143,"goal_id":"record-request"},"assignment":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"order":{"kind":"delivered","id":"record-order-P3","owner":"/home/ts/wt/pi-cn-phase3--P1/src/agent_comms/todos.py:69","check":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf 1","result":{"kind":"count","expected":1}},"edits":["Decode pull request target branches through the existing record owner"],"banned":["No second order store"],"report":"Report the actual Core tool response","at":{"kind":"recorded","at":1792432771.201293},"first_call_deadline":{"kind":"recorded","at":1792432891.201293},"first_call":{"kind":"awaiting"},"run":{"kind":"mechanical"},"delivery":{"kind":"checked_commit","at":{"kind":"recorded","at":1792432302.0},"commit":"e0ca0150042587e7494184b5ab2f6701f3487076","delivered_commit":"e0ca01500","merge":{"kind":"unmerged","branches":["feat/cn-phase3-record"]}}},"deadline":{"kind":"recorded","at":1792436371.201293},"strikes":[],"voided":[],"released":[],"verdicts":[],"corrections":[],"messages":[]},"strike_count":0} {"goal":{"text":"Complete the assigned problems.","id":"record-work","published_seq":0,"progress":"","revision":5,"reported_turn":null,"mention_source":{"goal_id":"record-work","text_revision":5,"text_digest":"816861a4f5b6cbf17b6bf4e4a3d9f87e1b8bdcd32bcf98c2b447f2a3a9be80b7","owner_name":"record-worker","owner_created_at":1792432385.2149153,"bindings":[]},"state":{"kind":"active"},"order":{"kind":"problem"}},"todo":{"row":"todo","id":"P4","repo":"openhcsdev/agent-comms","text":"Show the real verdict and strike records without a second source","creator":"record-lead","creator_created":1792432385.1369143,"state":"open","revision":1,"goal":{"owner":"record-lead","owner_created":1792432385.1369143,"goal_id":"record-request"},"assignment":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"order":{"kind":"pending","id":"record-order-P4","owner":"/home/ts/wt/pi-cn-phase3--P1/src/agent_comms/todos.py:69","check":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf 1","result":{"kind":"count","expected":1}},"edits":["Decode pull request target branches through the existing record owner"],"banned":["No second order store"],"report":"Report the actual Core tool response","at":{"kind":"recorded","at":1792433118.0605557},"first_call_deadline":{"kind":"recorded","at":1792433238.0605557},"first_call":{"kind":"awaiting"},"run":{"kind":"mechanical"}},"deadline":{"kind":"recorded","at":1792436718.0605557},"strikes":[],"voided":[],"released":[],"verdicts":[],"corrections":[],"messages":[]}} {"error":"Delivered commit 2874a87ada914add901c7f318ea46c3c88aa555a is not merged and has no branch linked to an open PR or issue.","type":"failed"} +{"goal":{"text":"Complete the assigned problems.","id":"record-work","published_seq":0,"progress":"","revision":6,"reported_turn":null,"mention_source":{"goal_id":"record-work","text_revision":6,"text_digest":"816861a4f5b6cbf17b6bf4e4a3d9f87e1b8bdcd32bcf98c2b447f2a3a9be80b7","owner_name":"record-worker","owner_created_at":1792432385.2149153,"bindings":[]},"state":{"kind":"active"},"order":{"kind":"problem"}},"todo":{"row":"todo","id":"P5","repo":"openhcsdev/agent-comms","text":"Show the real verdict and strike records without a second source","creator":"record-lead","creator_created":1792432385.1369143,"state":"open","revision":1,"goal":{"owner":"record-lead","owner_created":1792432385.1369143,"goal_id":"record-request"},"assignment":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"order":{"kind":"pending","id":"record-order-P5","owner":"/home/ts/wt/pi-cn-phase3--P1/src/agent_comms/todos.py:69","check":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf 1","result":{"kind":"count","expected":1}},"edits":["Decode pull request target branches through the existing record owner"],"banned":["No second order store"],"report":"Report the actual Core tool response","at":{"kind":"recorded","at":1792433505.0452404},"first_call_deadline":{"kind":"recorded","at":1792433625.0452404},"first_call":{"kind":"awaiting"},"run":{"kind":"mechanical"}},"deadline":{"kind":"recorded","at":1792437105.0452404},"strikes":[],"voided":[],"released":[],"verdicts":[],"corrections":[],"messages":[]}} +{"todo":{"row":"todo","id":"P5","repo":"openhcsdev/agent-comms","text":"Show the real verdict and strike records without a second source","creator":"record-lead","creator_created":1792432385.1369143,"state":"open","revision":2,"goal":{"owner":"record-lead","owner_created":1792432385.1369143,"goal_id":"record-request"},"assignment":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"order":{"kind":"delivered","id":"record-order-P5","owner":"/home/ts/wt/pi-cn-phase3--P1/src/agent_comms/todos.py:69","check":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf 1","result":{"kind":"count","expected":1}},"edits":["Decode pull request target branches through the existing record owner"],"banned":["No second order store"],"report":"Report the actual Core tool response","at":{"kind":"recorded","at":1792433505.0452404},"first_call_deadline":{"kind":"recorded","at":1792433625.0452404},"first_call":{"kind":"awaiting"},"run":{"kind":"mechanical"},"delivery":{"kind":"checked_commit","at":{"kind":"recorded","at":1792433301.0},"commit":"256efcc8b6d632e224a3a4ffe7d5d1b49c4ae7e5","delivered_commit":"ec5f94568","merge":{"kind":"unmerged","branches":["feat/cn-phase3-record"]}}},"deadline":{"kind":"recorded","at":1792437105.0452404},"strikes":[],"voided":[],"released":[],"verdicts":[],"corrections":[],"messages":[]},"strike_count":0} diff --git a/docs/evidence/cn-phase3-record.md b/docs/evidence/cn-phase3-record.md index 5bfcdef34f..d06a2449de 100644 --- a/docs/evidence/cn-phase3-record.md +++ b/docs/evidence/cn-phase3-record.md @@ -29,7 +29,8 @@ records a `DeliveryMerge`: main, another PR target, or linked but unmerged sourc `DeliveredProblemOrder.line` derives its text from that delivery. `WorkBranches` still owns the open-PR/open-issue branch-link rule. `PullRequests.listed` now owns exact order-reference matching; `ProgressSource` no longer repeats that identity decision. PR lookup still uses the immutable order reference, now decoded -through named query records including the target branch. No verdict, strike, delivery or branch store +through named query records including the target branch. The old scalar `PullRequest.listed` +decoder is deleted; typed query rows are no longer decoded a second time. No verdict, strike, delivery or branch store was added. The integration is the observation at completion, not a later claim of installation. Before/after AST evidence used refactor-audit's `Package.load` with Python 3.14: 382 Core production @@ -63,7 +64,7 @@ acceptance, preserving the concise receipt below. The proof does not own other a ## Actual CLI proof -Original response bodies: `docs/evidence/cn-phase3-record-results.jsonl` (12 actual responses, +Original response bodies: `docs/evidence/cn-phase3-record-results.jsonl` (14 actual responses, normalized only for whitespace). Driver: `tools/proofs/cn_phase3_record.py`. The driver initializes real Core stores and calls `python -m agent_comms.cli tool --from ...`. It uses no fake Comms, fixture events or model replies. Judgment names a real edit from this unit's native session, @@ -93,7 +94,10 @@ Delivered commit 2874a87ada914add901c7f318ea46c3c88aa555a is not merged and has P4 remained pending at revision 1. The check can still leave its normal Git check note even when completion is refused. These are source-CLI checks of the candidate, not an installed runtime claim. -Nothing was merged, published as a runtime, restarted or installed. +No PR was merged, and nothing was published as a runtime, restarted or installed. Latest main +was merged normally into this branch at 256efcc8b. The two codec checks and CLI entrypoint passed +again there; a fresh P5 order then completed as unmerged through the actual CLI. The post-merge +Core AST pass parsed 384 modules with zero omissions. ## Local checks and precise blockers diff --git a/src/agent_comms/todos.py b/src/agent_comms/todos.py index 3dcab05eb0..796ae08eee 100644 --- a/src/agent_comms/todos.py +++ b/src/agent_comms/todos.py @@ -32,7 +32,6 @@ from .errors import ToolRefusedError from .field_codec import ( FieldCodec, GitCommitText, MalformedRecord, NullMember, RecordMember, SubclassUnion, WireValue, - boundary_decoder, ) from .channel_targets import CommandTarget from .goals import CountCheckResult, OrderCheck, ProblemGoalOrder @@ -300,15 +299,6 @@ class PullRequest: orders: tuple[str, ...] = () base: str = field(default=WorkBranches.main, kw_only=True) - @classmethod - @boundary_decoder - def listed(cls, number: int, branch: str, head: str, merge: str, merged: str, - updated: str, body: str, base: str = WorkBranches.main) -> Self: - """One row of ``gh pr list``; GitHub leaves an unmerged PR's merge commit and time empty.""" - return cls(int(number), branch, head, merge or None, - _github_time(merged) if merged else None, _github_time(updated), - ProblemOrder.pull_request_ids(body), base=base) - def _github_time(text: str) -> float: return calendar.timegm(time.strptime(text, "%Y-%m-%dT%H:%M:%SZ")) @@ -333,9 +323,10 @@ class ListedPullRequest: base: str def parsed(self) -> PullRequest: - return PullRequest.listed( - self.number, self.branch, self.head, self.merge.oid if self.merge else "", - self.merged or "", self.updated, self.body, self.base) + return PullRequest( + self.number, self.branch, self.head, self.merge.oid if self.merge else None, + _github_time(self.merged) if self.merged else None, _github_time(self.updated), + ProblemOrder.pull_request_ids(self.body), base=self.base) class PullRequests: diff --git a/tests/test_delivery_merge.py b/tests/test_delivery_merge.py index 45f060de7c..af1d96d0b0 100644 --- a/tests/test_delivery_merge.py +++ b/tests/test_delivery_merge.py @@ -27,4 +27,5 @@ def test_named_forge_row_preserves_the_actual_nonmain_merge_target(): request = row.parsed() assert request.base == 'release' assert request.merge == 'b' * 40 + assert request.merged_at < request.updated_at assert request.orders == ('proof-order',) diff --git a/tests/test_order_progress.py b/tests/test_order_progress.py index 96cbed66d2..4637ee1070 100644 --- a/tests/test_order_progress.py +++ b/tests/test_order_progress.py @@ -361,8 +361,8 @@ def forge(command, **arguments): monkeypatch.setattr(subprocess, "run", forge) script.PullRequestVerb(pid=pid, title=subject, body_file=str(body_file)).run() assert todo.order.pull_request_ids(bodies[0]) == (todo.order.id,) - stamp = "1970-01-01T00:08:20Z" - merged = PullRequest.listed(number, branch, "b" * 40, "d" * 40, stamp, stamp, bodies[0]) + merged = PullRequest(number, branch, "b" * 40, "d" * 40, + merged_at=500, updated_at=500, orders=todo.order.pull_request_ids(bodies[0])) foreign = replace(merged, number=number + 1, merge="f" * 40, orders=("another-order",)) monkeypatch.setattr(PullRequests, "read", lambda self, repo, state, order: (foreign, merged) if state == "merged" else ()) diff --git a/tests/test_standing_rules.py b/tests/test_standing_rules.py index ff6f4adf20..f5883185a2 100644 --- a/tests/test_standing_rules.py +++ b/tests/test_standing_rules.py @@ -55,13 +55,12 @@ def test_board_self_check_uses_order_progress_and_its_linked_merge( commander, standing, monkeypatch, reported, alarms): comms, caller = commander owned, pending, foreign = (create(commander, pid) for pid in ("P77", "P109", "P117")) - stamp = "1970-01-01T00:08:20Z" - merged = PullRequest.listed(1581, "docs/module-layers", "b" * 40, "d" * 40, - stamp, stamp, owned.order.pull_request_body("Module layer map"), 'main') - opened = PullRequest.listed(1624, "ratchet/none-absence", "c" * 40, "", "", - stamp, pending.order.pull_request_body("Declared measure"), 'main') - other = PullRequest.listed(1629, "ratchet/decoder-marker", "e" * 40, "f" * 40, - stamp, stamp, "Agent-Comms-Order: another-order\n", 'main') + merged = PullRequest(1581, "docs/module-layers", "b" * 40, "d" * 40, + merged_at=500, updated_at=500, orders=(owned.order.id,)) + opened = PullRequest(1624, "ratchet/none-absence", "c" * 40, + updated_at=500, orders=(pending.order.id,)) + other = PullRequest(1629, "ratchet/decoder-marker", "e" * 40, "f" * 40, + merged_at=500, updated_at=500, orders=("another-order",)) def forge(self, repo, state, order): return (merged, other) if state == "merged" else (opened,) From e44cd2013ea04b28aa5bc7c645cae1a557f2ffee Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 14:30:17 -0400 Subject: [PATCH 05/22] Keep remote progress when the local checkout is absent --- docs/evidence/cn-phase3-record-results.jsonl | 1 + docs/evidence/cn-phase3-record.md | 56 ++++++++++-- src/agent_comms/todos.py | 90 ++++++++++++++------ tests/test_order_progress.py | 19 ++++- tools/proofs/cn_phase3_record.py | 2 +- 5 files changed, 133 insertions(+), 35 deletions(-) diff --git a/docs/evidence/cn-phase3-record-results.jsonl b/docs/evidence/cn-phase3-record-results.jsonl index bc9ea0c04d..6ad87cc68e 100644 --- a/docs/evidence/cn-phase3-record-results.jsonl +++ b/docs/evidence/cn-phase3-record-results.jsonl @@ -12,3 +12,4 @@ {"error":"Delivered commit 2874a87ada914add901c7f318ea46c3c88aa555a is not merged and has no branch linked to an open PR or issue.","type":"failed"} {"goal":{"text":"Complete the assigned problems.","id":"record-work","published_seq":0,"progress":"","revision":6,"reported_turn":null,"mention_source":{"goal_id":"record-work","text_revision":6,"text_digest":"816861a4f5b6cbf17b6bf4e4a3d9f87e1b8bdcd32bcf98c2b447f2a3a9be80b7","owner_name":"record-worker","owner_created_at":1792432385.2149153,"bindings":[]},"state":{"kind":"active"},"order":{"kind":"problem"}},"todo":{"row":"todo","id":"P5","repo":"openhcsdev/agent-comms","text":"Show the real verdict and strike records without a second source","creator":"record-lead","creator_created":1792432385.1369143,"state":"open","revision":1,"goal":{"owner":"record-lead","owner_created":1792432385.1369143,"goal_id":"record-request"},"assignment":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"order":{"kind":"pending","id":"record-order-P5","owner":"/home/ts/wt/pi-cn-phase3--P1/src/agent_comms/todos.py:69","check":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf 1","result":{"kind":"count","expected":1}},"edits":["Decode pull request target branches through the existing record owner"],"banned":["No second order store"],"report":"Report the actual Core tool response","at":{"kind":"recorded","at":1792433505.0452404},"first_call_deadline":{"kind":"recorded","at":1792433625.0452404},"first_call":{"kind":"awaiting"},"run":{"kind":"mechanical"}},"deadline":{"kind":"recorded","at":1792437105.0452404},"strikes":[],"voided":[],"released":[],"verdicts":[],"corrections":[],"messages":[]}} {"todo":{"row":"todo","id":"P5","repo":"openhcsdev/agent-comms","text":"Show the real verdict and strike records without a second source","creator":"record-lead","creator_created":1792432385.1369143,"state":"open","revision":2,"goal":{"owner":"record-lead","owner_created":1792432385.1369143,"goal_id":"record-request"},"assignment":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"order":{"kind":"delivered","id":"record-order-P5","owner":"/home/ts/wt/pi-cn-phase3--P1/src/agent_comms/todos.py:69","check":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf 1","result":{"kind":"count","expected":1}},"edits":["Decode pull request target branches through the existing record owner"],"banned":["No second order store"],"report":"Report the actual Core tool response","at":{"kind":"recorded","at":1792433505.0452404},"first_call_deadline":{"kind":"recorded","at":1792433625.0452404},"first_call":{"kind":"awaiting"},"run":{"kind":"mechanical"},"delivery":{"kind":"checked_commit","at":{"kind":"recorded","at":1792433301.0},"commit":"256efcc8b6d632e224a3a4ffe7d5d1b49c4ae7e5","delivered_commit":"ec5f94568","merge":{"kind":"unmerged","branches":["feat/cn-phase3-record"]}}},"deadline":{"kind":"recorded","at":1792437105.0452404},"strikes":[],"voided":[],"released":[],"verdicts":[],"corrections":[],"messages":[]},"strike_count":0} +{"todos":[{"row":"todo","id":"P1","repo":"openhcsdev/agent-comms","text":"Show the real verdict and strike records without a second source","creator":"record-lead","creator_created":1792432385.1369143,"state":"open","revision":6,"goal":{"owner":"record-lead","owner_created":1792432385.1369143,"goal_id":"record-request"},"assignment":null,"order":{"kind":"pending","id":"record-order-P1","owner":"/home/ts/wt/pi-cn-phase3--P1/src/agent_comms/todos.py:69","check":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf 1","result":{"kind":"count","expected":1}},"edits":["Decode pull request target branches through the existing record owner"],"banned":["No second order store"],"report":"Report the actual Core tool response","at":{"kind":"recorded","at":1792432466.497828},"first_call_deadline":{"kind":"recorded","at":1792432586.497828},"first_call":{"kind":"awaiting"},"run":{"kind":"mechanical"}},"deadline":{"kind":"recorded","at":1792436066.497828},"strikes":[{"kind":"strike_todo","expected_revision":2,"actor":{"name":"record-lead","created_at":1792432385.1369143},"previous":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"reason":"The assigned change left the target branch out of the saved order record","at":{"kind":"recorded","at":1792432640.0348916}},{"kind":"strike_todo","expected_revision":3,"actor":{"name":"record-lead","created_at":1792432385.1369143},"previous":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"reason":"The second review found the target branch still missing from the saved record","at":{"kind":"recorded","at":1792432652.476716}}],"voided":[{"kind":"void_strike_todo","expected_revision":5,"actor":{"name":"record-lead","created_at":1792432385.1369143},"strike":{"kind":"strike_todo","expected_revision":3,"actor":{"name":"record-lead","created_at":1792432385.1369143},"previous":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"reason":"The second review found the target branch still missing from the saved record","at":{"kind":"recorded","at":1792432652.476716}},"reason":"The second strike repeated the first finding rather than describing another failed change","at":{"kind":"recorded","at":1792432680.216849}}],"released":[{"kind":"release_todo","expected_revision":4,"actor":{"name":"record-lead","created_at":1792432385.1369143},"previous":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"reason":"The unit left the required target branch absent after both recorded reviews","at":{"kind":"recorded","at":1792432675.6006632}}],"verdicts":[{"kind":"accepted_verdict","expected_revision":1,"actor":{"name":"record-lead","created_at":1792432385.1369143},"previous":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"tool_call_id":"call_a3e3f7350f4e44faa55a72bebf904fa6|fc_01744e18603d1183016ad659dcd6c081a3a769ec0e1880e266","at":{"kind":"recorded","at":1792432628.8483949},"quote":"Decode pull request target branches"}],"corrections":[],"messages":[]},{"row":"todo","id":"P2","repo":"openhcsdev/agent-comms","text":"Show the real verdict and strike records without a second source","creator":"record-lead","creator_created":1792432385.1369143,"state":"open","revision":2,"goal":{"owner":"record-lead","owner_created":1792432385.1369143,"goal_id":"record-request"},"assignment":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"order":{"kind":"delivered","id":"record-order-P2","owner":"/home/ts/wt/pi-cn-phase3--P1/src/agent_comms/todos.py:69","check":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf 1","result":{"kind":"count","expected":1}},"edits":["Decode pull request target branches through the existing record owner"],"banned":["No second order store"],"report":"Report the actual Core tool response","at":{"kind":"recorded","at":1792432735.6061842},"first_call_deadline":{"kind":"recorded","at":1792432855.6061842},"first_call":{"kind":"awaiting"},"run":{"kind":"mechanical"},"delivery":{"kind":"checked_commit","at":{"kind":"recorded","at":1792431763.0},"commit":"e0ca0150042587e7494184b5ab2f6701f3487076","delivered_commit":"5cdac561d498ece25142a8d2e5cf9b06002b6c8a","merge":{"kind":"main"}}},"deadline":{"kind":"recorded","at":1792436335.6061842},"strikes":[],"voided":[],"released":[],"verdicts":[],"corrections":[],"messages":[]},{"row":"todo","id":"P3","repo":"openhcsdev/agent-comms","text":"Show the real verdict and strike records without a second source","creator":"record-lead","creator_created":1792432385.1369143,"state":"open","revision":2,"goal":{"owner":"record-lead","owner_created":1792432385.1369143,"goal_id":"record-request"},"assignment":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"order":{"kind":"delivered","id":"record-order-P3","owner":"/home/ts/wt/pi-cn-phase3--P1/src/agent_comms/todos.py:69","check":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf 1","result":{"kind":"count","expected":1}},"edits":["Decode pull request target branches through the existing record owner"],"banned":["No second order store"],"report":"Report the actual Core tool response","at":{"kind":"recorded","at":1792432771.201293},"first_call_deadline":{"kind":"recorded","at":1792432891.201293},"first_call":{"kind":"awaiting"},"run":{"kind":"mechanical"},"delivery":{"kind":"checked_commit","at":{"kind":"recorded","at":1792432302.0},"commit":"e0ca0150042587e7494184b5ab2f6701f3487076","delivered_commit":"e0ca01500","merge":{"kind":"unmerged","branches":["feat/cn-phase3-record"]}}},"deadline":{"kind":"recorded","at":1792436371.201293},"strikes":[],"voided":[],"released":[],"verdicts":[],"corrections":[],"messages":[]},{"row":"todo","id":"P4","repo":"openhcsdev/agent-comms","text":"Show the real verdict and strike records without a second source","creator":"record-lead","creator_created":1792432385.1369143,"state":"open","revision":1,"goal":{"owner":"record-lead","owner_created":1792432385.1369143,"goal_id":"record-request"},"assignment":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"order":{"kind":"pending","id":"record-order-P4","owner":"/home/ts/wt/pi-cn-phase3--P1/src/agent_comms/todos.py:69","check":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf 1","result":{"kind":"count","expected":1}},"edits":["Decode pull request target branches through the existing record owner"],"banned":["No second order store"],"report":"Report the actual Core tool response","at":{"kind":"recorded","at":1792433118.0605557},"first_call_deadline":{"kind":"recorded","at":1792433238.0605557},"first_call":{"kind":"awaiting"},"run":{"kind":"mechanical"}},"deadline":{"kind":"recorded","at":1792436718.0605557},"strikes":[],"voided":[],"released":[],"verdicts":[],"corrections":[],"messages":[]},{"row":"todo","id":"P5","repo":"openhcsdev/agent-comms","text":"Show the real verdict and strike records without a second source","creator":"record-lead","creator_created":1792432385.1369143,"state":"open","revision":2,"goal":{"owner":"record-lead","owner_created":1792432385.1369143,"goal_id":"record-request"},"assignment":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"order":{"kind":"delivered","id":"record-order-P5","owner":"/home/ts/wt/pi-cn-phase3--P1/src/agent_comms/todos.py:69","check":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf 1","result":{"kind":"count","expected":1}},"edits":["Decode pull request target branches through the existing record owner"],"banned":["No second order store"],"report":"Report the actual Core tool response","at":{"kind":"recorded","at":1792433505.0452404},"first_call_deadline":{"kind":"recorded","at":1792433625.0452404},"first_call":{"kind":"awaiting"},"run":{"kind":"mechanical"},"delivery":{"kind":"checked_commit","at":{"kind":"recorded","at":1792433301.0},"commit":"256efcc8b6d632e224a3a4ffe7d5d1b49c4ae7e5","delivered_commit":"ec5f94568","merge":{"kind":"unmerged","branches":["feat/cn-phase3-record"]}}},"deadline":{"kind":"recorded","at":1792437105.0452404},"strikes":[],"voided":[],"released":[],"verdicts":[],"corrections":[],"messages":[]}],"progress":{"P2":{"kind":"no_commit"},"P3":{"kind":"no_commit"},"P4":{"kind":"no_commit"},"P5":{"kind":"open_pr","at":1792434165,"readings":{"readings":[]},"number":1935,"head":"fd2dd82b0"}}} diff --git a/docs/evidence/cn-phase3-record.md b/docs/evidence/cn-phase3-record.md index d06a2449de..6c233b9b5b 100644 --- a/docs/evidence/cn-phase3-record.md +++ b/docs/evidence/cn-phase3-record.md @@ -11,7 +11,8 @@ Baseline: 5cdac561d498ece25142a8d2e5cf9b06002b6c8a. - [x] Decode PR query rows into named records, not positional tuples. - [x] Confirm real scratch-bus order, verdict, strikes, explicit fire and void through Core tools. - [x] Confirm main/unmerged delivery and unlinked refusal through the CLI. -- [x] Pass the two bounded record/codec sanity checks. Pytest is unavailable. +- [x] Pass the bounded record/codec checks and real missing-checkout board read. +- [ ] Finish the single-worker pytest run; the unrelated post-commit correction failure is assigned for overlap settlement. - [x] Publish draft PR #1935; do not merge or install. - [ ] Automatic firing at the strike limit, with all strike sources in the same write. - [ ] Retain original typed transitions and the state each operation replaces. @@ -64,7 +65,7 @@ acceptance, preserving the concise receipt below. The proof does not own other a ## Actual CLI proof -Original response bodies: `docs/evidence/cn-phase3-record-results.jsonl` (14 actual responses, +Original response bodies: `docs/evidence/cn-phase3-record-results.jsonl` (15 actual responses, normalized only for whitespace). Driver: `tools/proofs/cn_phase3_record.py`. The driver initializes real Core stores and calls `python -m agent_comms.cli tool --from ...`. It uses no fake Comms, fixture events or model replies. Judgment names a real edit from this unit's native session, @@ -101,10 +102,12 @@ Core AST pass parsed 384 modules with zero omissions. ## Local checks and precise blockers -- The requested background `timeout 300 python -X faulthandler -m pytest ...` failed before - collection: the shared interpreter has no pytest. An isolated-venv attempt was refused by the - existing StorageRule; no alternate installation was attempted. A shared test interpreter with - pytest, or a reviewed dev-dependency installation by its owner, is needed for the suite. +- The first interpreter had no pytest. StorageRule refused an isolated venv; no workaround + installation was attempted. pi-cn-phase7 supplied the existing + `/home/ts/code/projects/agent-comms/.venv/bin/python`. Bounded checks use `-o addopts=''` to avoid + the default eight workers and coverage gate, plus owned persistent basetemp/TMPDIR paths. + The first four-module run gave 125 passed and two failures. The GitHub-unread failure is repaired + at its typed remote-error boundary; the post-commit correction failure is described below. - Final checks on source checkpoint ec5f94568: Core/Toad AST passes above, the candidate CLI entrypoint, and both standalone record/codec sanity functions passed under the same bounded/background command form. They check every DeliveryMerge member and named external non-main target decoding; @@ -113,7 +116,48 @@ Core AST pass parsed 384 modules with zero omissions. not automatic firing. The shared TodoStore/Goals write-path work overlaps recent #1878 and is raised with claude-code. Source inspection also finds destructive order/assignment replacement, missing Core recording timestamps and direct fold/correction writes. pi-cn-phase3 retains this implementation. +- A real post-commit error remains: Messaging.send catches TodoConflict only, so an ordinary SQLite + correction error escapes after the message is committed and loses its receipt at the caller. + Reproducer: `test_committed_send_reports_correction_failure_to_tool_and_cli`. Recent #1878 + (toad-theme-2) changes that same method/class. claude-code has the exact overlap and the proposed + CorrectionResult-owned outcome capture; no Messaging code is edited here. +- New #1938 (P710) requires every delivered commit to be on origin/main. That conflicts with the + explicit delivered-but-not-merged workflow in phase 3. claude-code has this product decision; + the two branches must not silently overwrite each other's policy. - pi-cn-phase5 owns SendBackVerdict/Goals.send_back publication, OrderPost rows/insertion and the canonical order channel. Those changes must remain in the same transaction as retained operations; this checkpoint does not edit their classes or TodoStore.rows/apply. Their exact published commit is a dependency for that continuing write-path change. + +## Remote progress with no local checkout + +pi-cn-phase7 supplied a concrete rotation failure: all four +`test_board_self_check_uses_order_progress_and_its_linked_merge` cases reached +`OpenPrProgress.found -> source.readings -> git merge-base` on an absent checkout. +Remote PR evidence and local distance evidence were incorrectly compulsory together. + +The existing ProgressSource now owns remote facts and the absence of local evidence. +`ProgressSource.at` selects LocalProgressSource once at the filesystem boundary when the +checkout directory is available. The local subtype owns commits, Git and distance reads. +Its Git mechanism reuses CheckedCommit.git, rather than another subprocess/error path; +commits no longer suppress arbitrary Git errors. A broken existing checkout remains an +actual GitCommandFailed, not a fabricated no-commit or unread-PR result. + +ProgressPoint owns the log-row format and decode used by both commit and distance reads. +CommittedProgress and PushedProgress consume those named records; the raw sha/time tuples +and repeated split/float conversions are deleted. An order's baseline point still uses its +issue time; a Git commit point uses committer time. + +There are two production ProgressSource consumers: OrderProgress.read uses the availability +factory; TodoStore.board_disagreements intentionally uses the remote-only source to look for +missed merges. No TodoStore or phase-5 publication method was changed. PullRequests raises the +existing WorkBranchError for a forge refusal, which OrderProgress can report as UnreadProgress +without swallowing unrelated Git or malformed-record errors. + +The real proof temporarily added P5's actual immutable reference to this owned draft PR through +`todo.order.pull_request_body`. With `/home/ts/wt/record-worker--P5` absent, the actual CLI +`comms_todo_list` returned OpenPrProgress for PR #1935, with empty local distance readings. +The real TodoStore.board_disagreements read also passed. The temporary reference was removed +in finally while preserving other PR text. No fixture Comms, events or replacement Git runner +were used. Driver/logs remain in the owned proof root and `.proof-board-read.log`; the complete +CLI response is the fifteenth result in the retained receipt. diff --git a/src/agent_comms/todos.py b/src/agent_comms/todos.py index 796ae08eee..b25ba4cac3 100644 --- a/src/agent_comms/todos.py +++ b/src/agent_comms/todos.py @@ -34,7 +34,7 @@ FieldCodec, GitCommitText, MalformedRecord, NullMember, RecordMember, SubclassUnion, WireValue, ) from .channel_targets import CommandTarget -from .goals import CountCheckResult, OrderCheck, ProblemGoalOrder +from .goals import CheckedCommit, CountCheckResult, OrderCheck, ProblemGoalOrder from .recorded_time import EventTime, RecordedEventTime, UnrecordedEventTime from .thread_identity import RecordedThreadIncarnation, ThreadIncarnation from .threads import Thread @@ -42,7 +42,7 @@ from .standing_rules import UndatedStandingRule from .storage_rules import StorageRule from .plan_phases import OrderPhase, UnplannedPhase -from .work_branches import WorkBranches +from .work_branches import WorkBranchError, WorkBranches if TYPE_CHECKING: from .commander.config import Repo @@ -363,7 +363,7 @@ def read(self, repo: str, state: str, order: ProblemOrder) -> tuple[PullRequest, "--jq", '.data | map_values(.nodes)'), capture_output=True, text=True, timeout=60) if listed.returncode: - raise ValueError(listed.stderr.strip() or f"GitHub order lookup in {repo} failed.") + raise WorkBranchError(listed.stderr.strip() or f"GitHub order lookup in {repo} failed.") rows = FieldCodec.decode(dict[str, tuple[ListedPullRequest, ...]], FieldCodec.parse(listed.stdout)) for alias, candidate in aliases.items(): @@ -375,6 +375,19 @@ def _clock(at: float) -> str: return time.strftime("%H:%M", time.localtime(at)) +@dataclass(frozen=True) +class ProgressPoint: + """A revision and its time in the order's progress; Git rows use committer time.""" + sha: str + at: float + format: ClassVar[str] = "%H %ct" + + @classmethod + def listed(cls, line: str) -> ProgressPoint: + sha, at = line.split() + return cls(sha, float(at)) + + @dataclass(frozen=True) class DistanceReading: at: float @@ -511,16 +524,15 @@ def readings(self, source: ProgressSource) -> DistanceReadings: @dataclass(frozen=True) class ProgressSource: - """An order's local checkout capability and independently retained PR references.""" + """The repository's PR facts, extended by local evidence only while a checkout is available.""" todo: Todo - checkout: ProgressCheckout + checkout: Path pulls: PullRequests - @classmethod - def of(cls, todo: Todo, storage: StorageRule, pulls: PullRequests) -> Self: - path = storage.order_checkout(todo.held().owner, todo.id) - checkout = PresentCheckout(path) if path.is_dir() else MissingCheckout(path) - return cls(todo, checkout, pulls) + @staticmethod + def at(todo: Todo, checkout: Path, pulls: PullRequests) -> ProgressSource: + return (LocalProgressSource(todo, checkout, pulls) if checkout.is_dir() + else ProgressSource(todo, checkout, pulls)) @property def since(self) -> float: @@ -531,19 +543,45 @@ def requests(self, state: str) -> list[PullRequest]: return [pr for pr in self.pulls.listed(self.todo.repo, state, self.todo.order) if pr.updated_at >= self.since] - def commits(self, *excluded: str) -> Iterator[tuple[str, float]]: - return self.checkout.commits(self.since, *excluded) + def commits(self, *excluded: str) -> Iterator[ProgressPoint]: + """A remote source makes no claim about unpublished local commits.""" + return iter(()) def readings(self) -> DistanceReadings: - return self.checkout.readings(self) + return DistanceReadings() + + +class LocalProgressSource(ProgressSource): + """An available checkout supplies the local commit and distance evidence.""" + + def commits(self, *excluded: str) -> Iterator[ProgressPoint]: + """The checkout's newest commit since the order that no ``excluded`` ref already holds.""" + after = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(self.since)) + log = self.git("log", "-1", f"--since={after}", f"--format={ProgressPoint.format}", + "HEAD", "--not", *excluded) + return (ProgressPoint.listed(line) for line in log.splitlines()) - def reading(self, checkout: PresentCheckout, base: str, sha: str, at: float) -> DistanceReading: - """Keep the check command with its measured or missing distance.""" + def git(self, *args: str) -> str: + return CheckedCommit.git(str(self.checkout), *args) + + def readings(self) -> DistanceReadings: + """The order's base, then each branch commit since the order; the last two are read.""" + base = self.git("merge-base", "HEAD", "origin/HEAD").strip() + after = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(self.since)) + commits = [ProgressPoint.listed(line) for line in reversed(self.git( + "log", "--first-parent", f"--since={after}", f"--format={ProgressPoint.format}", + "HEAD", "--not", base).splitlines())] + points = [ProgressPoint(base, self.since), *commits][-2:] + return DistanceReadings(tuple( + DistanceReading(point.at, self.distance(base, point.sha)) for point in points)) + + def distance(self, base: str, sha: str) -> int: + """The order's Check run on ``sha`` against its N, plus its numbered edits the diff since + ``base`` has not touched.""" check = self.todo.order.check - touched = checkout.git("diff", "--name-only", base, sha).split() - run = check.at(sha, str(checkout.path)) - return DistanceReading(at, check.result.distance(run).plus( - self.todo.order.untouched(touched)), check.command) + touched = self.git("diff", "--name-only", base, sha).split() + run = check.at(sha, str(self.checkout)) + return check.result.distance(run) + self.todo.order.untouched(touched) @dataclass(frozen=True) @@ -555,12 +593,14 @@ class OrderProgress(DeclaredFamily, affix="Progress"): @classmethod def read(cls, todo: Todo, storage: StorageRule, pulls: PullRequests) -> OrderProgress: """The first found member by its declared precedence; with none found, no commit yet.""" - source = ProgressSource.of(todo, storage, pulls) + owner = todo.holder + assert owner, "Only a held order has progress." + source = ProgressSource.at(todo, storage.order_checkout(owner, todo.id), pulls) members = sorted(cls.members_with(FoundProgress), key=lambda member: member.precedence) try: return next((progress for member in members for progress in member.found(source)), NoCommitProgress()) - except subprocess.TimeoutExpired as unread: + except (WorkBranchError, subprocess.TimeoutExpired) as unread: return UnreadProgress(str(unread)) @property @@ -662,8 +702,8 @@ class CommittedProgress(Moving, FoundProgress): @classmethod def found(cls, source: ProgressSource) -> Iterator[Self]: - return (cls(sha[:9], at=at, readings=source.readings()) - for sha, at in source.commits("--remotes")) + return (cls(commit.sha[:9], at=commit.at, readings=source.readings()) + for commit in source.commits("--remotes")) @property def state(self) -> str: @@ -677,8 +717,8 @@ class PushedProgress(CommittedProgress): @classmethod def found(cls, source: ProgressSource) -> Iterator[Self]: - return (cls(sha[:9], at=at, readings=source.readings()) - for sha, at in source.commits("origin/HEAD")) + return (cls(commit.sha[:9], at=commit.at, readings=source.readings()) + for commit in source.commits("origin/HEAD")) @property def state(self) -> str: diff --git a/tests/test_order_progress.py b/tests/test_order_progress.py index 4637ee1070..a2c463f417 100644 --- a/tests/test_order_progress.py +++ b/tests/test_order_progress.py @@ -8,13 +8,16 @@ import pytest from agent_comms.recorded_time import RecordedEventTime +from agent_comms.goals import GitCommandFailed from agent_comms.tools import CommsOrderDoneTool from agent_comms.todos import (CommittedProgress, DistanceReadings, MergedProgress, NoCommitProgress, InstallDeadline, InstallDue, OpenPrProgress, OperationDelivery, OrderProgress, PlannedInstallDeadline, - PullRequest, PullRequests, PushedProgress, UnreadProgress) + LocalProgressSource, ProgressPoint, ProgressSource, PullRequest, PullRequests, + PushedProgress, UnreadProgress) from agent_comms.messages import InfoKind from agent_comms.storage_rules import StorageRule +from agent_comms.work_branches import WorkBranchError from test_commander_problem_orders import create, working @@ -163,11 +166,20 @@ def test_a_merge_not_yet_installed_waits_only_on_install(checkout): def test_unread_github_is_explicit(checkout): pulls = Listed() - pulls.states = ValueError('gh: not logged in') + pulls.states = WorkBranchError('gh: not logged in') progress = read(checkout, pulls) assert progress == UnreadProgress('gh: not logged in') +def test_an_existing_broken_checkout_is_not_silently_treated_as_missing(checkout, tmp_path): + bad = tmp_path / 'not-a-repository' + bad.mkdir() + source = ProgressSource.at(checkout[0], bad, Listed()) + assert isinstance(source, LocalProgressSource) + with pytest.raises(GitCommandFailed): + source.readings() + + def test_board_and_overdue_name_a_merge_waiting_on_install(commander, commander_script, monkeypatch, tmp_path): script, _ = commander_script # worker's P1 checkout, on the branch its merged PR came from. @@ -201,7 +213,8 @@ class HeldSource: since = 100.0 def commits(self, *excluded): - commits = {('--remotes',): ('a' * 40, 1000.0), ('origin/HEAD',): ('e' * 40, 1000.0)} + commits = {('--remotes',): ProgressPoint('a' * 40, 1000.0), + ('origin/HEAD',): ProgressPoint('e' * 40, 1000.0)} yield commits[excluded] def readings(self): diff --git a/tools/proofs/cn_phase3_record.py b/tools/proofs/cn_phase3_record.py index d8577e3503..e1d2ba9ab1 100644 --- a/tools/proofs/cn_phase3_record.py +++ b/tools/proofs/cn_phase3_record.py @@ -44,7 +44,7 @@ def cli(caller, tool): raise RuntimeError(result.stderr or result.stdout) with RECEIPT.open("a") as output: output.write(result.stdout) - return json.loads(result.stdout) + return result.stdout def setup(): From 1f5109402416d236dbfee1c68f6c7c1d7f8425f7 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 14:41:24 -0400 Subject: [PATCH 06/22] Scope local progress to its checkout and bounded reading window --- docs/evidence/cn-phase3-record.md | 21 ++++++++++++++++----- src/agent_comms/todos.py | 10 ++++++---- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/docs/evidence/cn-phase3-record.md b/docs/evidence/cn-phase3-record.md index 6c233b9b5b..bdab4af770 100644 --- a/docs/evidence/cn-phase3-record.md +++ b/docs/evidence/cn-phase3-record.md @@ -12,7 +12,8 @@ Baseline: 5cdac561d498ece25142a8d2e5cf9b06002b6c8a. - [x] Confirm real scratch-bus order, verdict, strikes, explicit fire and void through Core tools. - [x] Confirm main/unmerged delivery and unlinked refusal through the CLI. - [x] Pass the bounded record/codec checks and real missing-checkout board read. -- [ ] Finish the single-worker pytest run; the unrelated post-commit correction failure is assigned for overlap settlement. +- [x] Pass 38 owned checks in one bounded worker, including the four missing-checkout cases. +- [ ] The broader post-commit correction failure remains with #1878; review comment posted as directed. - [x] Publish draft PR #1935; do not merge or install. - [ ] Automatic firing at the strike limit, with all strike sources in the same write. - [ ] Retain original typed transitions and the state each operation replaces. @@ -121,9 +122,10 @@ Core AST pass parsed 384 modules with zero omissions. Reproducer: `test_committed_send_reports_correction_failure_to_tool_and_cli`. Recent #1878 (toad-theme-2) changes that same method/class. claude-code has the exact overlap and the proposed CorrectionResult-owned outcome capture; no Messaging code is edited here. -- New #1938 (P710) requires every delivered commit to be on origin/main. That conflicts with the - explicit delivered-but-not-merged workflow in phase 3. claude-code has this product decision; - the two branches must not silently overwrite each other's policy. +- claude-code resolved the #1938 policy overlap: delivered, merged, installed and working remain + separate states; the desktop moves main ancestry onto the merged state. CommitDelivery here is + unchanged. The #1878 review comment is + https://github.com/OpenHCSDev/agent-comms/pull/1878#issuecomment-6274426175. - pi-cn-phase5 owns SendBackVerdict/Goals.send_back publication, OrderPost rows/insertion and the canonical order channel. Those changes must remain in the same transaction as retained operations; this checkpoint does not edit their classes or TodoStore.rows/apply. Their exact published commit @@ -141,7 +143,10 @@ The existing ProgressSource now owns remote facts and the absence of local evide checkout directory is available. The local subtype owns commits, Git and distance reads. Its Git mechanism reuses CheckedCommit.git, rather than another subprocess/error path; commits no longer suppress arbitrary Git errors. A broken existing checkout remains an -actual GitCommandFailed, not a fabricated no-commit or unread-PR result. +actual GitCommandFailed, not a fabricated no-commit or unread-PR result. Local Git is bound to +that checkout's own `.git`, so an empty directory cannot silently borrow a parent repository. +The real development worktree's gitfile was also exercised. DistanceReadings owns its two-point +window, which now bounds the Git log request instead of reading all commits and discarding them. ProgressPoint owns the log-row format and decode used by both commit and distance reads. CommittedProgress and PushedProgress consume those named records; the raw sha/time tuples @@ -161,3 +166,9 @@ The real TodoStore.board_disagreements read also passed. The temporary reference in finally while preserving other PR text. No fixture Comms, events or replacement Git runner were used. Driver/logs remain in the owned proof root and `.proof-board-read.log`; the complete CLI response is the fifteenth result in the retained receipt. + +Final owned check: `test_delivery_merge.py`, `test_order_progress.py` and the four standing-rule +missing-checkout cases: **38 passed**, single worker, 32.57 seconds, `OWNED_PYTEST_EXIT=0`. +The shared interpreter warns that pytest-timeout is absent; the outer timeout remains 300 seconds. +A broader intermediate run gave 126 passed and two failures: the remaining #1878 correction +failure, and the empty-directory/parent-repository defect that this final owned run confirms fixed. diff --git a/src/agent_comms/todos.py b/src/agent_comms/todos.py index b25ba4cac3..18110b0692 100644 --- a/src/agent_comms/todos.py +++ b/src/agent_comms/todos.py @@ -400,6 +400,7 @@ class DistanceReadings: """The order's last two distance readings, oldest first: its base at the order, then its newest commit. Fewer than two read, or a last reading at done, project nothing.""" readings: tuple[DistanceReading, ...] = () + window: ClassVar[int] = 2 @property def trend(self) -> DistanceTrend: @@ -562,16 +563,17 @@ def commits(self, *excluded: str) -> Iterator[ProgressPoint]: return (ProgressPoint.listed(line) for line in log.splitlines()) def git(self, *args: str) -> str: - return CheckedCommit.git(str(self.checkout), *args) + return CheckedCommit.git( + str(self.checkout), f"--git-dir={(self.checkout / '.git').absolute()}", *args) def readings(self) -> DistanceReadings: """The order's base, then each branch commit since the order; the last two are read.""" base = self.git("merge-base", "HEAD", "origin/HEAD").strip() after = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(self.since)) commits = [ProgressPoint.listed(line) for line in reversed(self.git( - "log", "--first-parent", f"--since={after}", f"--format={ProgressPoint.format}", - "HEAD", "--not", base).splitlines())] - points = [ProgressPoint(base, self.since), *commits][-2:] + "log", f"--max-count={DistanceReadings.window}", "--first-parent", f"--since={after}", + f"--format={ProgressPoint.format}", "HEAD", "--not", base).splitlines())] + points = [ProgressPoint(base, self.since), *commits][-DistanceReadings.window:] return DistanceReadings(tuple( DistanceReading(point.at, self.distance(base, point.sha)) for point in points)) From 6d530abdadd03db330301fdb37cc217339971585 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 14:55:29 -0400 Subject: [PATCH 07/22] Read recorded check evidence without executing from progress --- src/agent_comms/goals.py | 13 +++--- src/agent_comms/todos.py | 14 +++--- tests/test_order_progress.py | 19 +++++++- tools/proofs/cn_progress_read_only.py | 65 +++++++++++++++++++++++++++ 4 files changed, 98 insertions(+), 13 deletions(-) create mode 100644 tools/proofs/cn_progress_read_only.py diff --git a/src/agent_comms/goals.py b/src/agent_comms/goals.py index 85653a9dcc..b2080b6ac8 100644 --- a/src/agent_comms/goals.py +++ b/src/agent_comms/goals.py @@ -318,13 +318,16 @@ async def run(self) -> ChildResult: return await BoundedRun.run(("bash", "-c", self.command), cwd=self.directory, timeout=self.timeout) + def recorded(self, sha: str, repository: str) -> tuple[ChildResult, ...]: + """Read an explicit run's receipt; absent evidence never starts the check.""" + return tuple(checked.result for checked in CheckedCommit.stored(repository, sha) + if checked.command == self.command)[:1] + def at(self, revision: str, repository: str) -> ChildResult: - """The check on ``revision``: the result a git note on the commit records, or one run in - a throwaway detached checkout and recorded there for every later reader.""" + """The check on ``revision``: a recorded result, or an explicit run in a detached checkout.""" sha = CheckedCommit.git(repository, "rev-parse", f"{revision}^{{commit}}").strip() - for checked in CheckedCommit.stored(repository, sha): - if checked.command == self.command: - return checked.result + for result in self.recorded(sha, repository): + return result with tempfile.TemporaryDirectory(prefix="order-check-") as scratch: tree = str(Path(scratch) / "tree") CheckedCommit.git(repository, "worktree", "add", "--detach", tree, sha) diff --git a/src/agent_comms/todos.py b/src/agent_comms/todos.py index 18110b0692..14e1c9ad30 100644 --- a/src/agent_comms/todos.py +++ b/src/agent_comms/todos.py @@ -575,15 +575,15 @@ def readings(self) -> DistanceReadings: f"--format={ProgressPoint.format}", "HEAD", "--not", base).splitlines())] points = [ProgressPoint(base, self.since), *commits][-DistanceReadings.window:] return DistanceReadings(tuple( - DistanceReading(point.at, self.distance(base, point.sha)) for point in points)) + reading for point in points for reading in self.recorded_distances(base, point))) - def distance(self, base: str, sha: str) -> int: - """The order's Check run on ``sha`` against its N, plus its numbered edits the diff since - ``base`` has not touched.""" + def recorded_distances(self, base: str, point: ProgressPoint) -> Iterator[DistanceReading]: + """Only explicit check receipts supply a distance; no receipt means unknown.""" check = self.todo.order.check - touched = self.git("diff", "--name-only", base, sha).split() - run = check.at(sha, str(self.checkout)) - return check.result.distance(run) + self.todo.order.untouched(touched) + for run in check.recorded(point.sha, str(self.checkout)): + touched = self.git("diff", "--name-only", base, point.sha).split() + yield DistanceReading(point.at, check.result.distance(run) + + self.todo.order.untouched(touched)) @dataclass(frozen=True) diff --git a/tests/test_order_progress.py b/tests/test_order_progress.py index a2c463f417..c2d7ccc2e1 100644 --- a/tests/test_order_progress.py +++ b/tests/test_order_progress.py @@ -1,6 +1,7 @@ """An order's progress is read from its unit's checkout and PRs beside its deadline; merged work draws no strike.""" import shutil import subprocess +import shlex import time from dataclasses import replace @@ -8,7 +9,7 @@ import pytest from agent_comms.recorded_time import RecordedEventTime -from agent_comms.goals import GitCommandFailed +from agent_comms.goals import CountCheckResult, GitCommandFailed, OrderCheck from agent_comms.tools import CommsOrderDoneTool from agent_comms.todos import (CommittedProgress, DistanceReadings, MergedProgress, NoCommitProgress, InstallDeadline, InstallDue, OpenPrProgress, OperationDelivery, OrderProgress, PlannedInstallDeadline, @@ -171,6 +172,22 @@ def test_unread_github_is_explicit(checkout): assert progress == UnreadProgress('gh: not logged in') +def test_progress_reads_only_explicit_check_receipts(checkout, tmp_path): + todo, unit = checkout + marker = tmp_path / 'explicit-check-count' + check = OrderCheck(str(unit), f'printf x >> {shlex.quote(str(marker))}; printf 0', + CountCheckResult(0)) + todo = replace(todo, order=replace(todo.order, check=check)) + source = ProgressSource.at(todo, unit, Listed()) + assert source.readings() == DistanceReadings() + assert not marker.exists() + check.at(git(unit, 'rev-parse', 'HEAD').strip(), str(unit)) + assert marker.read_text() == 'x' + assert len(source.readings().readings) == 1 + assert source.readings().readings[0].distance == todo.order.untouched(()) + assert marker.read_text() == 'x' + + def test_an_existing_broken_checkout_is_not_silently_treated_as_missing(checkout, tmp_path): bad = tmp_path / 'not-a-repository' bad.mkdir() diff --git a/tools/proofs/cn_progress_read_only.py b/tools/proofs/cn_progress_read_only.py new file mode 100644 index 0000000000..ccc2c78d98 --- /dev/null +++ b/tools/proofs/cn_progress_read_only.py @@ -0,0 +1,65 @@ +"""Real Core board reads consume explicit check receipts, never run the check.""" +import argparse +import shlex +import subprocess + +from agent_comms.comms import Comms +from agent_comms.field_codec import FieldCodec +from agent_comms.goals import CheckedCommit, CountCheckResult, OrderCheck +from agent_comms.todos import CorrectCheckTodo, PushedProgress +from agent_comms.tools import CommsTodoListTool, CommsTodoTool, TodoList +from tools.proofs.cn_phase3_record import CHECKOUT, ROOT, cli, create_order + +MARKER = ROOT / 'explicit-check-count' + + +def prepare(): + create_order('P6') + comms = Comms(ROOT) + todo = comms.goals.todos.get('P6') + check = OrderCheck(str(CHECKOUT), f'printf x >> {shlex.quote(str(MARKER))}; printf 0', + CountCheckResult(0)) + cli('record-lead', CommsTodoTool(id='P6', operation=CorrectCheckTodo( + todo.revision, comms.registry.require('record-lead').incarnation, + todo.order.check, check, 'Prove that reading the board cannot execute the check'))) + + +def board(): + listed = FieldCodec.decode(TodoList, FieldCodec.parse(cli('record-lead', CommsTodoListTool()))) + progress = listed.progress['P6'] + assert isinstance(progress, PushedProgress), progress + return progress.distance_readings() + + +def verify(): + comms = Comms(ROOT) + todo = comms.goals.todos.get('P6') + checkout = comms.registry.storage_rule.order_checkout(todo.held().owner, todo.id) + if checkout.exists() or MARKER.exists(): + raise RuntimeError('Proof resources already exist; inspect before another attempt') + subprocess.run(['git', 'worktree', 'add', '--detach', str(checkout), 'HEAD'], + cwd=CHECKOUT, check=True, timeout=60) + try: + assert not board().readings + assert not MARKER.exists() + sha = CheckedCommit.git(str(checkout), 'rev-parse', 'HEAD').strip() + assert not todo.order.check.recorded(sha, str(checkout)) + todo.order.check.at(sha, str(checkout)) # Deliberately explicit, never a board read. + assert MARKER.read_text() == 'x' + assert len(board().readings) == 1 + assert MARKER.read_text() == 'x' + for receipt in CheckedCommit.stored(str(checkout), sha): + if receipt.command == todo.order.check.command: + print(FieldCodec.encode(receipt), flush=True) + print('Actual CLI: unknown before explicit run, recorded afterward; exactly one execution.', + flush=True) + finally: + subprocess.run(['git', 'worktree', 'remove', str(checkout)], cwd=CHECKOUT, + check=True, timeout=60) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('step', choices=('prepare', 'verify')) + args = parser.parse_args() + prepare() if args.step == 'prepare' else verify() From 877f109566989c4fc3a7c9c1c7f38172a5c4fe2c Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 15:21:45 -0400 Subject: [PATCH 08/22] Declare external records have no native entry projection --- src/agent_comms/external_transcript.py | 11 ++++++++--- src/agent_comms/native_transcript.py | 4 ++-- src/agent_comms/transcript_sources.py | 8 ++++++++ tests/test_check_root.py | 20 ++++++++++++++++++++ 4 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/agent_comms/external_transcript.py b/src/agent_comms/external_transcript.py index d2eb3a25b0..b4819fcbe8 100644 --- a/src/agent_comms/external_transcript.py +++ b/src/agent_comms/external_transcript.py @@ -24,7 +24,7 @@ from .native_transcript import RecordsCut from .thread_identity import ThreadIncarnation from .transcript_events import TranscriptEvent, TurnDigest, ToolStartTranscript -from .transcript_sources import NoDraft, TranscriptDraft, TranscriptSource +from .transcript_sources import NoDraft, TranscriptDraft, TranscriptRecord, TranscriptSource from .typed_table import Column, ForeignKey, Index, RealStorage, StoreWriter, TypedRow, TypedStore, TypedTable from .wire_watch import WatchSource, WireWatch @@ -274,8 +274,8 @@ def end_turn(self, transcript: StoreWriter) -> None: @dataclass(frozen=True) -class ExternalRecord: - """An item at its page position, in the shape the page merge consumes.""" +class ExternalRecord(TranscriptRecord): + """An item at its page position, with events but no decoded native entry.""" seq: int events: tuple[TranscriptEvent, ...] @@ -482,6 +482,11 @@ def end_turn(self) -> None: draft = records.current_draft(self.root, self.incarnation) draft.end_turn(self) + def turns(self, bound: TurnBound) -> tuple[TurnDigest, ...]: + """The stored CLI turn IDs, not native input entries, own these boundaries.""" + with self.reading() as reader: + return reader.turns(bound) + @contextmanager def reading(self): """Keep item and draft queries within the same SQL snapshot.""" diff --git a/src/agent_comms/native_transcript.py b/src/agent_comms/native_transcript.py index b9567324fd..0b0730fbb5 100644 --- a/src/agent_comms/native_transcript.py +++ b/src/agent_comms/native_transcript.py @@ -20,7 +20,7 @@ from .native_entries import NativeEntry, RecordedEntry, SessionEntry, TranscriptProjection from .native_runtime_input import NativeRuntimeInput, PublishedReplyRevision from .private_path import FileRevision -from .transcript_sources import TranscriptSource +from .transcript_sources import TranscriptRecord, TranscriptSource def reverse_records( @@ -130,7 +130,7 @@ def publication_revision( @dataclass(frozen=True) -class NativeRecord: +class NativeRecord(TranscriptRecord): start: int end: int entry: RecordedEntry | None diff --git a/src/agent_comms/transcript_sources.py b/src/agent_comms/transcript_sources.py index e6a7600250..703ef265c9 100644 --- a/src/agent_comms/transcript_sources.py +++ b/src/agent_comms/transcript_sources.py @@ -296,6 +296,14 @@ class NoDraft(TranscriptDraft): pass +class TranscriptRecord: + """A source position; only native records can project a decoded native entry.""" + + def project(self, projection): + """External records have events already, but no native entry for this callback.""" + return () + + class TranscriptSource: """Recorded items alone carry no assigned-wire or publication authority. diff --git a/tests/test_check_root.py b/tests/test_check_root.py index 9ed9137412..bcf7860a6b 100644 --- a/tests/test_check_root.py +++ b/tests/test_check_root.py @@ -15,6 +15,8 @@ from agent_comms.input_disposition import InputDispositions from agent_comms.native_entries import NativeEntry from agent_comms.thread_status import RunningThreadStatus, StoppedThreadStatus +from agent_comms.transcript_events import AssistantTranscript +from agent_comms.turn_digests import LastTurns, TurnDigestRead @pytest.fixture @@ -95,6 +97,24 @@ def test_new_uncertain_input_is_kept_instead_of_erased(source, tmp_path): shutil.rmtree(archive) +def test_external_records_open_without_borrowing_a_native_entry(source, tmp_path): + transcript = source.transcripts.external('B') + transcript.finish(turn_id='external-turn', item_id='reply', + events=(AssistantTranscript('external reply', timestamp=1.0, position='reply'),)) + with transcript.reading() as reader: + record, = reader.forward(0, reader.cut().offset) + assert record.project(lambda native: pytest.fail('external record is not native')) == () + turns = TurnDigestRead(source.root, source.registry.require('B'), LastTurns()).native().turns + assert [turn.turn_id for turn in turns] == ['external-turn'] + assert [event.text for event in turns[0].events] == ['external reply'] + root = tmp_path / 'external-window' + with CheckRoot(source, ('B',), root).open() as copied: + assert copied.registry.require('B').name == 'B' + assert Path(copied.registry.require('B').session_file).is_file() + assert not root.exists() + assert transcript.turns(LastTurns()) == turns + + def test_native_window_uses_existing_import_and_route_readers(source, tmp_path, monkeypatch): session = create_fresh_private_session(tmp_path / "original-session", worktree=tmp_path) stamp = "2026-10-19T14:00:00Z" From 6906c83cd9aa1fc7e9b4f26ee757eb3c67a75b52 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 16:25:50 -0400 Subject: [PATCH 09/22] Own merged PR facts and typed record reads for P254 --- docs/evidence/cn-phase3-record-results.jsonl | 4 + src/agent_comms/external_transcript.py | 2 +- src/agent_comms/goals.py | 8 +- src/agent_comms/todos.py | 112 +++++++++++++------ src/agent_comms/transcript_sources.py | 4 +- tests/test_delivery_merge.py | 18 ++- tests/test_order_progress.py | 28 ++--- tests/test_standing_rules.py | 9 +- tools/proofs/cn_phase3_record.py | 34 +++--- tools/proofs/cn_progress_read_only.py | 11 +- 10 files changed, 151 insertions(+), 79 deletions(-) diff --git a/docs/evidence/cn-phase3-record-results.jsonl b/docs/evidence/cn-phase3-record-results.jsonl index 6ad87cc68e..5bcf1fcd3d 100644 --- a/docs/evidence/cn-phase3-record-results.jsonl +++ b/docs/evidence/cn-phase3-record-results.jsonl @@ -13,3 +13,7 @@ {"goal":{"text":"Complete the assigned problems.","id":"record-work","published_seq":0,"progress":"","revision":6,"reported_turn":null,"mention_source":{"goal_id":"record-work","text_revision":6,"text_digest":"816861a4f5b6cbf17b6bf4e4a3d9f87e1b8bdcd32bcf98c2b447f2a3a9be80b7","owner_name":"record-worker","owner_created_at":1792432385.2149153,"bindings":[]},"state":{"kind":"active"},"order":{"kind":"problem"}},"todo":{"row":"todo","id":"P5","repo":"openhcsdev/agent-comms","text":"Show the real verdict and strike records without a second source","creator":"record-lead","creator_created":1792432385.1369143,"state":"open","revision":1,"goal":{"owner":"record-lead","owner_created":1792432385.1369143,"goal_id":"record-request"},"assignment":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"order":{"kind":"pending","id":"record-order-P5","owner":"/home/ts/wt/pi-cn-phase3--P1/src/agent_comms/todos.py:69","check":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf 1","result":{"kind":"count","expected":1}},"edits":["Decode pull request target branches through the existing record owner"],"banned":["No second order store"],"report":"Report the actual Core tool response","at":{"kind":"recorded","at":1792433505.0452404},"first_call_deadline":{"kind":"recorded","at":1792433625.0452404},"first_call":{"kind":"awaiting"},"run":{"kind":"mechanical"}},"deadline":{"kind":"recorded","at":1792437105.0452404},"strikes":[],"voided":[],"released":[],"verdicts":[],"corrections":[],"messages":[]}} {"todo":{"row":"todo","id":"P5","repo":"openhcsdev/agent-comms","text":"Show the real verdict and strike records without a second source","creator":"record-lead","creator_created":1792432385.1369143,"state":"open","revision":2,"goal":{"owner":"record-lead","owner_created":1792432385.1369143,"goal_id":"record-request"},"assignment":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"order":{"kind":"delivered","id":"record-order-P5","owner":"/home/ts/wt/pi-cn-phase3--P1/src/agent_comms/todos.py:69","check":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf 1","result":{"kind":"count","expected":1}},"edits":["Decode pull request target branches through the existing record owner"],"banned":["No second order store"],"report":"Report the actual Core tool response","at":{"kind":"recorded","at":1792433505.0452404},"first_call_deadline":{"kind":"recorded","at":1792433625.0452404},"first_call":{"kind":"awaiting"},"run":{"kind":"mechanical"},"delivery":{"kind":"checked_commit","at":{"kind":"recorded","at":1792433301.0},"commit":"256efcc8b6d632e224a3a4ffe7d5d1b49c4ae7e5","delivered_commit":"ec5f94568","merge":{"kind":"unmerged","branches":["feat/cn-phase3-record"]}}},"deadline":{"kind":"recorded","at":1792437105.0452404},"strikes":[],"voided":[],"released":[],"verdicts":[],"corrections":[],"messages":[]},"strike_count":0} {"todos":[{"row":"todo","id":"P1","repo":"openhcsdev/agent-comms","text":"Show the real verdict and strike records without a second source","creator":"record-lead","creator_created":1792432385.1369143,"state":"open","revision":6,"goal":{"owner":"record-lead","owner_created":1792432385.1369143,"goal_id":"record-request"},"assignment":null,"order":{"kind":"pending","id":"record-order-P1","owner":"/home/ts/wt/pi-cn-phase3--P1/src/agent_comms/todos.py:69","check":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf 1","result":{"kind":"count","expected":1}},"edits":["Decode pull request target branches through the existing record owner"],"banned":["No second order store"],"report":"Report the actual Core tool response","at":{"kind":"recorded","at":1792432466.497828},"first_call_deadline":{"kind":"recorded","at":1792432586.497828},"first_call":{"kind":"awaiting"},"run":{"kind":"mechanical"}},"deadline":{"kind":"recorded","at":1792436066.497828},"strikes":[{"kind":"strike_todo","expected_revision":2,"actor":{"name":"record-lead","created_at":1792432385.1369143},"previous":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"reason":"The assigned change left the target branch out of the saved order record","at":{"kind":"recorded","at":1792432640.0348916}},{"kind":"strike_todo","expected_revision":3,"actor":{"name":"record-lead","created_at":1792432385.1369143},"previous":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"reason":"The second review found the target branch still missing from the saved record","at":{"kind":"recorded","at":1792432652.476716}}],"voided":[{"kind":"void_strike_todo","expected_revision":5,"actor":{"name":"record-lead","created_at":1792432385.1369143},"strike":{"kind":"strike_todo","expected_revision":3,"actor":{"name":"record-lead","created_at":1792432385.1369143},"previous":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"reason":"The second review found the target branch still missing from the saved record","at":{"kind":"recorded","at":1792432652.476716}},"reason":"The second strike repeated the first finding rather than describing another failed change","at":{"kind":"recorded","at":1792432680.216849}}],"released":[{"kind":"release_todo","expected_revision":4,"actor":{"name":"record-lead","created_at":1792432385.1369143},"previous":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"reason":"The unit left the required target branch absent after both recorded reviews","at":{"kind":"recorded","at":1792432675.6006632}}],"verdicts":[{"kind":"accepted_verdict","expected_revision":1,"actor":{"name":"record-lead","created_at":1792432385.1369143},"previous":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"tool_call_id":"call_a3e3f7350f4e44faa55a72bebf904fa6|fc_01744e18603d1183016ad659dcd6c081a3a769ec0e1880e266","at":{"kind":"recorded","at":1792432628.8483949},"quote":"Decode pull request target branches"}],"corrections":[],"messages":[]},{"row":"todo","id":"P2","repo":"openhcsdev/agent-comms","text":"Show the real verdict and strike records without a second source","creator":"record-lead","creator_created":1792432385.1369143,"state":"open","revision":2,"goal":{"owner":"record-lead","owner_created":1792432385.1369143,"goal_id":"record-request"},"assignment":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"order":{"kind":"delivered","id":"record-order-P2","owner":"/home/ts/wt/pi-cn-phase3--P1/src/agent_comms/todos.py:69","check":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf 1","result":{"kind":"count","expected":1}},"edits":["Decode pull request target branches through the existing record owner"],"banned":["No second order store"],"report":"Report the actual Core tool response","at":{"kind":"recorded","at":1792432735.6061842},"first_call_deadline":{"kind":"recorded","at":1792432855.6061842},"first_call":{"kind":"awaiting"},"run":{"kind":"mechanical"},"delivery":{"kind":"checked_commit","at":{"kind":"recorded","at":1792431763.0},"commit":"e0ca0150042587e7494184b5ab2f6701f3487076","delivered_commit":"5cdac561d498ece25142a8d2e5cf9b06002b6c8a","merge":{"kind":"main"}}},"deadline":{"kind":"recorded","at":1792436335.6061842},"strikes":[],"voided":[],"released":[],"verdicts":[],"corrections":[],"messages":[]},{"row":"todo","id":"P3","repo":"openhcsdev/agent-comms","text":"Show the real verdict and strike records without a second source","creator":"record-lead","creator_created":1792432385.1369143,"state":"open","revision":2,"goal":{"owner":"record-lead","owner_created":1792432385.1369143,"goal_id":"record-request"},"assignment":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"order":{"kind":"delivered","id":"record-order-P3","owner":"/home/ts/wt/pi-cn-phase3--P1/src/agent_comms/todos.py:69","check":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf 1","result":{"kind":"count","expected":1}},"edits":["Decode pull request target branches through the existing record owner"],"banned":["No second order store"],"report":"Report the actual Core tool response","at":{"kind":"recorded","at":1792432771.201293},"first_call_deadline":{"kind":"recorded","at":1792432891.201293},"first_call":{"kind":"awaiting"},"run":{"kind":"mechanical"},"delivery":{"kind":"checked_commit","at":{"kind":"recorded","at":1792432302.0},"commit":"e0ca0150042587e7494184b5ab2f6701f3487076","delivered_commit":"e0ca01500","merge":{"kind":"unmerged","branches":["feat/cn-phase3-record"]}}},"deadline":{"kind":"recorded","at":1792436371.201293},"strikes":[],"voided":[],"released":[],"verdicts":[],"corrections":[],"messages":[]},{"row":"todo","id":"P4","repo":"openhcsdev/agent-comms","text":"Show the real verdict and strike records without a second source","creator":"record-lead","creator_created":1792432385.1369143,"state":"open","revision":1,"goal":{"owner":"record-lead","owner_created":1792432385.1369143,"goal_id":"record-request"},"assignment":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"order":{"kind":"pending","id":"record-order-P4","owner":"/home/ts/wt/pi-cn-phase3--P1/src/agent_comms/todos.py:69","check":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf 1","result":{"kind":"count","expected":1}},"edits":["Decode pull request target branches through the existing record owner"],"banned":["No second order store"],"report":"Report the actual Core tool response","at":{"kind":"recorded","at":1792433118.0605557},"first_call_deadline":{"kind":"recorded","at":1792433238.0605557},"first_call":{"kind":"awaiting"},"run":{"kind":"mechanical"}},"deadline":{"kind":"recorded","at":1792436718.0605557},"strikes":[],"voided":[],"released":[],"verdicts":[],"corrections":[],"messages":[]},{"row":"todo","id":"P5","repo":"openhcsdev/agent-comms","text":"Show the real verdict and strike records without a second source","creator":"record-lead","creator_created":1792432385.1369143,"state":"open","revision":2,"goal":{"owner":"record-lead","owner_created":1792432385.1369143,"goal_id":"record-request"},"assignment":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"order":{"kind":"delivered","id":"record-order-P5","owner":"/home/ts/wt/pi-cn-phase3--P1/src/agent_comms/todos.py:69","check":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf 1","result":{"kind":"count","expected":1}},"edits":["Decode pull request target branches through the existing record owner"],"banned":["No second order store"],"report":"Report the actual Core tool response","at":{"kind":"recorded","at":1792433505.0452404},"first_call_deadline":{"kind":"recorded","at":1792433625.0452404},"first_call":{"kind":"awaiting"},"run":{"kind":"mechanical"},"delivery":{"kind":"checked_commit","at":{"kind":"recorded","at":1792433301.0},"commit":"256efcc8b6d632e224a3a4ffe7d5d1b49c4ae7e5","delivered_commit":"ec5f94568","merge":{"kind":"unmerged","branches":["feat/cn-phase3-record"]}}},"deadline":{"kind":"recorded","at":1792437105.0452404},"strikes":[],"voided":[],"released":[],"verdicts":[],"corrections":[],"messages":[]}],"progress":{"P2":{"kind":"no_commit"},"P3":{"kind":"no_commit"},"P4":{"kind":"no_commit"},"P5":{"kind":"open_pr","at":1792434165,"readings":{"readings":[]},"number":1935,"head":"fd2dd82b0"}}} +{"goal":{"text":"Complete the assigned problems.","id":"record-work","published_seq":0,"progress":"","revision":7,"reported_turn":null,"mention_source":{"goal_id":"record-work","text_revision":7,"text_digest":"816861a4f5b6cbf17b6bf4e4a3d9f87e1b8bdcd32bcf98c2b447f2a3a9be80b7","owner_name":"record-worker","owner_created_at":1792432385.2149153,"bindings":[]},"state":{"kind":"active"},"order":{"kind":"problem"}},"todo":{"row":"todo","id":"P6","repo":"openhcsdev/agent-comms","text":"Show the real verdict and strike records without a second source","creator":"record-lead","creator_created":1792432385.1369143,"state":"open","revision":1,"goal":{"owner":"record-lead","owner_created":1792432385.1369143,"goal_id":"record-request"},"assignment":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"order":{"kind":"pending","id":"record-order-P6","owner":"/home/ts/wt/pi-cn-phase3--P1/src/agent_comms/todos.py:69","check":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf 1","result":{"kind":"count","expected":1}},"edits":["Decode pull request target branches through the existing record owner"],"banned":["No second order store"],"report":"Report the actual Core tool response","at":{"kind":"recorded","at":1792436082.9144495},"first_call_deadline":{"kind":"recorded","at":1792436202.9144495},"first_call":{"kind":"awaiting"},"run":{"kind":"mechanical"}},"deadline":{"kind":"recorded","at":1792439682.9144495},"strikes":[],"voided":[],"released":[],"verdicts":[],"corrections":[],"messages":[]}} +{"todo":{"row":"todo","id":"P6","repo":"openhcsdev/agent-comms","text":"Show the real verdict and strike records without a second source","creator":"record-lead","creator_created":1792432385.1369143,"state":"open","revision":2,"goal":{"owner":"record-lead","owner_created":1792432385.1369143,"goal_id":"record-request"},"assignment":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"order":{"kind":"pending","id":"record-order-P6","owner":"/home/ts/wt/pi-cn-phase3--P1/src/agent_comms/todos.py:69","check":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf x >> /home/ts/wt/pi-cn-phase3--P1/.proof-record-plane/explicit-check-count; printf 0","result":{"kind":"count","expected":0}},"edits":["Decode pull request target branches through the existing record owner"],"banned":["No second order store"],"report":"Report the actual Core tool response","at":{"kind":"recorded","at":1792436082.9144495},"first_call_deadline":{"kind":"recorded","at":1792436202.9144495},"first_call":{"kind":"awaiting"},"check_corrections":[{"kind":"correct_check_todo","expected_revision":1,"actor":{"name":"record-lead","created_at":1792432385.1369143},"previous":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf 1","result":{"kind":"count","expected":1}},"check":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf x >> /home/ts/wt/pi-cn-phase3--P1/.proof-record-plane/explicit-check-count; printf 0","result":{"kind":"count","expected":0}},"reason":"Prove that reading the board cannot execute the check","at":{"kind":"recorded","at":1792436090.2217019}}],"run":{"kind":"mechanical"}},"deadline":{"kind":"recorded","at":1792439682.9144495},"strikes":[],"voided":[],"released":[],"verdicts":[],"corrections":[],"messages":[]},"strike_count":0} +{"todos":[{"row":"todo","id":"P1","repo":"openhcsdev/agent-comms","text":"Show the real verdict and strike records without a second source","creator":"record-lead","creator_created":1792432385.1369143,"state":"open","revision":6,"goal":{"owner":"record-lead","owner_created":1792432385.1369143,"goal_id":"record-request"},"assignment":null,"order":{"kind":"pending","id":"record-order-P1","owner":"/home/ts/wt/pi-cn-phase3--P1/src/agent_comms/todos.py:69","check":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf 1","result":{"kind":"count","expected":1}},"edits":["Decode pull request target branches through the existing record owner"],"banned":["No second order store"],"report":"Report the actual Core tool response","at":{"kind":"recorded","at":1792432466.497828},"first_call_deadline":{"kind":"recorded","at":1792432586.497828},"first_call":{"kind":"awaiting"},"run":{"kind":"mechanical"}},"deadline":{"kind":"recorded","at":1792436066.497828},"strikes":[{"kind":"strike_todo","expected_revision":2,"actor":{"name":"record-lead","created_at":1792432385.1369143},"previous":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"reason":"The assigned change left the target branch out of the saved order record","at":{"kind":"recorded","at":1792432640.0348916}},{"kind":"strike_todo","expected_revision":3,"actor":{"name":"record-lead","created_at":1792432385.1369143},"previous":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"reason":"The second review found the target branch still missing from the saved record","at":{"kind":"recorded","at":1792432652.476716}}],"voided":[{"kind":"void_strike_todo","expected_revision":5,"actor":{"name":"record-lead","created_at":1792432385.1369143},"strike":{"kind":"strike_todo","expected_revision":3,"actor":{"name":"record-lead","created_at":1792432385.1369143},"previous":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"reason":"The second review found the target branch still missing from the saved record","at":{"kind":"recorded","at":1792432652.476716}},"reason":"The second strike repeated the first finding rather than describing another failed change","at":{"kind":"recorded","at":1792432680.216849}}],"released":[{"kind":"release_todo","expected_revision":4,"actor":{"name":"record-lead","created_at":1792432385.1369143},"previous":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"reason":"The unit left the required target branch absent after both recorded reviews","at":{"kind":"recorded","at":1792432675.6006632}}],"verdicts":[{"kind":"accepted_verdict","expected_revision":1,"actor":{"name":"record-lead","created_at":1792432385.1369143},"previous":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"tool_call_id":"call_a3e3f7350f4e44faa55a72bebf904fa6|fc_01744e18603d1183016ad659dcd6c081a3a769ec0e1880e266","at":{"kind":"recorded","at":1792432628.8483949},"quote":"Decode pull request target branches"}],"corrections":[],"messages":[]},{"row":"todo","id":"P2","repo":"openhcsdev/agent-comms","text":"Show the real verdict and strike records without a second source","creator":"record-lead","creator_created":1792432385.1369143,"state":"open","revision":2,"goal":{"owner":"record-lead","owner_created":1792432385.1369143,"goal_id":"record-request"},"assignment":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"order":{"kind":"delivered","id":"record-order-P2","owner":"/home/ts/wt/pi-cn-phase3--P1/src/agent_comms/todos.py:69","check":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf 1","result":{"kind":"count","expected":1}},"edits":["Decode pull request target branches through the existing record owner"],"banned":["No second order store"],"report":"Report the actual Core tool response","at":{"kind":"recorded","at":1792432735.6061842},"first_call_deadline":{"kind":"recorded","at":1792432855.6061842},"first_call":{"kind":"awaiting"},"run":{"kind":"mechanical"},"delivery":{"kind":"checked_commit","at":{"kind":"recorded","at":1792431763.0},"commit":"e0ca0150042587e7494184b5ab2f6701f3487076","delivered_commit":"5cdac561d498ece25142a8d2e5cf9b06002b6c8a","merge":{"kind":"main"}}},"deadline":{"kind":"recorded","at":1792436335.6061842},"strikes":[],"voided":[],"released":[],"verdicts":[],"corrections":[],"messages":[]},{"row":"todo","id":"P3","repo":"openhcsdev/agent-comms","text":"Show the real verdict and strike records without a second source","creator":"record-lead","creator_created":1792432385.1369143,"state":"open","revision":2,"goal":{"owner":"record-lead","owner_created":1792432385.1369143,"goal_id":"record-request"},"assignment":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"order":{"kind":"delivered","id":"record-order-P3","owner":"/home/ts/wt/pi-cn-phase3--P1/src/agent_comms/todos.py:69","check":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf 1","result":{"kind":"count","expected":1}},"edits":["Decode pull request target branches through the existing record owner"],"banned":["No second order store"],"report":"Report the actual Core tool response","at":{"kind":"recorded","at":1792432771.201293},"first_call_deadline":{"kind":"recorded","at":1792432891.201293},"first_call":{"kind":"awaiting"},"run":{"kind":"mechanical"},"delivery":{"kind":"checked_commit","at":{"kind":"recorded","at":1792432302.0},"commit":"e0ca0150042587e7494184b5ab2f6701f3487076","delivered_commit":"e0ca01500","merge":{"kind":"unmerged","branches":["feat/cn-phase3-record"]}}},"deadline":{"kind":"recorded","at":1792436371.201293},"strikes":[],"voided":[],"released":[],"verdicts":[],"corrections":[],"messages":[]},{"row":"todo","id":"P4","repo":"openhcsdev/agent-comms","text":"Show the real verdict and strike records without a second source","creator":"record-lead","creator_created":1792432385.1369143,"state":"open","revision":1,"goal":{"owner":"record-lead","owner_created":1792432385.1369143,"goal_id":"record-request"},"assignment":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"order":{"kind":"pending","id":"record-order-P4","owner":"/home/ts/wt/pi-cn-phase3--P1/src/agent_comms/todos.py:69","check":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf 1","result":{"kind":"count","expected":1}},"edits":["Decode pull request target branches through the existing record owner"],"banned":["No second order store"],"report":"Report the actual Core tool response","at":{"kind":"recorded","at":1792433118.0605557},"first_call_deadline":{"kind":"recorded","at":1792433238.0605557},"first_call":{"kind":"awaiting"},"run":{"kind":"mechanical"}},"deadline":{"kind":"recorded","at":1792436718.0605557},"strikes":[],"voided":[],"released":[],"verdicts":[],"corrections":[],"messages":[]},{"row":"todo","id":"P5","repo":"openhcsdev/agent-comms","text":"Show the real verdict and strike records without a second source","creator":"record-lead","creator_created":1792432385.1369143,"state":"open","revision":2,"goal":{"owner":"record-lead","owner_created":1792432385.1369143,"goal_id":"record-request"},"assignment":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"order":{"kind":"delivered","id":"record-order-P5","owner":"/home/ts/wt/pi-cn-phase3--P1/src/agent_comms/todos.py:69","check":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf 1","result":{"kind":"count","expected":1}},"edits":["Decode pull request target branches through the existing record owner"],"banned":["No second order store"],"report":"Report the actual Core tool response","at":{"kind":"recorded","at":1792433505.0452404},"first_call_deadline":{"kind":"recorded","at":1792433625.0452404},"first_call":{"kind":"awaiting"},"run":{"kind":"mechanical"},"delivery":{"kind":"checked_commit","at":{"kind":"recorded","at":1792433301.0},"commit":"256efcc8b6d632e224a3a4ffe7d5d1b49c4ae7e5","delivered_commit":"ec5f94568","merge":{"kind":"unmerged","branches":["feat/cn-phase3-record"]}}},"deadline":{"kind":"recorded","at":1792437105.0452404},"strikes":[],"voided":[],"released":[],"verdicts":[],"corrections":[],"messages":[]},{"row":"todo","id":"P6","repo":"openhcsdev/agent-comms","text":"Show the real verdict and strike records without a second source","creator":"record-lead","creator_created":1792432385.1369143,"state":"open","revision":2,"goal":{"owner":"record-lead","owner_created":1792432385.1369143,"goal_id":"record-request"},"assignment":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"order":{"kind":"pending","id":"record-order-P6","owner":"/home/ts/wt/pi-cn-phase3--P1/src/agent_comms/todos.py:69","check":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf x >> /home/ts/wt/pi-cn-phase3--P1/.proof-record-plane/explicit-check-count; printf 0","result":{"kind":"count","expected":0}},"edits":["Decode pull request target branches through the existing record owner"],"banned":["No second order store"],"report":"Report the actual Core tool response","at":{"kind":"recorded","at":1792436082.9144495},"first_call_deadline":{"kind":"recorded","at":1792436202.9144495},"first_call":{"kind":"awaiting"},"check_corrections":[{"kind":"correct_check_todo","expected_revision":1,"actor":{"name":"record-lead","created_at":1792432385.1369143},"previous":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf 1","result":{"kind":"count","expected":1}},"check":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf x >> /home/ts/wt/pi-cn-phase3--P1/.proof-record-plane/explicit-check-count; printf 0","result":{"kind":"count","expected":0}},"reason":"Prove that reading the board cannot execute the check","at":{"kind":"recorded","at":1792436090.2217019}}],"run":{"kind":"mechanical"}},"deadline":{"kind":"recorded","at":1792439682.9144495},"strikes":[],"voided":[],"released":[],"verdicts":[],"corrections":[],"messages":[]}],"progress":{"P2":{"kind":"no_commit"},"P3":{"kind":"no_commit"},"P4":{"kind":"no_commit"},"P5":{"kind":"no_commit"},"P6":{"kind":"pushed","at":1792436129.0,"readings":{"readings":[]},"sha":"1b8bf1c05"}}} +{"todos":[{"row":"todo","id":"P1","repo":"openhcsdev/agent-comms","text":"Show the real verdict and strike records without a second source","creator":"record-lead","creator_created":1792432385.1369143,"state":"open","revision":6,"goal":{"owner":"record-lead","owner_created":1792432385.1369143,"goal_id":"record-request"},"assignment":null,"order":{"kind":"pending","id":"record-order-P1","owner":"/home/ts/wt/pi-cn-phase3--P1/src/agent_comms/todos.py:69","check":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf 1","result":{"kind":"count","expected":1}},"edits":["Decode pull request target branches through the existing record owner"],"banned":["No second order store"],"report":"Report the actual Core tool response","at":{"kind":"recorded","at":1792432466.497828},"first_call_deadline":{"kind":"recorded","at":1792432586.497828},"first_call":{"kind":"awaiting"},"run":{"kind":"mechanical"}},"deadline":{"kind":"recorded","at":1792436066.497828},"strikes":[{"kind":"strike_todo","expected_revision":2,"actor":{"name":"record-lead","created_at":1792432385.1369143},"previous":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"reason":"The assigned change left the target branch out of the saved order record","at":{"kind":"recorded","at":1792432640.0348916}},{"kind":"strike_todo","expected_revision":3,"actor":{"name":"record-lead","created_at":1792432385.1369143},"previous":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"reason":"The second review found the target branch still missing from the saved record","at":{"kind":"recorded","at":1792432652.476716}}],"voided":[{"kind":"void_strike_todo","expected_revision":5,"actor":{"name":"record-lead","created_at":1792432385.1369143},"strike":{"kind":"strike_todo","expected_revision":3,"actor":{"name":"record-lead","created_at":1792432385.1369143},"previous":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"reason":"The second review found the target branch still missing from the saved record","at":{"kind":"recorded","at":1792432652.476716}},"reason":"The second strike repeated the first finding rather than describing another failed change","at":{"kind":"recorded","at":1792432680.216849}}],"released":[{"kind":"release_todo","expected_revision":4,"actor":{"name":"record-lead","created_at":1792432385.1369143},"previous":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"reason":"The unit left the required target branch absent after both recorded reviews","at":{"kind":"recorded","at":1792432675.6006632}}],"verdicts":[{"kind":"accepted_verdict","expected_revision":1,"actor":{"name":"record-lead","created_at":1792432385.1369143},"previous":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"tool_call_id":"call_a3e3f7350f4e44faa55a72bebf904fa6|fc_01744e18603d1183016ad659dcd6c081a3a769ec0e1880e266","at":{"kind":"recorded","at":1792432628.8483949},"quote":"Decode pull request target branches"}],"corrections":[],"messages":[]},{"row":"todo","id":"P2","repo":"openhcsdev/agent-comms","text":"Show the real verdict and strike records without a second source","creator":"record-lead","creator_created":1792432385.1369143,"state":"open","revision":2,"goal":{"owner":"record-lead","owner_created":1792432385.1369143,"goal_id":"record-request"},"assignment":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"order":{"kind":"delivered","id":"record-order-P2","owner":"/home/ts/wt/pi-cn-phase3--P1/src/agent_comms/todos.py:69","check":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf 1","result":{"kind":"count","expected":1}},"edits":["Decode pull request target branches through the existing record owner"],"banned":["No second order store"],"report":"Report the actual Core tool response","at":{"kind":"recorded","at":1792432735.6061842},"first_call_deadline":{"kind":"recorded","at":1792432855.6061842},"first_call":{"kind":"awaiting"},"run":{"kind":"mechanical"},"delivery":{"kind":"checked_commit","at":{"kind":"recorded","at":1792431763.0},"commit":"e0ca0150042587e7494184b5ab2f6701f3487076","delivered_commit":"5cdac561d498ece25142a8d2e5cf9b06002b6c8a","merge":{"kind":"main"}}},"deadline":{"kind":"recorded","at":1792436335.6061842},"strikes":[],"voided":[],"released":[],"verdicts":[],"corrections":[],"messages":[]},{"row":"todo","id":"P3","repo":"openhcsdev/agent-comms","text":"Show the real verdict and strike records without a second source","creator":"record-lead","creator_created":1792432385.1369143,"state":"open","revision":2,"goal":{"owner":"record-lead","owner_created":1792432385.1369143,"goal_id":"record-request"},"assignment":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"order":{"kind":"delivered","id":"record-order-P3","owner":"/home/ts/wt/pi-cn-phase3--P1/src/agent_comms/todos.py:69","check":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf 1","result":{"kind":"count","expected":1}},"edits":["Decode pull request target branches through the existing record owner"],"banned":["No second order store"],"report":"Report the actual Core tool response","at":{"kind":"recorded","at":1792432771.201293},"first_call_deadline":{"kind":"recorded","at":1792432891.201293},"first_call":{"kind":"awaiting"},"run":{"kind":"mechanical"},"delivery":{"kind":"checked_commit","at":{"kind":"recorded","at":1792432302.0},"commit":"e0ca0150042587e7494184b5ab2f6701f3487076","delivered_commit":"e0ca01500","merge":{"kind":"unmerged","branches":["feat/cn-phase3-record"]}}},"deadline":{"kind":"recorded","at":1792436371.201293},"strikes":[],"voided":[],"released":[],"verdicts":[],"corrections":[],"messages":[]},{"row":"todo","id":"P4","repo":"openhcsdev/agent-comms","text":"Show the real verdict and strike records without a second source","creator":"record-lead","creator_created":1792432385.1369143,"state":"open","revision":1,"goal":{"owner":"record-lead","owner_created":1792432385.1369143,"goal_id":"record-request"},"assignment":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"order":{"kind":"pending","id":"record-order-P4","owner":"/home/ts/wt/pi-cn-phase3--P1/src/agent_comms/todos.py:69","check":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf 1","result":{"kind":"count","expected":1}},"edits":["Decode pull request target branches through the existing record owner"],"banned":["No second order store"],"report":"Report the actual Core tool response","at":{"kind":"recorded","at":1792433118.0605557},"first_call_deadline":{"kind":"recorded","at":1792433238.0605557},"first_call":{"kind":"awaiting"},"run":{"kind":"mechanical"}},"deadline":{"kind":"recorded","at":1792436718.0605557},"strikes":[],"voided":[],"released":[],"verdicts":[],"corrections":[],"messages":[]},{"row":"todo","id":"P5","repo":"openhcsdev/agent-comms","text":"Show the real verdict and strike records without a second source","creator":"record-lead","creator_created":1792432385.1369143,"state":"open","revision":2,"goal":{"owner":"record-lead","owner_created":1792432385.1369143,"goal_id":"record-request"},"assignment":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"order":{"kind":"delivered","id":"record-order-P5","owner":"/home/ts/wt/pi-cn-phase3--P1/src/agent_comms/todos.py:69","check":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf 1","result":{"kind":"count","expected":1}},"edits":["Decode pull request target branches through the existing record owner"],"banned":["No second order store"],"report":"Report the actual Core tool response","at":{"kind":"recorded","at":1792433505.0452404},"first_call_deadline":{"kind":"recorded","at":1792433625.0452404},"first_call":{"kind":"awaiting"},"run":{"kind":"mechanical"},"delivery":{"kind":"checked_commit","at":{"kind":"recorded","at":1792433301.0},"commit":"256efcc8b6d632e224a3a4ffe7d5d1b49c4ae7e5","delivered_commit":"ec5f94568","merge":{"kind":"unmerged","branches":["feat/cn-phase3-record"]}}},"deadline":{"kind":"recorded","at":1792437105.0452404},"strikes":[],"voided":[],"released":[],"verdicts":[],"corrections":[],"messages":[]},{"row":"todo","id":"P6","repo":"openhcsdev/agent-comms","text":"Show the real verdict and strike records without a second source","creator":"record-lead","creator_created":1792432385.1369143,"state":"open","revision":2,"goal":{"owner":"record-lead","owner_created":1792432385.1369143,"goal_id":"record-request"},"assignment":{"owner":"record-worker","owner_created":1792432385.2149153,"parent":"record-lead","parent_created":1792432385.1369143,"generation":"record-work"},"order":{"kind":"pending","id":"record-order-P6","owner":"/home/ts/wt/pi-cn-phase3--P1/src/agent_comms/todos.py:69","check":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf x >> /home/ts/wt/pi-cn-phase3--P1/.proof-record-plane/explicit-check-count; printf 0","result":{"kind":"count","expected":0}},"edits":["Decode pull request target branches through the existing record owner"],"banned":["No second order store"],"report":"Report the actual Core tool response","at":{"kind":"recorded","at":1792436082.9144495},"first_call_deadline":{"kind":"recorded","at":1792436202.9144495},"first_call":{"kind":"awaiting"},"check_corrections":[{"kind":"correct_check_todo","expected_revision":1,"actor":{"name":"record-lead","created_at":1792432385.1369143},"previous":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf 1","result":{"kind":"count","expected":1}},"check":{"directory":"/home/ts/wt/pi-cn-phase3--P1","command":"printf x >> /home/ts/wt/pi-cn-phase3--P1/.proof-record-plane/explicit-check-count; printf 0","result":{"kind":"count","expected":0}},"reason":"Prove that reading the board cannot execute the check","at":{"kind":"recorded","at":1792436090.2217019}}],"run":{"kind":"mechanical"}},"deadline":{"kind":"recorded","at":1792439682.9144495},"strikes":[],"voided":[],"released":[],"verdicts":[],"corrections":[],"messages":[]}],"progress":{"P2":{"kind":"no_commit"},"P3":{"kind":"no_commit"},"P4":{"kind":"no_commit"},"P5":{"kind":"no_commit"},"P6":{"kind":"pushed","at":1792436129.0,"readings":{"readings":[{"at":1792436129.0,"distance":0}]},"sha":"1b8bf1c05"}}} diff --git a/src/agent_comms/external_transcript.py b/src/agent_comms/external_transcript.py index b4819fcbe8..f042415db8 100644 --- a/src/agent_comms/external_transcript.py +++ b/src/agent_comms/external_transcript.py @@ -488,7 +488,7 @@ def turns(self, bound: TurnBound) -> tuple[TurnDigest, ...]: return reader.turns(bound) @contextmanager - def reading(self): + def reading(self) -> Iterator[StoreRead]: """Keep item and draft queries within the same SQL snapshot.""" with self.snapshot() as connection: yield StoreRead(self, connection) diff --git a/src/agent_comms/goals.py b/src/agent_comms/goals.py index b2080b6ac8..fbfc145f31 100644 --- a/src/agent_comms/goals.py +++ b/src/agent_comms/goals.py @@ -319,15 +319,15 @@ async def run(self) -> ChildResult: timeout=self.timeout) def recorded(self, sha: str, repository: str) -> tuple[ChildResult, ...]: - """Read an explicit run's receipt; absent evidence never starts the check.""" + """Read the check run result without starting the check.""" return tuple(checked.result for checked in CheckedCommit.stored(repository, sha) if checked.command == self.command)[:1] def at(self, revision: str, repository: str) -> ChildResult: - """The check on ``revision``: a recorded result, or an explicit run in a detached checkout.""" + """Read the result on ``revision``, or explicitly run the check in a detached checkout.""" sha = CheckedCommit.git(repository, "rev-parse", f"{revision}^{{commit}}").strip() - for result in self.recorded(sha, repository): - return result + for recorded in self.recorded(sha, repository): + return recorded with tempfile.TemporaryDirectory(prefix="order-check-") as scratch: tree = str(Path(scratch) / "tree") CheckedCommit.git(repository, "worktree", "add", "--detach", tree, sha) diff --git a/src/agent_comms/todos.py b/src/agent_comms/todos.py index 14e1c9ad30..26793c1f75 100644 --- a/src/agent_comms/todos.py +++ b/src/agent_comms/todos.py @@ -195,7 +195,7 @@ async def merge_state(self, todo: Todo, registry: Registration) -> DeliveryMerge and names a non-main target. Otherwise the pushed source must still have an open link, whose owner is WorkBranches, not a second branch policy in the delivery tool. """ - check = todo.order.check + check = todo.order.checked().check branches = (await self.run( check, "git", "for-each-ref", f"--contains={self.delivered}", "--format=%(refname:strip=3)", "refs/remotes/origin/")).splitlines() @@ -204,9 +204,8 @@ async def merge_state(self, todo: Todo, registry: Registration) -> DeliveryMerge requests = await asyncio.to_thread(PullRequests((todo,)).listed, todo.repo, "merged", todo.order) for request in requests: - if request.merge and await self.contains(check, request.head): - return (MainDeliveryMerge() if request.base == WorkBranches.main - else BranchDeliveryMerge(request.base, request.merge)) + for merge in await request.delivery_merges(self, check): + return merge linked = await asyncio.to_thread( WorkBranches(Path(check.directory)).read, registry.snapshot()) sources = tuple(sorted(branch.name for branch in linked @@ -289,16 +288,42 @@ async def delivery_time(self, check): @dataclass(frozen=True) -class PullRequest: +class PullRequest(DeclaredFamily, affix="PullRequest"): number: int branch: str head: str - merge: str | None = None - merged_at: float | None = None updated_at: float = 0.0 orders: tuple[str, ...] = () base: str = field(default=WorkBranches.main, kw_only=True) + def merged_commits_since(self, since: float) -> Iterator[str]: + return iter(()) + + async def delivery_merges(self, delivery: CommitDelivery, + check: OrderCheck) -> tuple[DeliveryMerge, ...]: + return () + + +class UnmergedPullRequest(PullRequest): + """An open or closed request has no merge commit or integration time.""" + + +@dataclass(frozen=True, kw_only=True) +class MergedPullRequest(PullRequest): + merge: str + merged_at: float + + def merged_commits_since(self, since: float) -> Iterator[str]: + if self.merged_at >= since: + yield self.merge + + async def delivery_merges(self, delivery: CommitDelivery, + check: OrderCheck) -> tuple[DeliveryMerge, ...]: + if not await delivery.contains(check, self.head): + return () + return (MainDeliveryMerge() if self.base == WorkBranches.main + else BranchDeliveryMerge(self.base, self.merge),) + def _github_time(text: str) -> float: return calendar.timegm(time.strptime(text, "%Y-%m-%dT%H:%M:%SZ")) @@ -306,27 +331,39 @@ def _github_time(text: str) -> float: @dataclass(frozen=True) class PullRequestCommit: - """GitHub's optional mergeCommit object, decoded at the query boundary.""" + """GitHub's mergeCommit object on a merged request.""" oid: str @dataclass(frozen=True) -class ListedPullRequest: +class ListedPullRequest(DeclaredFamily, affix="ListedPullRequest"): """The external query row; conversion lives here, never in its consumers.""" number: int branch: str head: str - merge: PullRequestCommit | None - merged: str | None updated: str body: str base: str - def parsed(self) -> PullRequest: - return PullRequest( - self.number, self.branch, self.head, self.merge.oid if self.merge else None, - _github_time(self.merged) if self.merged else None, _github_time(self.updated), - ProblemOrder.pull_request_ids(self.body), base=self.base) + @abstractmethod + def parsed(self) -> PullRequest: ... + + +class UnmergedListedPullRequest(ListedPullRequest): + def parsed(self) -> UnmergedPullRequest: + return UnmergedPullRequest(self.number, self.branch, self.head, _github_time(self.updated), + ProblemOrder.pull_request_ids(self.body), base=self.base) + + +@dataclass(frozen=True, kw_only=True) +class MergedListedPullRequest(ListedPullRequest): + merge: PullRequestCommit + merged: str + + def parsed(self) -> MergedPullRequest: + return MergedPullRequest(self.number, self.branch, self.head, _github_time(self.updated), + ProblemOrder.pull_request_ids(self.body), base=self.base, + merge=self.merge.oid, merged_at=_github_time(self.merged)) class PullRequests: @@ -334,20 +371,23 @@ class PullRequests: def __init__(self, todos: Sequence[Todo] = ()) -> None: self._read: dict[tuple[str, str, str], tuple[PullRequest, ...]] = {} - self._orders: dict[str, dict[str, ProblemOrder]] = {} + self._orders: dict[str, dict[str, CheckedProblemOrder]] = {} for todo in todos: if todo.holder: - self._orders.setdefault(todo.repo, {})[todo.order.id] = todo.order + order = todo.order.checked() + self._orders.setdefault(todo.repo, {})[order.id] = order def listed(self, repo: str, state: str, order: ProblemOrder) -> tuple[PullRequest, ...]: - key = (repo, state, order.id) + checked = order.checked() + key = (repo, state, checked.id) if key not in self._read: - self._read[key] = self.read(repo, state, order) - return tuple(request for request in self._read[key] if order.id in request.orders) + self._read[key] = self.read(repo, state, checked) + return tuple(request for request in self._read[key] if checked.id in request.orders) def read(self, repo: str, state: str, order: ProblemOrder) -> tuple[PullRequest, ...]: + checked = order.checked() orders = self._orders.setdefault(repo, {}) - orders[order.id] = order + orders[checked.id] = checked aliases = {f"o{index}": candidate for index, candidate in enumerate(orders.values())} selections = [] for alias, candidate in aliases.items(): @@ -360,7 +400,9 @@ def read(self, repo: str, state: str, order: ProblemOrder) -> tuple[PullRequest, 'updated: updatedAt } } }') listed = subprocess.run( ("gh", "api", "graphql", "-f", "query=query {" + " ".join(selections) + "}", - "--jq", '.data | map_values(.nodes)'), + "--jq", '.data | map_values(.nodes | map(if .merged == null then ' + f'del(.merge,.merged) + {{kind: "{UnmergedListedPullRequest.declared_name}"}} else ' + f'. + {{kind: "{MergedListedPullRequest.declared_name}"}} end))'), capture_output=True, text=True, timeout=60) if listed.returncode: raise WorkBranchError(listed.stderr.strip() or f"GitHub order lookup in {repo} failed.") @@ -368,7 +410,7 @@ def read(self, repo: str, state: str, order: ProblemOrder) -> tuple[PullRequest, FieldCodec.parse(listed.stdout)) for alias, candidate in aliases.items(): self._read[repo, state, candidate.id] = tuple(row.parsed() for row in rows[alias]) - return self._read[repo, state, order.id] + return self._read[repo, state, checked.id] def _clock(at: float) -> str: @@ -537,7 +579,7 @@ def at(todo: Todo, checkout: Path, pulls: PullRequests) -> ProgressSource: @property def since(self) -> float: - return self.todo.order.at.recorded_at("An order") + return self.todo.order.checked().at.recorded_at("An order") def requests(self, state: str) -> list[PullRequest]: """Explicit PR references survive checkout removal or branch reuse.""" @@ -578,12 +620,12 @@ def readings(self) -> DistanceReadings: reading for point in points for reading in self.recorded_distances(base, point))) def recorded_distances(self, base: str, point: ProgressPoint) -> Iterator[DistanceReading]: - """Only explicit check receipts supply a distance; no receipt means unknown.""" - check = self.todo.order.check - for run in check.recorded(point.sha, str(self.checkout)): + """Read the check run result; without a recorded result the distance is unknown.""" + order = self.todo.order.checked() + for run in order.check.recorded(point.sha, str(self.checkout)): touched = self.git("diff", "--name-only", base, point.sha).split() - yield DistanceReading(point.at, check.result.distance(run) + - self.todo.order.untouched(touched)) + yield DistanceReading(point.at, order.check.result.distance(run) + + order.untouched(touched)) @dataclass(frozen=True) @@ -754,8 +796,8 @@ class MergedProgress(FoundProgress): @classmethod def found(cls, source: ProgressSource) -> Iterator[Self]: - return (cls(pr.merge) for pr in source.requests("merged") - if pr.merge and (pr.merged_at or 0) >= source.since) + return (cls(sha) for pr in source.requests("merged") + for sha in pr.merged_commits_since(source.since)) @property def state(self) -> str: @@ -1286,6 +1328,9 @@ def pull_request_ids(cls, body: str) -> tuple[str, ...]: return tuple(line.removeprefix(cls.pull_request_label).strip() for line in body.splitlines() if line.startswith(cls.pull_request_label)) + def checked(self) -> CheckedProblemOrder: + raise ValueError("This problem has no checked order.") + def require_transfer(self, todo: Todo) -> None: raise ValueError("This problem cannot receive another order.") @@ -1347,6 +1392,9 @@ def withdrawals(self, todo: Todo) -> tuple[str, ...]: class CheckedProblemOrder(ProblemOrder, declared_name=None): """A checked order keeps its actual body, check and source location.""" + def checked(self) -> CheckedProblemOrder: + return self + id: str owner: FileLine check: OrderCheck diff --git a/src/agent_comms/transcript_sources.py b/src/agent_comms/transcript_sources.py index 703ef265c9..6fc2c54c62 100644 --- a/src/agent_comms/transcript_sources.py +++ b/src/agent_comms/transcript_sources.py @@ -7,7 +7,7 @@ from __future__ import annotations from abc import ABC, abstractmethod -from collections.abc import Iterator +from collections.abc import Callable, Iterator from contextlib import closing, contextmanager from dataclasses import dataclass, field, replace from functools import partial @@ -299,7 +299,7 @@ class NoDraft(TranscriptDraft): class TranscriptRecord: """A source position; only native records can project a decoded native entry.""" - def project(self, projection): + def project[Result](self, projection: Callable[[NativeRecord], Result]) -> Result | tuple[()]: """External records have events already, but no native entry for this callback.""" return () diff --git a/tests/test_delivery_merge.py b/tests/test_delivery_merge.py index af1d96d0b0..77966997b3 100644 --- a/tests/test_delivery_merge.py +++ b/tests/test_delivery_merge.py @@ -2,7 +2,8 @@ from agent_comms.field_codec import FieldCodec from agent_comms.todos import ( BranchDeliveryMerge, CheckedCommitDelivery, DeliveryMerge, ListedPullRequest, - MainDeliveryMerge, OrderDelivery, UnmergedDeliveryMerge, + MainDeliveryMerge, MergedListedPullRequest, MergedPullRequest, OrderDelivery, + UnmergedDeliveryMerge, UnmergedListedPullRequest, UnmergedPullRequest, ) @@ -18,14 +19,27 @@ def test_delivery_integration_members_keep_their_data_through_the_only_codec(): def test_named_forge_row_preserves_the_actual_nonmain_merge_target(): - row = FieldCodec.decode(ListedPullRequest, FieldCodec.parse('''{ + row = FieldCodec.decode(MergedListedPullRequest, FieldCodec.parse('''{ "number": 42, "branch": "fix/order", "head": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "merge": {"oid": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"}, "merged": "2026-10-19T12:00:00Z", "updated": "2026-10-19T12:00:01Z", "body": "Agent-Comms-Order: proof-order", "base": "release" }''')) request = row.parsed() + assert isinstance(request, MergedPullRequest) assert request.base == 'release' assert request.merge == 'b' * 40 assert request.merged_at < request.updated_at assert request.orders == ('proof-order',) + assert FieldCodec.decode(ListedPullRequest, FieldCodec.encode(row)) == row + + +def test_unmerged_query_row_has_no_merge_fields(): + row = FieldCodec.decode(UnmergedListedPullRequest, FieldCodec.parse('''{ + "number": 43, "branch": "fix/order", "head": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "updated": "2026-10-19T12:00:01Z", "body": "Agent-Comms-Order: proof-order", "base": "main" + }''')) + request = row.parsed() + assert isinstance(request, UnmergedPullRequest) + assert tuple(request.merged_commits_since(0)) == () + assert FieldCodec.decode(ListedPullRequest, FieldCodec.encode(row)) == row diff --git a/tests/test_order_progress.py b/tests/test_order_progress.py index c2d7ccc2e1..d48e286304 100644 --- a/tests/test_order_progress.py +++ b/tests/test_order_progress.py @@ -13,8 +13,8 @@ from agent_comms.tools import CommsOrderDoneTool from agent_comms.todos import (CommittedProgress, DistanceReadings, MergedProgress, NoCommitProgress, InstallDeadline, InstallDue, OpenPrProgress, OperationDelivery, OrderProgress, PlannedInstallDeadline, - LocalProgressSource, ProgressPoint, ProgressSource, PullRequest, PullRequests, - PushedProgress, UnreadProgress) + LocalProgressSource, MergedPullRequest, ProgressPoint, ProgressSource, PullRequest, PullRequests, + PushedProgress, UnmergedPullRequest, UnreadProgress) from agent_comms.messages import InfoKind from agent_comms.storage_rules import StorageRule @@ -113,7 +113,7 @@ def test_a_pushed_commit_with_no_pr_says_so(checkout): def test_an_open_pr_naming_the_order_shows_its_number_and_head(checkout): - pulls = Listed(open=(PullRequest(12, 'fix/p10', 'f' * 40, orders=('order-P10',)), PullRequest(7, 'fix/p1', 'a' * 40, + pulls = Listed(open=(UnmergedPullRequest(12, 'fix/p10', 'f' * 40, orders=('order-P10',)), UnmergedPullRequest(7, 'fix/p1', 'a' * 40, updated_at=400.0, orders=('order-P1',)))) progress = read(checkout, pulls) assert progress == OpenPrProgress(7, 'a' * 9, at=400.0) @@ -140,27 +140,27 @@ def test_removed_checkout_uses_only_retained_pr_and_present_git_errors_propagate def test_an_order_owns_the_pr_from_its_identity_whatever_the_branch_says(checkout): # #7 was renamed without its P-number; #9 is titled "P1: ..." but comes from another order's branch. - pulls = Listed(open=(PullRequest(9, 'fix/p2', 'b' * 40, updated_at=500.0, orders=('order-P2',)), - PullRequest(7, 'fix/p1', 'a' * 40, updated_at=400.0, orders=('order-P1',)))) + pulls = Listed(open=(UnmergedPullRequest(9, 'fix/p2', 'b' * 40, updated_at=500.0, orders=('order-P2',)), + UnmergedPullRequest(7, 'fix/p1', 'a' * 40, updated_at=400.0, orders=('order-P1',)))) assert read(checkout, pulls) == OpenPrProgress(7, 'a' * 9, at=400.0) def test_a_detached_checkout_still_reads_its_order_pr(checkout): git(checkout[1], 'switch', '-q', '--detach') - assert read(checkout, Listed(open=(PullRequest(7, 'fix/p1', 'a' * 40, updated_at=400.0, orders=('order-P1',)),))) == OpenPrProgress(7, 'a' * 9, at=400.0) + assert read(checkout, Listed(open=(UnmergedPullRequest(7, 'fix/p1', 'a' * 40, updated_at=400.0, orders=('order-P1',)),))) == OpenPrProgress(7, 'a' * 9, at=400.0) def test_an_open_pr_untouched_since_the_order_belongs_to_an_earlier_one(checkout): - assert read(checkout, Listed(open=(PullRequest(5, 'fix/p1', 'e' * 40, updated_at=99.0, orders=('order-P1',)),))) == NoCommitProgress() + assert read(checkout, Listed(open=(UnmergedPullRequest(5, 'fix/p1', 'e' * 40, updated_at=99.0, orders=('order-P1',)),))) == NoCommitProgress() def test_a_pr_merged_before_the_order_is_not_its_merge(checkout): - pulls = Listed(merged=(PullRequest(3, 'fix/p1', 'b' * 40, 'c' * 40, 99.0, 600.0, orders=('order-P1',)),)) + pulls = Listed(merged=(MergedPullRequest(3, 'fix/p1', 'b' * 40, merge='c' * 40, merged_at=99.0, updated_at=600.0, orders=('order-P1',)),)) assert read(checkout, pulls) == NoCommitProgress() def test_a_merge_not_yet_installed_waits_only_on_install(checkout): - progress = read(checkout, Listed(merged=(PullRequest(8, 'fix/p1', 'b' * 40, 'd' * 40, 500.0, 500.0, orders=('order-P1',)),))) + progress = read(checkout, Listed(merged=(MergedPullRequest(8, 'fix/p1', 'b' * 40, merge='d' * 40, merged_at=500.0, updated_at=500.0, orders=('order-P1',)),))) assert progress == MergedProgress('d' * 40) assert progress.line == 'merged as ddddddddd, waiting on install' @@ -205,7 +205,7 @@ def test_board_and_overdue_name_a_merge_waiting_on_install(commander, commander_ unit.mkdir(parents=True) git(unit, 'init', '-q', '-b', 'fix/p1') create(commander, 'P1', deadline=1) - merged = (PullRequest(8, 'fix/p1', 'b' * 40, 'd' * 40, 500.0, 500.0, orders=('order-P1',)),) + merged = (MergedPullRequest(8, 'fix/p1', 'b' * 40, merge='d' * 40, merged_at=500.0, updated_at=500.0, orders=('order-P1',)),) monkeypatch.setattr(PullRequests, 'read', lambda self, repo, state, order: merged if state == 'merged' else ()) assert "waiting on install" in deadline_lines(script)[0] script.board() @@ -238,8 +238,8 @@ def readings(self): return DistanceReadings() def requests(self, state): - return {'open': [PullRequest(7, 'fix/p1', 'b' * 40, updated_at=1000.0, orders=('order-P1',))], - 'merged': [PullRequest(8, 'fix/p1', 'f' * 40, merge='c' * 40, merged_at=200.0, orders=('order-P1',))]}[state] + return {'open': [UnmergedPullRequest(7, 'fix/p1', 'b' * 40, updated_at=1000.0, orders=('order-P1',))], + 'merged': [MergedPullRequest(8, 'fix/p1', 'f' * 40, merge='c' * 40, merged_at=200.0, orders=('order-P1',))]}[state] class EmptySource: @@ -349,7 +349,7 @@ def test_merged_and_delivered_orders_wait_on_the_one_install_deadline(commander, git(unit, 'init', '-q', '-b', 'fix/p1') for pid in ('P1', 'P2', 'P3'): create(commander, pid, deadline=1) - merged = (PullRequest(8, 'fix/p1', 'b' * 40, 'd' * 40, 500.0, 500.0, orders=('order-P1',)),) + merged = (MergedPullRequest(8, 'fix/p1', 'b' * 40, merge='d' * 40, merged_at=500.0, updated_at=500.0, orders=('order-P1',)),) monkeypatch.setattr(PullRequests, 'read', lambda self, repo, state, order: merged if state == 'merged' else ()) for pid in ('P2', 'P3'): CommsOrderDoneTool(problem=pid, order_id=comms.goals.todos.get(pid).order.id, delivery=OperationDelivery(), claims=working(comms)).apply(comms, caller) @@ -391,7 +391,7 @@ def forge(command, **arguments): monkeypatch.setattr(subprocess, "run", forge) script.PullRequestVerb(pid=pid, title=subject, body_file=str(body_file)).run() assert todo.order.pull_request_ids(bodies[0]) == (todo.order.id,) - merged = PullRequest(number, branch, "b" * 40, "d" * 40, + merged = MergedPullRequest(number, branch, "b" * 40, merge="d" * 40, merged_at=500, updated_at=500, orders=todo.order.pull_request_ids(bodies[0])) foreign = replace(merged, number=number + 1, merge="f" * 40, orders=("another-order",)) monkeypatch.setattr(PullRequests, "read", lambda self, repo, state, order: diff --git a/tests/test_standing_rules.py b/tests/test_standing_rules.py index f5883185a2..1d599e41be 100644 --- a/tests/test_standing_rules.py +++ b/tests/test_standing_rules.py @@ -23,7 +23,8 @@ from agent_comms.thread_execution import ExternalThreadExecution from agent_comms.thread_status import StoppedThreadStatus from agent_comms.todos import (CleanHunt, DefectText, DirectiveText, MergedProgress, - NoCommitProgress, OpenPrProgress, OrderProgress, PullRequest, PullRequests, StrikeReason, Todo, UnreadProgress) + MergedPullRequest, NoCommitProgress, OpenPrProgress, OrderProgress, PullRequests, StrikeReason, + Todo, UnmergedPullRequest, UnreadProgress) from agent_comms.connector import CALLER from agent_comms.tools import CommsOrderServeTool, CommsRuleReviewTool, CommsSendTool, CommsStandingRulesTool, CommsTodoTool @@ -55,11 +56,11 @@ def test_board_self_check_uses_order_progress_and_its_linked_merge( commander, standing, monkeypatch, reported, alarms): comms, caller = commander owned, pending, foreign = (create(commander, pid) for pid in ("P77", "P109", "P117")) - merged = PullRequest(1581, "docs/module-layers", "b" * 40, "d" * 40, + merged = MergedPullRequest(1581, "docs/module-layers", "b" * 40, merge="d" * 40, merged_at=500, updated_at=500, orders=(owned.order.id,)) - opened = PullRequest(1624, "ratchet/none-absence", "c" * 40, + opened = UnmergedPullRequest(1624, "ratchet/none-absence", "c" * 40, updated_at=500, orders=(pending.order.id,)) - other = PullRequest(1629, "ratchet/decoder-marker", "e" * 40, "f" * 40, + other = MergedPullRequest(1629, "ratchet/decoder-marker", "e" * 40, merge="f" * 40, merged_at=500, updated_at=500, orders=("another-order",)) def forge(self, repo, state, order): diff --git a/tools/proofs/cn_phase3_record.py b/tools/proofs/cn_phase3_record.py index e1d2ba9ab1..98fc3d858f 100644 --- a/tools/proofs/cn_phase3_record.py +++ b/tools/proofs/cn_phase3_record.py @@ -14,6 +14,7 @@ import sys import time +from agent_comms import cli as comms_cli from agent_comms.change_report import FileLine from agent_comms.comms import Comms from agent_comms.field_codec import FieldCodec @@ -27,14 +28,14 @@ from agent_comms.progress_claims import MergedClaim CHECKOUT = Path(__file__).resolve().parents[2] -ROOT = CHECKOUT / ".proof-record-plane" -RECEIPT = CHECKOUT / "docs/evidence/cn-phase3-record-results.jsonl" +ROOT = Path.home() / '.cache' / 'agent-scratch' / 'pi-cn-phase3' / 'record-plane' +RECEIPT = ROOT.parent / 'cn-phase3-record-results.jsonl' def cli(caller, tool): arguments = FieldCodec.encode(tool) del arguments[tool.family_discriminator] - command = [sys.executable, "-m", "agent_comms.cli", "--root", str(ROOT), "tool", + command = [sys.executable, "-m", comms_cli.__name__, "--root", str(ROOT), "tool", "--from", caller, "--tool", tool.declared_name, "--arguments", json.dumps(arguments)] result = subprocess.run(command, capture_output=True, text=True, timeout=180, @@ -128,17 +129,18 @@ def deliver(problem: str, delivered: str, checked: str | None): if __name__ == "__main__": parser = argparse.ArgumentParser() - parser.add_argument("step", choices=("setup", "order", "record", "deliver")) - parser.add_argument("--session", type=Path) - parser.add_argument("--problem") - parser.add_argument("--delivered") - parser.add_argument("--checked") + steps = parser.add_subparsers(required=True) + steps.add_parser(setup.__name__).set_defaults(action=lambda _: setup()) + order_parser = steps.add_parser(create_order.__name__) + order_parser.add_argument('--problem', default='P1') + order_parser.set_defaults(action=lambda args: create_order(args.problem)) + record_parser = steps.add_parser(record.__name__) + record_parser.add_argument('--session', required=True, type=Path) + record_parser.set_defaults(action=lambda args: record(args.session)) + delivery_parser = steps.add_parser(deliver.__name__) + delivery_parser.add_argument('--problem', required=True) + delivery_parser.add_argument('--delivered', required=True) + delivery_parser.add_argument('--checked') + delivery_parser.set_defaults(action=lambda args: deliver(args.problem, args.delivered, args.checked)) args = parser.parse_args() - if args.step == "setup": - setup() - elif args.step == "order": - create_order() - elif args.step == "deliver": - deliver(args.problem, args.delivered, args.checked) - else: - record(args.session) + args.action(args) diff --git a/tools/proofs/cn_progress_read_only.py b/tools/proofs/cn_progress_read_only.py index ccc2c78d98..514e404eb6 100644 --- a/tools/proofs/cn_progress_read_only.py +++ b/tools/proofs/cn_progress_read_only.py @@ -11,6 +11,7 @@ from tools.proofs.cn_phase3_record import CHECKOUT, ROOT, cli, create_order MARKER = ROOT / 'explicit-check-count' +MARKER_CONTENT = b'x' def prepare(): @@ -45,9 +46,9 @@ def verify(): sha = CheckedCommit.git(str(checkout), 'rev-parse', 'HEAD').strip() assert not todo.order.check.recorded(sha, str(checkout)) todo.order.check.at(sha, str(checkout)) # Deliberately explicit, never a board read. - assert MARKER.read_text() == 'x' + assert MARKER.read_bytes() == MARKER_CONTENT assert len(board().readings) == 1 - assert MARKER.read_text() == 'x' + assert MARKER.read_bytes() == MARKER_CONTENT for receipt in CheckedCommit.stored(str(checkout), sha): if receipt.command == todo.order.check.command: print(FieldCodec.encode(receipt), flush=True) @@ -60,6 +61,8 @@ def verify(): if __name__ == '__main__': parser = argparse.ArgumentParser() - parser.add_argument('step', choices=('prepare', 'verify')) + steps = parser.add_subparsers(required=True) + steps.add_parser(prepare.__name__).set_defaults(action=prepare) + steps.add_parser(verify.__name__).set_defaults(action=verify) args = parser.parse_args() - prepare() if args.step == 'prepare' else verify() + args.action() From 09420d7ec90a874081c07eeff2d684fe7628802c Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 16:28:42 -0400 Subject: [PATCH 10/22] Round trip declared PR variants in codec checks --- tests/test_delivery_merge.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/tests/test_delivery_merge.py b/tests/test_delivery_merge.py index 77966997b3..6bb69fc69c 100644 --- a/tests/test_delivery_merge.py +++ b/tests/test_delivery_merge.py @@ -2,7 +2,7 @@ from agent_comms.field_codec import FieldCodec from agent_comms.todos import ( BranchDeliveryMerge, CheckedCommitDelivery, DeliveryMerge, ListedPullRequest, - MainDeliveryMerge, MergedListedPullRequest, MergedPullRequest, OrderDelivery, + MainDeliveryMerge, MergedListedPullRequest, MergedPullRequest, OrderDelivery, PullRequestCommit, UnmergedDeliveryMerge, UnmergedListedPullRequest, UnmergedPullRequest, ) @@ -19,12 +19,10 @@ def test_delivery_integration_members_keep_their_data_through_the_only_codec(): def test_named_forge_row_preserves_the_actual_nonmain_merge_target(): - row = FieldCodec.decode(MergedListedPullRequest, FieldCodec.parse('''{ - "number": 42, "branch": "fix/order", "head": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "merge": {"oid": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"}, - "merged": "2026-10-19T12:00:00Z", "updated": "2026-10-19T12:00:01Z", - "body": "Agent-Comms-Order: proof-order", "base": "release" - }''')) + original = MergedListedPullRequest(42, 'fix/order', 'a' * 40, + updated='2026-10-19T12:00:01Z', body='Agent-Comms-Order: proof-order', base='release', + merge=PullRequestCommit('b' * 40), merged='2026-10-19T12:00:00Z') + row = FieldCodec.decode(ListedPullRequest, FieldCodec.encode(original)) request = row.parsed() assert isinstance(request, MergedPullRequest) assert request.base == 'release' @@ -35,10 +33,9 @@ def test_named_forge_row_preserves_the_actual_nonmain_merge_target(): def test_unmerged_query_row_has_no_merge_fields(): - row = FieldCodec.decode(UnmergedListedPullRequest, FieldCodec.parse('''{ - "number": 43, "branch": "fix/order", "head": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "updated": "2026-10-19T12:00:01Z", "body": "Agent-Comms-Order: proof-order", "base": "main" - }''')) + original = UnmergedListedPullRequest(43, 'fix/order', 'a' * 40, + updated='2026-10-19T12:00:01Z', body='Agent-Comms-Order: proof-order', base='main') + row = FieldCodec.decode(ListedPullRequest, FieldCodec.encode(original)) request = row.parsed() assert isinstance(request, UnmergedPullRequest) assert tuple(request.merged_commits_since(0)) == () From add99a971c3ed73bc9a7bb753ed3d293a0efa677 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 16:29:28 -0400 Subject: [PATCH 11/22] Import the proof helper through the tools root owner --- tools/proofs/cn_progress_read_only.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/proofs/cn_progress_read_only.py b/tools/proofs/cn_progress_read_only.py index 514e404eb6..a8361bfd34 100644 --- a/tools/proofs/cn_progress_read_only.py +++ b/tools/proofs/cn_progress_read_only.py @@ -8,7 +8,7 @@ from agent_comms.goals import CheckedCommit, CountCheckResult, OrderCheck from agent_comms.todos import CorrectCheckTodo, PushedProgress from agent_comms.tools import CommsTodoListTool, CommsTodoTool, TodoList -from tools.proofs.cn_phase3_record import CHECKOUT, ROOT, cli, create_order +from proofs.cn_phase3_record import CHECKOUT, ROOT, cli, create_order MARKER = ROOT / 'explicit-check-count' MARKER_CONTENT = b'x' From 1c2c5b253b0b3d8a772c5ce0f2c924a4401118ca Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 16:31:39 -0400 Subject: [PATCH 12/22] Type and format the proof drivers through their owning records --- tools/proofs/cn_phase3_record.py | 250 ++++++++++++++++++-------- tools/proofs/cn_progress_read_only.py | 89 +++++---- 2 files changed, 233 insertions(+), 106 deletions(-) diff --git a/tools/proofs/cn_phase3_record.py b/tools/proofs/cn_phase3_record.py index 98fc3d858f..aba8a87702 100644 --- a/tools/proofs/cn_phase3_record.py +++ b/tools/proofs/cn_phase3_record.py @@ -3,16 +3,18 @@ Run setup, perform a real tool edit in the owning native session, then run record with that session path. All observations below are CLI tool results, not hand-constructed events. """ + from __future__ import annotations import argparse import json import os -from pathlib import Path import shutil import subprocess import sys import time +from dataclasses import replace +from pathlib import Path from agent_comms import cli as comms_cli from agent_comms.change_report import FileLine @@ -20,111 +22,213 @@ from agent_comms.field_codec import FieldCodec from agent_comms.goal_actions import CreateProblemGoalAction, SetGoalAction from agent_comms.goals import CountCheckResult, OrderCheck, RequestGoalOrder -from agent_comms.recorded_time import RecordedEventTime -from agent_comms.tools import (CommsFireTool, CommsOrderDoneTool, CommsTodoCreateTool, - CommsTodoTool, CommsVerdictTool) -from agent_comms.todos import (AcceptedVerdict, CommitDelivery, MechanicalOrderRun, - PendingProblemOrder, StrikeReason, StrikeTodo, VoidStrikeTodo) from agent_comms.progress_claims import MergedClaim +from agent_comms.recorded_time import RecordedEventTime +from agent_comms.todos import ( + AcceptedVerdict, + CommitDelivery, + MechanicalOrderRun, + PendingProblemOrder, + StrikeReason, + StrikeTodo, + VoidStrikeTodo, +) +from agent_comms.tool_caller import ToolCaller +from agent_comms.tools import ( + CommsFireTool, + CommsOrderDoneTool, + CommsTodoCreateTool, + CommsTodoTool, + CommsVerdictTool, + ToolRequest, +) +from agent_comms.turn_digests import OrderEditsRead, TurnsSince CHECKOUT = Path(__file__).resolve().parents[2] -ROOT = Path.home() / '.cache' / 'agent-scratch' / 'pi-cn-phase3' / 'record-plane' -RECEIPT = ROOT.parent / 'cn-phase3-record-results.jsonl' +ROOT = Path.home() / ".cache" / "agent-scratch" / "pi-cn-phase3" / "record-plane" +RESULTS = ROOT.parent / "cn-phase3-record-results.jsonl" -def cli(caller, tool): +def cli[Caller: ToolCaller](caller: str, tool: ToolRequest[Caller]) -> str: arguments = FieldCodec.encode(tool) del arguments[tool.family_discriminator] - command = [sys.executable, "-m", comms_cli.__name__, "--root", str(ROOT), "tool", - "--from", caller, "--tool", tool.declared_name, - "--arguments", json.dumps(arguments)] - result = subprocess.run(command, capture_output=True, text=True, timeout=180, - env=os.environ | {"PYTHONPATH": str(CHECKOUT / "src")}) + command = [ + sys.executable, + "-m", + comms_cli.__name__, + "--root", + str(ROOT), + "tool", + "--from", + caller, + "--tool", + tool.declared_name, + "--arguments", + json.dumps(arguments), + ] + result = subprocess.run( + command, + capture_output=True, + text=True, + timeout=180, + env=os.environ | {"PYTHONPATH": str(CHECKOUT / "src")}, + ) print(result.stdout, end="", flush=True) if result.returncode: raise RuntimeError(result.stderr or result.stdout) - with RECEIPT.open("a") as output: + with RESULTS.open("a") as output: output.write(result.stdout) return result.stdout -def setup(): +def setup() -> None: if ROOT.exists(): raise RuntimeError(f"Refusing to reuse an existing proof root: {ROOT}") comms = Comms(ROOT) comms.messaging.initialize_private_initial_protocol() for name in ("record-lead", "record-worker"): - comms.registry.declare(comms.registry.new_thread( - name, frozenset(), str(CHECKOUT), created_at=time.time())) - comms.goals.update_goal("record-lead", SetGoalAction( - text="Confirm the order record through the actual Core tools on this isolated bus", - goal_id="record-request", order=RequestGoalOrder())) - comms.goals.update_goal("record-worker", SetGoalAction( - text="Complete the assigned record-plane proof without touching any live session", - goal_id="record-work")) + comms.registry.declare( + comms.registry.new_thread(name, frozenset(), str(CHECKOUT), created_at=time.time()) + ) + comms.goals.update_goal( + "record-lead", + SetGoalAction( + text="Confirm the order record through the actual Core tools on this isolated bus", + goal_id="record-request", + order=RequestGoalOrder(), + ), + ) + comms.goals.update_goal( + "record-worker", + SetGoalAction( + text="Complete the assigned record-plane proof without touching any live session", + goal_id="record-work", + ), + ) create_order() -def create_order(problem="P1"): +def create_order(problem: str = "P1") -> None: now = time.time() - cli("record-lead", CommsTodoCreateTool(owner="record-worker", order=CreateProblemGoalAction( - problem=problem, repo="OpenHCSDev/agent-comms", - text="Show the real verdict and strike records without a second source", - request="record-request", commander="record-lead", - deadline=RecordedEventTime(now + 3600), order=PendingProblemOrder( - id=f"record-order-{problem}", owner=FileLine(str(CHECKOUT / "src/agent_comms/todos.py"), 69), - check=OrderCheck(str(CHECKOUT), "printf 1", CountCheckResult(1)), - edits=("Decode pull request target branches through the existing record owner",), - banned=("No second order store",), report="Report the actual Core tool response", - run=MechanicalOrderRun(), at=RecordedEventTime(now), - first_call_deadline=RecordedEventTime(now + 120))))) - - -def record(session: Path): - from dataclasses import replace - from agent_comms.turn_digests import OrderEditsRead, TurnsSince - + cli( + "record-lead", + CommsTodoCreateTool( + owner="record-worker", + order=CreateProblemGoalAction( + problem=problem, + repo="OpenHCSDev/agent-comms", + text="Show the real verdict and strike records without a second source", + request="record-request", + commander="record-lead", + deadline=RecordedEventTime(now + 3600), + order=PendingProblemOrder( + id=f"record-order-{problem}", + owner=FileLine(str(CHECKOUT / "src/agent_comms/todos.py"), 69), + check=OrderCheck(str(CHECKOUT), "printf 1", CountCheckResult(1)), + edits=( + "Decode pull request target branches through the existing record owner", + ), + banned=("No second order store",), + report="Report the actual Core tool response", + run=MechanicalOrderRun(), + at=RecordedEventTime(now), + first_call_deadline=RecordedEventTime(now + 120), + ), + ), + ), + ) + + +def record(session: Path) -> None: retained = ROOT / "record-worker.jsonl" shutil.copy2(session, retained) comms = Comms(ROOT) worker = comms.registry.require("record-worker") - comms.registry.register(replace(worker, session_file=str(retained)), - comms.registry.status(worker.name)) + comms.registry.register( + replace(worker, session_file=str(retained)), comms.registry.status(worker.name) + ) todo = comms.goals.todos.get("P1") - since = todo.order.at.recorded_at("An order") - calls = OrderEditsRead(ROOT, comms.registry.require(worker.name), TurnsSince(since), todo).read() + since = todo.order.checked().at.recorded_at("An order") + calls = OrderEditsRead( + ROOT, comms.registry.require(worker.name), TurnsSince(since), todo + ).read() if not calls: raise RuntimeError("No actual native edit call followed the order; do not invent one") judged = calls[0].id - cli("record-lead", CommsVerdictTool(id="P1", operation=AcceptedVerdict( - todo.revision, comms.registry.require("record-lead").incarnation, todo.held(), judged, - quote="Decode pull request target branches"))) - for reason in ("The assigned change left the target branch out of the saved order record", - "The second review found the target branch still missing from the saved record"): + cli( + "record-lead", + CommsVerdictTool( + id="P1", + operation=AcceptedVerdict( + todo.revision, + comms.registry.require("record-lead").incarnation, + todo.held(), + judged, + quote="Decode pull request target branches", + ), + ), + ) + for reason in ( + "The assigned change left the target branch out of the saved order record", + "The second review found the target branch still missing from the saved record", + ): todo = comms.goals.todos.get("P1") - cli("record-lead", CommsTodoTool(id="P1", operation=StrikeTodo( - todo.revision, comms.registry.require("record-lead").incarnation, todo.held(), reason))) + cli( + "record-lead", + CommsTodoTool( + id="P1", + operation=StrikeTodo( + todo.revision, + comms.registry.require("record-lead").incarnation, + todo.held(), + reason, + ), + ), + ) todo = comms.goals.todos.get("P1") assert todo.struck_out(worker.name) - cli("record-lead", CommsFireTool(unit=worker.name, reason=StrikeReason( - "The unit left the required target branch absent after both recorded reviews"))) + cli( + "record-lead", + CommsFireTool( + unit=worker.name, + reason=StrikeReason( + "The unit left the required target branch absent after both recorded reviews" + ), + ), + ) todo = comms.goals.todos.get("P1") - cli("record-lead", CommsTodoTool(id="P1", operation=VoidStrikeTodo( - todo.revision, comms.registry.require("record-lead").incarnation, todo.strikes[-1], - "The second strike repeated the first finding rather than describing another failed change"))) + cli( + "record-lead", + CommsTodoTool( + id="P1", + operation=VoidStrikeTodo( + todo.revision, + comms.registry.require("record-lead").incarnation, + todo.strikes[-1], + "The second strike repeated the first finding " + "rather than describing another failed change", + ), + ), + ) todo = comms.goals.todos.get("P1") assert len(todo.strikes) == 2 and len(todo.voided) == 1 - assert len(todo.live_strikes(worker.name)) == 1 and todo.assignment is None + assert len(todo.live_strikes(worker.name)) == 1 and len(todo.released) == 1 + released = todo.released[-1].previous + assert released.owner_identity == worker.incarnation + todo.require_released_order(worker.incarnation, released.generation) -def deliver(problem: str, delivered: str, checked: str | None): +def deliver(problem: str, delivered: str, checked: str) -> None: create_order(problem) - checked = checked or subprocess.check_output( - ["git", "rev-parse", "HEAD"], cwd=CHECKOUT, text=True).strip() - cli("record-lead", CommsOrderDoneTool( - problem=problem, order_id=f"record-order-{problem}", - delivery=CommitDelivery(commit=checked, delivered_commit=delivered), - claims=(MergedClaim(delivered),))) + cli( + "record-lead", + CommsOrderDoneTool( + problem=problem, + order_id=f"record-order-{problem}", + delivery=CommitDelivery(commit=checked, delivered_commit=delivered), + claims=(MergedClaim(delivered),), + ), + ) if __name__ == "__main__": @@ -132,15 +236,17 @@ def deliver(problem: str, delivered: str, checked: str | None): steps = parser.add_subparsers(required=True) steps.add_parser(setup.__name__).set_defaults(action=lambda _: setup()) order_parser = steps.add_parser(create_order.__name__) - order_parser.add_argument('--problem', default='P1') + order_parser.add_argument("--problem", default="P1") order_parser.set_defaults(action=lambda args: create_order(args.problem)) record_parser = steps.add_parser(record.__name__) - record_parser.add_argument('--session', required=True, type=Path) + record_parser.add_argument("--session", required=True, type=Path) record_parser.set_defaults(action=lambda args: record(args.session)) delivery_parser = steps.add_parser(deliver.__name__) - delivery_parser.add_argument('--problem', required=True) - delivery_parser.add_argument('--delivered', required=True) - delivery_parser.add_argument('--checked') - delivery_parser.set_defaults(action=lambda args: deliver(args.problem, args.delivered, args.checked)) + delivery_parser.add_argument("--problem", required=True) + delivery_parser.add_argument("--delivered", required=True) + delivery_parser.add_argument("--checked", required=True) + delivery_parser.set_defaults( + action=lambda args: deliver(args.problem, args.delivered, args.checked) + ) args = parser.parse_args() args.action(args) diff --git a/tools/proofs/cn_progress_read_only.py b/tools/proofs/cn_progress_read_only.py index a8361bfd34..9c7174c10c 100644 --- a/tools/proofs/cn_progress_read_only.py +++ b/tools/proofs/cn_progress_read_only.py @@ -1,65 +1,86 @@ -"""Real Core board reads consume explicit check receipts, never run the check.""" +"""Real Core board reads consume the recorded check run result, never run the check.""" + import argparse import shlex import subprocess +from proofs.cn_phase3_record import CHECKOUT, ROOT, cli, create_order + from agent_comms.comms import Comms from agent_comms.field_codec import FieldCodec from agent_comms.goals import CheckedCommit, CountCheckResult, OrderCheck -from agent_comms.todos import CorrectCheckTodo, PushedProgress +from agent_comms.todos import CorrectCheckTodo, DistanceReadings, PushedProgress, Todo from agent_comms.tools import CommsTodoListTool, CommsTodoTool, TodoList -from proofs.cn_phase3_record import CHECKOUT, ROOT, cli, create_order -MARKER = ROOT / 'explicit-check-count' -MARKER_CONTENT = b'x' +MARKER = ROOT / "explicit-check-count" +MARKER_CONTENT = b"x" -def prepare(): - create_order('P6') +def prepare() -> None: + create_order("P6") comms = Comms(ROOT) - todo = comms.goals.todos.get('P6') - check = OrderCheck(str(CHECKOUT), f'printf x >> {shlex.quote(str(MARKER))}; printf 0', - CountCheckResult(0)) - cli('record-lead', CommsTodoTool(id='P6', operation=CorrectCheckTodo( - todo.revision, comms.registry.require('record-lead').incarnation, - todo.order.check, check, 'Prove that reading the board cannot execute the check'))) + todo = comms.goals.todos.get("P6") + check = OrderCheck( + str(CHECKOUT), f"printf x >> {shlex.quote(str(MARKER))}; printf 0", CountCheckResult(0) + ) + cli( + "record-lead", + CommsTodoTool( + id="P6", + operation=CorrectCheckTodo( + todo.revision, + comms.registry.require("record-lead").incarnation, + todo.order.checked().check, + check, + "Prove that reading the board cannot execute the check", + ), + ), + ) -def board(): - listed = FieldCodec.decode(TodoList, FieldCodec.parse(cli('record-lead', CommsTodoListTool()))) - progress = listed.progress['P6'] +def board(todo: Todo) -> DistanceReadings: + listed = FieldCodec.decode(TodoList, FieldCodec.parse(cli("record-lead", CommsTodoListTool()))) + progress = listed.progress[todo.id] assert isinstance(progress, PushedProgress), progress return progress.distance_readings() -def verify(): +def verify() -> None: comms = Comms(ROOT) - todo = comms.goals.todos.get('P6') + todo = comms.goals.todos.get("P6") + check = todo.order.checked().check checkout = comms.registry.storage_rule.order_checkout(todo.held().owner, todo.id) if checkout.exists() or MARKER.exists(): - raise RuntimeError('Proof resources already exist; inspect before another attempt') - subprocess.run(['git', 'worktree', 'add', '--detach', str(checkout), 'HEAD'], - cwd=CHECKOUT, check=True, timeout=60) + raise RuntimeError("Proof resources already exist; inspect before another attempt") + subprocess.run( + ["git", "worktree", "add", "--detach", str(checkout), "HEAD"], + cwd=CHECKOUT, + check=True, + timeout=60, + ) try: - assert not board().readings + assert not board(todo).readings assert not MARKER.exists() - sha = CheckedCommit.git(str(checkout), 'rev-parse', 'HEAD').strip() - assert not todo.order.check.recorded(sha, str(checkout)) - todo.order.check.at(sha, str(checkout)) # Deliberately explicit, never a board read. + sha = CheckedCommit.git(str(checkout), "rev-parse", "HEAD").strip() + assert not check.recorded(sha, str(checkout)) + check.at(sha, str(checkout)) # Deliberately explicit, never a board read. assert MARKER.read_bytes() == MARKER_CONTENT - assert len(board().readings) == 1 + assert len(board(todo).readings) == 1 assert MARKER.read_bytes() == MARKER_CONTENT - for receipt in CheckedCommit.stored(str(checkout), sha): - if receipt.command == todo.order.check.command: - print(FieldCodec.encode(receipt), flush=True) - print('Actual CLI: unknown before explicit run, recorded afterward; exactly one execution.', - flush=True) + for result in CheckedCommit.stored(str(checkout), sha): + if result.command == check.command: + print(FieldCodec.encode(result), flush=True) + print( + "Actual CLI: unknown before explicit run, recorded afterward; exactly one execution.", + flush=True, + ) finally: - subprocess.run(['git', 'worktree', 'remove', str(checkout)], cwd=CHECKOUT, - check=True, timeout=60) + subprocess.run( + ["git", "worktree", "remove", str(checkout)], cwd=CHECKOUT, check=True, timeout=60 + ) -if __name__ == '__main__': +if __name__ == "__main__": parser = argparse.ArgumentParser() steps = parser.add_subparsers(required=True) steps.add_parser(prepare.__name__).set_defaults(action=prepare) From 3bcf3f3b5b3892fbe944e85a280099199d946e96 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 17:15:49 -0400 Subject: [PATCH 13/22] Record explicit check runs before reading progress trends --- tests/test_standing_rules.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/test_standing_rules.py b/tests/test_standing_rules.py index 1d599e41be..8ea363fd3a 100644 --- a/tests/test_standing_rules.py +++ b/tests/test_standing_rules.py @@ -189,20 +189,24 @@ def commit(left): runs = tmp_path / "runs" check = f"echo run >> {runs}; cat left" todo = create(commander, "P1", command=check, expected=0, deadline=time.time() + 3600) + ordered_check = todo.order.checked().check + ordered_check.at(git("rev-parse", "HEAD").strip(), str(unit)) def progress(): return OrderProgress.read(comms.goals.todos.get("P1"), storage, PullRequests()) commit(2) + ordered_check.at(git("rev-parse", "HEAD").strip(), str(unit)) assert "; distance 2, projected done " in progress().line assert isinstance(progress().distance_readings().trend, MovingTrend) commit(2) + ordered_check.at(git("rev-parse", "HEAD").strip(), str(unit)) assert progress().line.endswith("; distance 2, not falling") assert runs.read_text().count("run") == 3 # once per commit: base, then each branch commit reader = ("import sys; from agent_comms.goals import CountCheckResult, OrderCheck; " "check = OrderCheck(sys.argv[1], sys.argv[2], CountCheckResult(0)); " - "print(check.at(sys.argv[3], sys.argv[1]).stdout.decode(), end='')") + "print(check.recorded(sys.argv[3], sys.argv[1])[0].stdout.decode(), end='')") second = subprocess.run([sys.executable, "-c", reader, str(unit), check, git("rev-parse", "HEAD").strip()], check=True, capture_output=True, text=True) From 75994398f1939978aa918ab6250c597689d0b7dc Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 18:40:47 -0400 Subject: [PATCH 14/22] Preserve the existing checkout owner and recorded-progress shape on current main --- src/agent_comms/todos.py | 103 ++++++++++++----------------------- tests/test_order_progress.py | 22 ++++---- 2 files changed, 48 insertions(+), 77 deletions(-) diff --git a/src/agent_comms/todos.py b/src/agent_comms/todos.py index 26793c1f75..0cb6794c90 100644 --- a/src/agent_comms/todos.py +++ b/src/agent_comms/todos.py @@ -453,8 +453,8 @@ def trend(self) -> DistanceTrend: self.counted_trend, lambda: UncountedTrend(self.readings[-1].command)) def counted_trend(self, final: int) -> DistanceTrend: - last = self.readings[-2:] - if len(last) < 2 or not final: + last = self.readings[-self.window:] + if len(last) < self.window or not final: return UnreadTrend() first, latest = last @@ -521,7 +521,7 @@ class ProgressCheckout(DeclaredFamily, affix="Checkout"): path: Path @abstractmethod - def commits(self, since: float, *excluded: str) -> Iterator[tuple[str, float]]: ... + def commits(self, since: float, *excluded: str) -> Iterator[ProgressPoint]: ... @abstractmethod def readings(self, source: ProgressSource) -> DistanceReadings: ... @@ -530,7 +530,7 @@ def readings(self, source: ProgressSource) -> DistanceReadings: ... class MissingCheckout(ProgressCheckout): """A retired checkout contributes no local history, not a failed Git read.""" - def commits(self, since: float, *excluded: str) -> Iterator[tuple[str, float]]: + def commits(self, since: float, *excluded: str) -> Iterator[ProgressPoint]: return iter(()) def readings(self, source: ProgressSource) -> DistanceReadings: @@ -539,43 +539,49 @@ def readings(self, source: ProgressSource) -> DistanceReadings: class PresentCheckout(ProgressCheckout): def git(self, *args: str) -> str: - run = subprocess.run(("git", "-C", str(self.path), *args), - capture_output=True, text=True, timeout=60) - if run.returncode: - raise ValueError(f"git {args[0]} failed: {run.stderr.strip()}") - return run.stdout + return CheckedCommit.git( + str(self.path), f"--git-dir={(self.path / '.git').absolute()}", *args) - def commits(self, since: float, *excluded: str) -> Iterator[tuple[str, float]]: + def commits(self, since: float, *excluded: str) -> Iterator[ProgressPoint]: """Newest branch commit; a Git failure in a present checkout remains an error.""" after = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(since)) - for line in self.git("log", "-1", f"--since={after}", "--format=%H %ct", - "HEAD", "--not", *excluded).splitlines(): - sha, at = line.split() - yield sha, float(at) + log = self.git("log", "-1", f"--since={after}", f"--format={ProgressPoint.format}", + "HEAD", "--not", *excluded) + return (ProgressPoint.listed(line) for line in log.splitlines()) def readings(self, source: ProgressSource) -> DistanceReadings: - """The order's base and newest branch commit bound its distance trend.""" + """Read the bounded distance trend only from explicitly recorded check results.""" base = self.git("merge-base", "HEAD", "origin/HEAD").strip() after = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(source.since)) - commits = [(sha, float(at)) for sha, at in (line.split() for line in reversed(self.git( - "log", "--first-parent", f"--since={after}", "--format=%H %ct", "HEAD", - "--not", base).splitlines()))] - points = [(base, source.since), *commits][-2:] - return DistanceReadings( - tuple(source.reading(self, base, sha, at) for sha, at in points)) + commits = [ProgressPoint.listed(line) for line in reversed(self.git( + "log", f"--max-count={DistanceReadings.window}", "--first-parent", f"--since={after}", + f"--format={ProgressPoint.format}", "HEAD", "--not", base).splitlines())] + points = [ProgressPoint(base, source.since), *commits][-DistanceReadings.window:] + order = source.todo.order.checked() + return DistanceReadings(tuple( + reading for point in points for reading in self.recorded_distances(order, base, point))) + + def recorded_distances(self, order: CheckedProblemOrder, base: str, + point: ProgressPoint) -> Iterator[DistanceReading]: + """Read the check run result; without a recorded result the distance is unknown.""" + for run in order.check.recorded(point.sha, str(self.path)): + touched = self.git("diff", "--name-only", base, point.sha).split() + yield DistanceReading(point.at, order.check.result.distance(run).plus( + order.untouched(touched)), order.check.command) @dataclass(frozen=True) class ProgressSource: - """The repository's PR facts, extended by local evidence only while a checkout is available.""" + """An order's local checkout capability and independently retained PR references.""" todo: Todo - checkout: Path + checkout: ProgressCheckout pulls: PullRequests - @staticmethod - def at(todo: Todo, checkout: Path, pulls: PullRequests) -> ProgressSource: - return (LocalProgressSource(todo, checkout, pulls) if checkout.is_dir() - else ProgressSource(todo, checkout, pulls)) + @classmethod + def of(cls, todo: Todo, storage: StorageRule, pulls: PullRequests) -> Self: + path = storage.order_checkout(todo.held().owner, todo.id) + checkout = PresentCheckout(path) if path.is_dir() else MissingCheckout(path) + return cls(todo, checkout, pulls) @property def since(self) -> float: @@ -587,45 +593,10 @@ def requests(self, state: str) -> list[PullRequest]: if pr.updated_at >= self.since] def commits(self, *excluded: str) -> Iterator[ProgressPoint]: - """A remote source makes no claim about unpublished local commits.""" - return iter(()) + return self.checkout.commits(self.since, *excluded) def readings(self) -> DistanceReadings: - return DistanceReadings() - - -class LocalProgressSource(ProgressSource): - """An available checkout supplies the local commit and distance evidence.""" - - def commits(self, *excluded: str) -> Iterator[ProgressPoint]: - """The checkout's newest commit since the order that no ``excluded`` ref already holds.""" - after = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(self.since)) - log = self.git("log", "-1", f"--since={after}", f"--format={ProgressPoint.format}", - "HEAD", "--not", *excluded) - return (ProgressPoint.listed(line) for line in log.splitlines()) - - def git(self, *args: str) -> str: - return CheckedCommit.git( - str(self.checkout), f"--git-dir={(self.checkout / '.git').absolute()}", *args) - - def readings(self) -> DistanceReadings: - """The order's base, then each branch commit since the order; the last two are read.""" - base = self.git("merge-base", "HEAD", "origin/HEAD").strip() - after = time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime(self.since)) - commits = [ProgressPoint.listed(line) for line in reversed(self.git( - "log", f"--max-count={DistanceReadings.window}", "--first-parent", f"--since={after}", - f"--format={ProgressPoint.format}", "HEAD", "--not", base).splitlines())] - points = [ProgressPoint(base, self.since), *commits][-DistanceReadings.window:] - return DistanceReadings(tuple( - reading for point in points for reading in self.recorded_distances(base, point))) - - def recorded_distances(self, base: str, point: ProgressPoint) -> Iterator[DistanceReading]: - """Read the check run result; without a recorded result the distance is unknown.""" - order = self.todo.order.checked() - for run in order.check.recorded(point.sha, str(self.checkout)): - touched = self.git("diff", "--name-only", base, point.sha).split() - yield DistanceReading(point.at, order.check.result.distance(run) + - order.untouched(touched)) + return self.checkout.readings(self) @dataclass(frozen=True) @@ -637,9 +608,7 @@ class OrderProgress(DeclaredFamily, affix="Progress"): @classmethod def read(cls, todo: Todo, storage: StorageRule, pulls: PullRequests) -> OrderProgress: """The first found member by its declared precedence; with none found, no commit yet.""" - owner = todo.holder - assert owner, "Only a held order has progress." - source = ProgressSource.at(todo, storage.order_checkout(owner, todo.id), pulls) + source = ProgressSource.of(todo, storage, pulls) members = sorted(cls.members_with(FoundProgress), key=lambda member: member.precedence) try: return next((progress for member in members for progress in member.found(source)), diff --git a/tests/test_order_progress.py b/tests/test_order_progress.py index d48e286304..41bcef4074 100644 --- a/tests/test_order_progress.py +++ b/tests/test_order_progress.py @@ -9,11 +9,12 @@ import pytest from agent_comms.recorded_time import RecordedEventTime +from agent_comms.child_process import CountPrinted from agent_comms.goals import CountCheckResult, GitCommandFailed, OrderCheck from agent_comms.tools import CommsOrderDoneTool from agent_comms.todos import (CommittedProgress, DistanceReadings, MergedProgress, NoCommitProgress, InstallDeadline, InstallDue, OpenPrProgress, OperationDelivery, OrderProgress, PlannedInstallDeadline, - LocalProgressSource, MergedPullRequest, ProgressPoint, ProgressSource, PullRequest, PullRequests, + MergedPullRequest, PresentCheckout, ProgressPoint, ProgressSource, PullRequest, PullRequests, PushedProgress, UnmergedPullRequest, UnreadProgress) from agent_comms.messages import InfoKind @@ -71,7 +72,7 @@ def test_no_commit_since_the_order_is_explicit(checkout): def test_uncounted_check_keeps_other_overdue_orders_visible(commander, commander_script, checkout, monkeypatch): - from agent_comms.child_process import CountPrinted, NoIntegerPrinted + from agent_comms.child_process import NoIntegerPrinted from agent_comms.todos import DistanceReading, UncountedTrend script, _ = commander_script @@ -84,6 +85,7 @@ def test_uncounted_check_keeps_other_overdue_orders_visible(commander, commander replace(todo, order=broken).upsert(db) create(commander, 'P2', deadline=1) commit(unit) + broken.check.at(git(unit, 'rev-parse', 'HEAD'), str(unit)) progress = read((comms.goals.todos.get('P1'), unit)) assert progress.line.endswith("; check prints no integer: printf 'no count here'") @@ -123,7 +125,7 @@ def test_an_open_pr_naming_the_order_shows_its_number_and_head(checkout): def test_removed_checkout_uses_only_retained_pr_and_present_git_errors_propagate(checkout): _, unit = checkout shutil.rmtree(unit) - pulls = Listed(open=(PullRequest(7, 'fix/p1', 'a' * 40, + pulls = Listed(open=(UnmergedPullRequest(7, 'fix/p1', 'a' * 40, updated_at=400.0, orders=('order-P1',)),)) progress = read(checkout, pulls) assert progress == OpenPrProgress(7, 'a' * 9, at=400.0) @@ -178,21 +180,21 @@ def test_progress_reads_only_explicit_check_receipts(checkout, tmp_path): check = OrderCheck(str(unit), f'printf x >> {shlex.quote(str(marker))}; printf 0', CountCheckResult(0)) todo = replace(todo, order=replace(todo.order, check=check)) - source = ProgressSource.at(todo, unit, Listed()) - assert source.readings() == DistanceReadings() + source = ProgressSource.of(todo, StorageRule(checkout_base=unit.parent), Listed()) + assert not source.readings().readings assert not marker.exists() check.at(git(unit, 'rev-parse', 'HEAD').strip(), str(unit)) assert marker.read_text() == 'x' assert len(source.readings().readings) == 1 - assert source.readings().readings[0].distance == todo.order.untouched(()) + assert source.readings().readings[0].distance == CountPrinted(todo.order.checked().untouched(())) assert marker.read_text() == 'x' def test_an_existing_broken_checkout_is_not_silently_treated_as_missing(checkout, tmp_path): - bad = tmp_path / 'not-a-repository' - bad.mkdir() - source = ProgressSource.at(checkout[0], bad, Listed()) - assert isinstance(source, LocalProgressSource) + bad = tmp_path / 'not-a-repository' / 'worker--P1' + bad.mkdir(parents=True) + source = ProgressSource.of(checkout[0], StorageRule(checkout_base=bad.parent), Listed()) + assert isinstance(source.checkout, PresentCheckout) with pytest.raises(GitCommandFailed): source.readings() From abb389d5b0cf075f62b83cadcf6963ba28dad252 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 18:45:36 -0400 Subject: [PATCH 15/22] Let repository facts load without starting commander Core imports --- src/agent_comms/cli_commands.py | 3 ++- src/agent_comms/commander/config.py | 29 +---------------------- src/agent_comms/commander/order.py | 3 ++- src/agent_comms/commander/standing.py | 3 ++- src/agent_comms/repo.py | 34 +++++++++++++++++++++++++++ src/agent_comms/todos.py | 2 +- src/agent_comms/work_branches.py | 2 +- tests/test_installed_merge.py | 2 +- tests/test_standing_rules.py | 3 ++- tools/cutover/issue_rts_orders.py | 2 +- 10 files changed, 47 insertions(+), 36 deletions(-) create mode 100644 src/agent_comms/repo.py diff --git a/src/agent_comms/cli_commands.py b/src/agent_comms/cli_commands.py index c69ae0b9d8..874bde65e0 100644 --- a/src/agent_comms/cli_commands.py +++ b/src/agent_comms/cli_commands.py @@ -1462,7 +1462,8 @@ class InstallCliCommand(CliCommand): commander: str = option("--commander", default="claude-code") def apply(self, ctx: Comms) -> CliResult: - from .commander.config import CommanderConfig, Repo + from .commander.config import CommanderConfig + from .repo import Repo home = Path.home() project = self.project.resolve() diff --git a/src/agent_comms/commander/config.py b/src/agent_comms/commander/config.py index df6c020045..f82957b772 100644 --- a/src/agent_comms/commander/config.py +++ b/src/agent_comms/commander/config.py @@ -5,7 +5,6 @@ """ from __future__ import annotations -import subprocess from dataclasses import dataclass from functools import cache from importlib.resources import files @@ -16,6 +15,7 @@ from ..comms import Comms from ..errors import RelationViolationError from ..native_arguments import NativeArguments +from ..repo import Repo from ..threads import Thread from ..todos import Todo @@ -32,33 +32,6 @@ def require_commander(thread: Thread) -> None: ) -class Repo(BaseModel): - """A repository the fleet works on: its check checkout, its GitHub name and its source root.""" - - model_config = ConfigDict(frozen=True) - - checkout: Path - github: str - source: str = 'src' - - def contains(self, sha: str) -> bool: - """Only a successful ancestry proof in this installed checkout permits closure.""" - result = subprocess.run( - ('git', '-C', str(self.checkout), 'merge-base', '--is-ancestor', sha, 'HEAD'), - capture_output=True, timeout=60) - if result.returncode not in (0, 1): - result.check_returncode() - return result.returncode == 0 - - @classmethod - def of(cls, checkout: Path) -> Repo: - """Its GitHub name comes from the checkout's own origin remote.""" - url = subprocess.run(['git', '-C', str(checkout), 'remote', 'get-url', 'origin'], - capture_output=True, text=True, check=True).stdout.strip() - github = url.removesuffix('.git').replace(':', '/').split('/')[-2:] - return cls(checkout=checkout.resolve(), github='/'.join(github)) - - class CommanderConfig(CommanderRole, BaseModel): model_config = ConfigDict(frozen=True) diff --git a/src/agent_comms/commander/order.py b/src/agent_comms/commander/order.py index 3a04251f09..b599fda2df 100644 --- a/src/agent_comms/commander/order.py +++ b/src/agent_comms/commander/order.py @@ -46,7 +46,8 @@ OrderDeletion, StrikeReason, StrikeTodo, VoidStrikeTodo, LocatedProblemFinding, UnlocatedProblemFinding, FileDeliveryEvidence, NoDeliveryEvidence, WithdrawTodo, AssignTodo, DirectiveText, OrderProgress, Todo, Slop) from . import capacity -from .config import Commander, CommanderConfig, CommanderRole, Repo +from ..repo import Repo +from .config import Commander, CommanderConfig, CommanderRole CONFIG = CommanderConfig.installed() COMMANDER = Commander.of(CONFIG) diff --git a/src/agent_comms/commander/standing.py b/src/agent_comms/commander/standing.py index ddc65d71e8..cbafede25e 100644 --- a/src/agent_comms/commander/standing.py +++ b/src/agent_comms/commander/standing.py @@ -22,7 +22,8 @@ from ..todos import FlatTrend, OrderProgress, PullRequests, StrikeTodo, Todo from ..turn_digests import TurnDigestRead, TurnsSince from . import judge -from .config import CommanderConfig, Repo +from ..repo import Repo +from .config import CommanderConfig CONFIG = CommanderConfig.installed() diff --git a/src/agent_comms/repo.py b/src/agent_comms/repo.py new file mode 100644 index 0000000000..0106938592 --- /dev/null +++ b/src/agent_comms/repo.py @@ -0,0 +1,34 @@ +"""Repository facts shared by branch readers and commander configuration.""" +from __future__ import annotations + +import subprocess +from pathlib import Path + +from pydantic import BaseModel, ConfigDict + + +class Repo(BaseModel): + """A repository the fleet works on: its check checkout, its GitHub name and its source root.""" + + model_config = ConfigDict(frozen=True) + + checkout: Path + github: str + source: str = 'src' + + def contains(self, sha: str) -> bool: + """Only a successful ancestry proof in this installed checkout permits closure.""" + result = subprocess.run( + ('git', '-C', str(self.checkout), 'merge-base', '--is-ancestor', sha, 'HEAD'), + capture_output=True, timeout=60) + if result.returncode not in (0, 1): + result.check_returncode() + return result.returncode == 0 + + @classmethod + def of(cls, checkout: Path) -> Repo: + """Its GitHub name comes from the checkout's own origin remote.""" + url = subprocess.run(['git', '-C', str(checkout), 'remote', 'get-url', 'origin'], + capture_output=True, text=True, check=True).stdout.strip() + github = url.removesuffix('.git').replace(':', '/').split('/')[-2:] + return cls(checkout=checkout.resolve(), github='/'.join(github)) diff --git a/src/agent_comms/todos.py b/src/agent_comms/todos.py index 0cb6794c90..69c178744d 100644 --- a/src/agent_comms/todos.py +++ b/src/agent_comms/todos.py @@ -45,7 +45,7 @@ from .work_branches import WorkBranchError, WorkBranches if TYPE_CHECKING: - from .commander.config import Repo + from .repo import Repo from .comms import Comms from .goal_management import Goals from .registration import Registration diff --git a/src/agent_comms/work_branches.py b/src/agent_comms/work_branches.py index c075cdd61f..7c997d1459 100644 --- a/src/agent_comms/work_branches.py +++ b/src/agent_comms/work_branches.py @@ -18,9 +18,9 @@ from pydantic.alias_generators import to_camel from .cli_protocol import WireModel -from .commander.config import Repo from .declared_family import DeclaredFamily from .field_codec import projected +from .repo import Repo if TYPE_CHECKING: from .registry_document import RegistrySnapshot diff --git a/tests/test_installed_merge.py b/tests/test_installed_merge.py index 53399af943..7c5398b091 100644 --- a/tests/test_installed_merge.py +++ b/tests/test_installed_merge.py @@ -4,7 +4,7 @@ import pytest -from agent_comms.commander.config import Repo +from agent_comms.repo import Repo from agent_comms.field_codec import FieldCodec from agent_comms.todos import (DoneTodoState, MergedProblemOrder, OperationDelivery, PullRequest, PullRequests, UnorderedProblemOrder) diff --git a/tests/test_standing_rules.py b/tests/test_standing_rules.py index 8ea363fd3a..3966ca0e2d 100644 --- a/tests/test_standing_rules.py +++ b/tests/test_standing_rules.py @@ -11,7 +11,8 @@ from agent_comms.change_report import FileLine from agent_comms.child_process import ProcessIdentity -from agent_comms.commander.config import CommanderConfig, Repo +from agent_comms.commander.config import CommanderConfig +from agent_comms.repo import Repo from agent_comms.comms import Comms from agent_comms.field_codec import FieldCodec from agent_comms.plan_phases import RecordPlanePhase, ReviewQueuePhase, UnplannedPhase diff --git a/tools/cutover/issue_rts_orders.py b/tools/cutover/issue_rts_orders.py index d7fa92b602..dca44f72d7 100644 --- a/tools/cutover/issue_rts_orders.py +++ b/tools/cutover/issue_rts_orders.py @@ -15,7 +15,7 @@ from pathlib import Path from agent_comms.change_report import FileLine -from agent_comms.commander.config import Repo +from agent_comms.repo import Repo from agent_comms.comms import Comms from agent_comms.field_codec import FieldCodec from agent_comms.goal_actions import CreateProblemGoalAction, OrderGoalAction, SetGoalAction From bb7c4836c0307390802f286e8569c38e7097684b Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 18:46:20 -0400 Subject: [PATCH 16/22] Format the moved repository declaration --- src/agent_comms/repo.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/agent_comms/repo.py b/src/agent_comms/repo.py index 0106938592..bc9090f500 100644 --- a/src/agent_comms/repo.py +++ b/src/agent_comms/repo.py @@ -1,4 +1,5 @@ """Repository facts shared by branch readers and commander configuration.""" + from __future__ import annotations import subprocess @@ -14,7 +15,7 @@ class Repo(BaseModel): checkout: Path github: str - source: str = 'src' + source: str = "src" def contains(self, sha: str) -> bool: """Only a successful ancestry proof in this installed checkout permits closure.""" @@ -28,7 +29,11 @@ def contains(self, sha: str) -> bool: @classmethod def of(cls, checkout: Path) -> Repo: """Its GitHub name comes from the checkout's own origin remote.""" - url = subprocess.run(['git', '-C', str(checkout), 'remote', 'get-url', 'origin'], - capture_output=True, text=True, check=True).stdout.strip() - github = url.removesuffix('.git').replace(':', '/').split('/')[-2:] - return cls(checkout=checkout.resolve(), github='/'.join(github)) + url = subprocess.run( + ["git", "-C", str(checkout), "remote", "get-url", "origin"], + capture_output=True, + text=True, + check=True, + ).stdout.strip() + github = url.removesuffix(".git").replace(":", "/").split("/")[-2:] + return cls(checkout=checkout.resolve(), github="/".join(github)) From ba315524b84c8b6ff37b1bc7bc0dc33918a6d267 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 18:54:02 -0400 Subject: [PATCH 17/22] Keep shared Repo imports at module scope and supply required fixture timestamps --- src/agent_comms/cli_commands.py | 19 ++++---- src/agent_comms/commander/order.py | 68 ++++++++++++++++++++++----- src/agent_comms/commander/standing.py | 2 +- tests/test_external_transcript.py | 16 +++---- 4 files changed, 74 insertions(+), 31 deletions(-) diff --git a/src/agent_comms/cli_commands.py b/src/agent_comms/cli_commands.py index 874bde65e0..01b8e81fb9 100644 --- a/src/agent_comms/cli_commands.py +++ b/src/agent_comms/cli_commands.py @@ -28,19 +28,15 @@ from .activity import ActivityState from .catalog_document import CatalogDocument from .change_report import ChangeReport -from .check_root import CheckRoot from .channel_management import KeepThreadsTagDisposition, TagChangeResult, TagDisposition from .channel_targets import ChannelSelection, CommandTarget, NoChannel, OneChannel, ResolvedChannel -from .channels import ANY_CHANNEL -from .channels import Channel +from .channels import ANY_CHANNEL, Channel +from .check_root import CheckRoot from .child_process import NoProcess, ProcessIdentity, ProcessState from .cli_bridges.claude import ClaudeTranscript from .cli_bridges.history import History, OfflineHistory, OnlineHistory from .cli_protocol import ArrivedMessage, EarlierItems from .cli_result import CliResult -from .history_result import HistoryPageResult -from .send_result import SendResult -from .maintenance_barrier import MaintenanceHeldError from .command import Command from .comms import Comms from .config_options import ( @@ -63,22 +59,26 @@ SelectableWireExportFormat, ) from .field_codec import FieldCodec, PathText, WireForm, compile_decoder -from .history_views import CallingThreadTarget, NamedThreadTarget, PollTarget -from .presentation import CountedRead, InboxRead, ThreadRecord, ThreadView, UncountedRead from .harness_install import Harness, HarnessInstalled +from .history_result import HistoryPageResult +from .history_views import CallingThreadTarget, NamedThreadTarget, PollTarget from .importing import ImportedSessionMetadata, ImportLimits, ThreadImported +from .maintenance_barrier import MaintenanceHeldError from .message_page import PageBounds from .message_reference import MessageReference -from .page_traversal import OlderTraversal, PageCursor from .messages import InfoKind, Message, MessageKind from .native_settings import NativeThreadDefaults from .native_turn_context import NativeContextData from .owner_lifecycle import OwnerRestartResult, OwnerStartResult +from .page_traversal import OlderTraversal, PageCursor from .pi_vocabulary import ServiceTier, ThinkingLevelField +from .presentation import CountedRead, InboxRead, ThreadRecord, ThreadView, UncountedRead from .read_basis import OnReadWatchAcknowledgment, WatchAcknowledgment from .registry_document import RegistrySnapshot +from .repo import Repo from .runtime import RuntimeConnection, socket_path from .schema_report import StoreSchemaReport, StoreSchemaReports +from .send_result import SendResult from .shared_ledger import LedgerDocument from .thread_execution import ( ArchiveOwnerControl, @@ -1463,7 +1463,6 @@ class InstallCliCommand(CliCommand): def apply(self, ctx: Comms) -> CliResult: from .commander.config import CommanderConfig - from .repo import Repo home = Path.home() project = self.project.resolve() diff --git a/src/agent_comms/commander/order.py b/src/agent_comms/commander/order.py index b599fda2df..17740f3622 100644 --- a/src/agent_comms/commander/order.py +++ b/src/agent_comms/commander/order.py @@ -17,36 +17,80 @@ Nothing here judges code; it refuses orders that leave a unit room to interpret, and refuses to let a deadline lapse silently. """ -import argparse, asyncio, json, os, re, subprocess, sys, time +import argparse +import asyncio +import json +import os +import re +import subprocess +import sys +import time from abc import abstractmethod from dataclasses import dataclass, replace from pathlib import Path from uuid import uuid4 from ..change_report import FileLine -from ..child_process import BoundedRun +from ..child_process import BoundedRun, CheckPrinted from ..cli_commands import DeclaredOptions, option from ..declared_family import DeclaredFamily from ..external_transcript import ExternalTranscript from ..goal_actions import CreateProblemGoalAction, OrderGoalAction -from ..child_process import CheckPrinted from ..goals import OrderCheck, ProblemGoalOrder -from ..native_tool_call import SelectedToolDenied from ..messages import Message +from ..native_tool_call import SelectedToolDenied from ..plan_phases import OrderPhase, UnplannedPhase from ..progress_claims import MergedClaim, WorkingClaim from ..recorded_time import RecordedEventTime +from ..repo import Repo from ..standing_rules import HuntDue, RulesDue +from ..todos import ( + ApplicationOrderRun, + AssignTodo, + BluntText, + CleanHunt, + CommitDelivery, + DirectiveText, + FileDeliveryEvidence, + InstallDeadline, + LocatedProblemFinding, + MechanicalOrderRun, + NoDeliveryEvidence, + NotifiedFirstCallEvent, + ObservedFirstCallEvent, + OperationDelivery, + OrderDeletion, + OrderedHunt, + OrderMessage, + OrderProgress, + OrderRun, + PendingProblemOrder, + PlannedInstallDeadline, + RecordFirstCallTodo, + Slop, + StrikeReason, + StrikeTodo, + Todo, + UnlocatedProblemFinding, + VerdictAudience, + VoidStrikeTodo, + WithdrawTodo, +) from ..tool_caller import ThreadCaller -from ..tools import (CommsFireTool, CommsSendTool, CommsOrderCheckTool, - CommsOrderServeTool, CommsOrderDoneTool, CommsOrderTool, - CommsProblemCloseTool, CommsTodoCreateTool, CommsTodoListTool, CommsTodoTool, TodoList) -from ..todos import (ApplicationOrderRun, VerdictAudience, InstallDeadline, PlannedInstallDeadline, OrderMessage, MechanicalOrderRun, OrderRun, BluntText, CommitDelivery, NotifiedFirstCallEvent, ObservedFirstCallEvent, - OperationDelivery, CleanHunt, OrderedHunt, PendingProblemOrder, RecordFirstCallTodo, - OrderDeletion, StrikeReason, StrikeTodo, VoidStrikeTodo, LocatedProblemFinding, UnlocatedProblemFinding, FileDeliveryEvidence, NoDeliveryEvidence, - WithdrawTodo, AssignTodo, DirectiveText, OrderProgress, Todo, Slop) +from ..tools import ( + CommsFireTool, + CommsOrderCheckTool, + CommsOrderDoneTool, + CommsOrderServeTool, + CommsOrderTool, + CommsProblemCloseTool, + CommsSendTool, + CommsTodoCreateTool, + CommsTodoListTool, + CommsTodoTool, + TodoList, +) from . import capacity -from ..repo import Repo from .config import Commander, CommanderConfig, CommanderRole CONFIG = CommanderConfig.installed() diff --git a/src/agent_comms/commander/standing.py b/src/agent_comms/commander/standing.py index cbafede25e..ff29c25ef1 100644 --- a/src/agent_comms/commander/standing.py +++ b/src/agent_comms/commander/standing.py @@ -12,6 +12,7 @@ from ..comms import Comms from ..goal_actions import OrderGoalAction from ..goals import Goal +from ..repo import Repo from ..standing_rules import ( DatedStandingRule, ReasonedStandingRule, @@ -22,7 +23,6 @@ from ..todos import FlatTrend, OrderProgress, PullRequests, StrikeTodo, Todo from ..turn_digests import TurnDigestRead, TurnsSince from . import judge -from ..repo import Repo from .config import CommanderConfig CONFIG = CommanderConfig.installed() diff --git a/tests/test_external_transcript.py b/tests/test_external_transcript.py index 56c644fc3c..fb968a3fc6 100644 --- a/tests/test_external_transcript.py +++ b/tests/test_external_transcript.py @@ -52,7 +52,7 @@ def test_page_and_draft_hold_one_external_read_transaction(tmp_path, monkeypatch comms.registry.declare(comms.registry.new_thread('codex', frozenset(), str(tmp_path))) transcript = comms.transcripts.external("codex") transcript.draft( - turn_id="turn", item_id="reply", events=(AssistantTranscript("Writing", position='reply'),) + turn_id="turn", item_id="reply", events=(AssistantTranscript("Writing", timestamp=1.0, position='reply'),) ) read = comms.transcripts.capture_page_read("codex") reading_draft = threading.Event() @@ -73,7 +73,7 @@ def pause_draft(source, root, incarnation): transcript.finish, turn_id="turn", item_id="reply", - events=(AssistantTranscript("Finished", position='reply'),), + events=(AssistantTranscript("Finished", timestamp=1.0, position='reply'),), ) finish.result(timeout=5) release.set() @@ -105,10 +105,10 @@ def test_captured_draft_cannot_delete_a_later_turns_draft(tmp_path): comms = wire(tmp_path / "wire") comms.registry.declare(comms.registry.new_thread("cli", frozenset(), str(tmp_path))) transcript = comms.transcripts.external("cli") - transcript.draft(turn_id="old", item_id="draft", events=(AssistantTranscript("old", position='draft'),)) + transcript.draft(turn_id="old", item_id="draft", events=(AssistantTranscript("old", timestamp=1.0, position='draft'),)) with transcript.reading() as records: old = records.current_draft(transcript.root, transcript.incarnation) - transcript.draft(turn_id="new", item_id="draft", events=(AssistantTranscript("new", position='draft'),)) + transcript.draft(turn_id="new", item_id="draft", events=(AssistantTranscript("new", timestamp=1.0, position='draft'),)) old.end_turn(transcript) with transcript.reading() as records: assert records.current_draft(transcript.root, transcript.incarnation).turn_id == "new" @@ -127,11 +127,11 @@ def test_draft_is_current_state_and_finishes_atomically(tmp_path): comms = wire(tmp_path / "wire") comms.registry.declare(comms.registry.new_thread('codex', frozenset(), str(tmp_path))) transcript = comms.transcripts.external("codex") - transcript.draft(turn_id="t", item_id="a", events=(AssistantTranscript("Hel", position='a'),)) + transcript.draft(turn_id="t", item_id="a", events=(AssistantTranscript("Hel", timestamp=1.0, position='a'),)) with transcript.reading() as records: assert records.current_draft(transcript.root, transcript.incarnation).events[0].text == "Hel" assert records.cut().offset == 0 - transcript.draft(turn_id="t", item_id="a", events=(AssistantTranscript("Hello", position='a'),)) + transcript.draft(turn_id="t", item_id="a", events=(AssistantTranscript("Hello", timestamp=1.0, position='a'),)) transcript.finish(turn_id="t", item_id="other", events=(NoticeTranscript("Tool", position='other'),)) with transcript.reading() as records: assert records.current_draft(transcript.root, transcript.incarnation).item_id == "a" @@ -140,13 +140,13 @@ def test_draft_is_current_state_and_finishes_atomically(tmp_path): assert snapshot.draft.events[0].text == "Hello" assert [e.text for e in snapshot.page.events] == ["Tool"] assert decode_updates(encode_updates(snapshot)) == (snapshot,) - transcript.finish(turn_id="t", item_id="a", events=(AssistantTranscript("Hello!", position='a'),)) + transcript.finish(turn_id="t", item_id="a", events=(AssistantTranscript("Hello!", timestamp=1.0, position='a'),)) with pytest.raises(StaleRevision): read.current_draft() with transcript.reading() as records: assert records.current_draft(transcript.root, transcript.incarnation).external_row() is None assert [e.text for e in comms.transcripts.thread_transcript_page("codex").events] == ["Tool", "Hello!"] - transcript.draft(turn_id="t2", item_id="b", events=(AssistantTranscript("Interrupted", position='b'),)) + transcript.draft(turn_id="t2", item_id="b", events=(AssistantTranscript("Interrupted", timestamp=1.0, position='b'),)) transcript.end_turn() with transcript.reading() as records: assert records.current_draft(transcript.root, transcript.incarnation).external_row() is None From 58fe9808f8b7aca1b74e17e4ea72e851143cacac Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 18:54:56 -0400 Subject: [PATCH 18/22] Limit Repo migration to necessary import moves, without unrelated reformatting --- src/agent_comms/cli_commands.py | 17 ++++---- src/agent_comms/commander/order.py | 68 ++++++------------------------ 2 files changed, 21 insertions(+), 64 deletions(-) diff --git a/src/agent_comms/cli_commands.py b/src/agent_comms/cli_commands.py index 01b8e81fb9..69a3808d4f 100644 --- a/src/agent_comms/cli_commands.py +++ b/src/agent_comms/cli_commands.py @@ -28,15 +28,19 @@ from .activity import ActivityState from .catalog_document import CatalogDocument from .change_report import ChangeReport +from .check_root import CheckRoot from .channel_management import KeepThreadsTagDisposition, TagChangeResult, TagDisposition from .channel_targets import ChannelSelection, CommandTarget, NoChannel, OneChannel, ResolvedChannel -from .channels import ANY_CHANNEL, Channel -from .check_root import CheckRoot +from .channels import ANY_CHANNEL +from .channels import Channel from .child_process import NoProcess, ProcessIdentity, ProcessState from .cli_bridges.claude import ClaudeTranscript from .cli_bridges.history import History, OfflineHistory, OnlineHistory from .cli_protocol import ArrivedMessage, EarlierItems from .cli_result import CliResult +from .history_result import HistoryPageResult +from .send_result import SendResult +from .maintenance_barrier import MaintenanceHeldError from .command import Command from .comms import Comms from .config_options import ( @@ -59,26 +63,23 @@ SelectableWireExportFormat, ) from .field_codec import FieldCodec, PathText, WireForm, compile_decoder -from .harness_install import Harness, HarnessInstalled -from .history_result import HistoryPageResult from .history_views import CallingThreadTarget, NamedThreadTarget, PollTarget +from .presentation import CountedRead, InboxRead, ThreadRecord, ThreadView, UncountedRead +from .harness_install import Harness, HarnessInstalled from .importing import ImportedSessionMetadata, ImportLimits, ThreadImported -from .maintenance_barrier import MaintenanceHeldError from .message_page import PageBounds from .message_reference import MessageReference +from .page_traversal import OlderTraversal, PageCursor from .messages import InfoKind, Message, MessageKind from .native_settings import NativeThreadDefaults from .native_turn_context import NativeContextData from .owner_lifecycle import OwnerRestartResult, OwnerStartResult -from .page_traversal import OlderTraversal, PageCursor from .pi_vocabulary import ServiceTier, ThinkingLevelField -from .presentation import CountedRead, InboxRead, ThreadRecord, ThreadView, UncountedRead from .read_basis import OnReadWatchAcknowledgment, WatchAcknowledgment from .registry_document import RegistrySnapshot from .repo import Repo from .runtime import RuntimeConnection, socket_path from .schema_report import StoreSchemaReport, StoreSchemaReports -from .send_result import SendResult from .shared_ledger import LedgerDocument from .thread_execution import ( ArchiveOwnerControl, diff --git a/src/agent_comms/commander/order.py b/src/agent_comms/commander/order.py index 17740f3622..b599fda2df 100644 --- a/src/agent_comms/commander/order.py +++ b/src/agent_comms/commander/order.py @@ -17,80 +17,36 @@ Nothing here judges code; it refuses orders that leave a unit room to interpret, and refuses to let a deadline lapse silently. """ -import argparse -import asyncio -import json -import os -import re -import subprocess -import sys -import time +import argparse, asyncio, json, os, re, subprocess, sys, time from abc import abstractmethod from dataclasses import dataclass, replace from pathlib import Path from uuid import uuid4 from ..change_report import FileLine -from ..child_process import BoundedRun, CheckPrinted +from ..child_process import BoundedRun from ..cli_commands import DeclaredOptions, option from ..declared_family import DeclaredFamily from ..external_transcript import ExternalTranscript from ..goal_actions import CreateProblemGoalAction, OrderGoalAction +from ..child_process import CheckPrinted from ..goals import OrderCheck, ProblemGoalOrder -from ..messages import Message from ..native_tool_call import SelectedToolDenied +from ..messages import Message from ..plan_phases import OrderPhase, UnplannedPhase from ..progress_claims import MergedClaim, WorkingClaim from ..recorded_time import RecordedEventTime -from ..repo import Repo from ..standing_rules import HuntDue, RulesDue -from ..todos import ( - ApplicationOrderRun, - AssignTodo, - BluntText, - CleanHunt, - CommitDelivery, - DirectiveText, - FileDeliveryEvidence, - InstallDeadline, - LocatedProblemFinding, - MechanicalOrderRun, - NoDeliveryEvidence, - NotifiedFirstCallEvent, - ObservedFirstCallEvent, - OperationDelivery, - OrderDeletion, - OrderedHunt, - OrderMessage, - OrderProgress, - OrderRun, - PendingProblemOrder, - PlannedInstallDeadline, - RecordFirstCallTodo, - Slop, - StrikeReason, - StrikeTodo, - Todo, - UnlocatedProblemFinding, - VerdictAudience, - VoidStrikeTodo, - WithdrawTodo, -) from ..tool_caller import ThreadCaller -from ..tools import ( - CommsFireTool, - CommsOrderCheckTool, - CommsOrderDoneTool, - CommsOrderServeTool, - CommsOrderTool, - CommsProblemCloseTool, - CommsSendTool, - CommsTodoCreateTool, - CommsTodoListTool, - CommsTodoTool, - TodoList, -) +from ..tools import (CommsFireTool, CommsSendTool, CommsOrderCheckTool, + CommsOrderServeTool, CommsOrderDoneTool, CommsOrderTool, + CommsProblemCloseTool, CommsTodoCreateTool, CommsTodoListTool, CommsTodoTool, TodoList) +from ..todos import (ApplicationOrderRun, VerdictAudience, InstallDeadline, PlannedInstallDeadline, OrderMessage, MechanicalOrderRun, OrderRun, BluntText, CommitDelivery, NotifiedFirstCallEvent, ObservedFirstCallEvent, + OperationDelivery, CleanHunt, OrderedHunt, PendingProblemOrder, RecordFirstCallTodo, + OrderDeletion, StrikeReason, StrikeTodo, VoidStrikeTodo, LocatedProblemFinding, UnlocatedProblemFinding, FileDeliveryEvidence, NoDeliveryEvidence, + WithdrawTodo, AssignTodo, DirectiveText, OrderProgress, Todo, Slop) from . import capacity +from ..repo import Repo from .config import Commander, CommanderConfig, CommanderRole CONFIG = CommanderConfig.installed() From 9680beaf8aefd66dd86b1763ae82620696703619 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 18:58:19 -0400 Subject: [PATCH 19/22] Keep the landed ancestry method with the relocated Repo owner --- src/agent_comms/repo.py | 6 ++++-- src/agent_comms/todos.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/agent_comms/repo.py b/src/agent_comms/repo.py index bc9090f500..b79abe7083 100644 --- a/src/agent_comms/repo.py +++ b/src/agent_comms/repo.py @@ -20,8 +20,10 @@ class Repo(BaseModel): def contains(self, sha: str) -> bool: """Only a successful ancestry proof in this installed checkout permits closure.""" result = subprocess.run( - ('git', '-C', str(self.checkout), 'merge-base', '--is-ancestor', sha, 'HEAD'), - capture_output=True, timeout=60) + ("git", "-C", str(self.checkout), "merge-base", "--is-ancestor", sha, "HEAD"), + capture_output=True, + timeout=60, + ) if result.returncode not in (0, 1): result.check_returncode() return result.returncode == 0 diff --git a/src/agent_comms/todos.py b/src/agent_comms/todos.py index 69c178744d..2fc680949d 100644 --- a/src/agent_comms/todos.py +++ b/src/agent_comms/todos.py @@ -45,10 +45,10 @@ from .work_branches import WorkBranchError, WorkBranches if TYPE_CHECKING: - from .repo import Repo from .comms import Comms from .goal_management import Goals from .registration import Registration + from .repo import Repo @dataclass(frozen=True) From a3b79fa303f576d75360405ddc27ce14180100f0 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 18:59:51 -0400 Subject: [PATCH 20/22] Migrate merged-in tests to typed PR facts and required event timestamps --- tests/test_external_transcript.py | 2 +- tests/test_installed_merge.py | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/test_external_transcript.py b/tests/test_external_transcript.py index fb968a3fc6..52c990c51c 100644 --- a/tests/test_external_transcript.py +++ b/tests/test_external_transcript.py @@ -132,7 +132,7 @@ def test_draft_is_current_state_and_finishes_atomically(tmp_path): assert records.current_draft(transcript.root, transcript.incarnation).events[0].text == "Hel" assert records.cut().offset == 0 transcript.draft(turn_id="t", item_id="a", events=(AssistantTranscript("Hello", timestamp=1.0, position='a'),)) - transcript.finish(turn_id="t", item_id="other", events=(NoticeTranscript("Tool", position='other'),)) + transcript.finish(turn_id="t", item_id="other", events=(NoticeTranscript("Tool", timestamp=1.0, position='other'),)) with transcript.reading() as records: assert records.current_draft(transcript.root, transcript.incarnation).item_id == "a" read = comms.transcripts.capture_page_read("codex") diff --git a/tests/test_installed_merge.py b/tests/test_installed_merge.py index 7c5398b091..e6b21fd9fb 100644 --- a/tests/test_installed_merge.py +++ b/tests/test_installed_merge.py @@ -7,7 +7,7 @@ from agent_comms.repo import Repo from agent_comms.field_codec import FieldCodec from agent_comms.todos import (DoneTodoState, MergedProblemOrder, OperationDelivery, - PullRequest, PullRequests, UnorderedProblemOrder) + MergedPullRequest, PullRequests, UnorderedProblemOrder) from agent_comms.tools import CommsOrderDoneTool from test_commander_problem_orders import working from test_order_progress import checkout, commit, deadline_lines, git @@ -39,7 +39,8 @@ def test_installed_merge_closes_pending_or_delivered_order_once( repo = Repo(checkout=installed, github=todo.repo) config = script.CONFIG.model_copy(update={'repos': (repo,)}) monkeypatch.setattr(script, 'CONFIG', config) - merge = PullRequest(1, 'fix/p1', sha, sha, 500, 500, (original.id,)) + 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: (merge,) if state == 'merged' else ()) @@ -93,7 +94,8 @@ def test_new_revision_is_not_closed_from_an_old_merge_observation( script, installed = commander_script todo, _ = checkout repo = Repo(checkout=installed, github=todo.repo) - merge = PullRequest(1, 'fix/p1', 'a' * 40, 'b' * 40, 500, 500, (todo.order.id,)) + merge = MergedPullRequest(1, 'fix/p1', 'a' * 40, merge='b' * 40, merged_at=500, + updated_at=500, orders=(todo.order.id,)) monkeypatch.setattr(PullRequests, 'read', lambda self, repo, state, order: (merge,)) newer = replace(todo, revision=todo.revision + 1, order=replace(todo.order, id='replacement-order')) From 0231868bb28e5d4d1c735aba5d0851c4cfc0f4b4 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 19:52:24 -0400 Subject: [PATCH 21/22] Call main's problem tools in the P254 proofs Co-Authored-By: Claude Opus 5.5 --- tools/proofs/cn_phase3_record.py | 12 ++++++------ tools/proofs/cn_progress_read_only.py | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tools/proofs/cn_phase3_record.py b/tools/proofs/cn_phase3_record.py index aba8a87702..cc0a0fff73 100644 --- a/tools/proofs/cn_phase3_record.py +++ b/tools/proofs/cn_phase3_record.py @@ -38,7 +38,7 @@ CommsFireTool, CommsOrderDoneTool, CommsTodoCreateTool, - CommsTodoTool, + CommsChangeProblemTool, CommsVerdictTool, ToolRequest, ) @@ -158,7 +158,7 @@ def record(session: Path) -> None: cli( "record-lead", CommsVerdictTool( - id="P1", + problem="P1", operation=AcceptedVerdict( todo.revision, comms.registry.require("record-lead").incarnation, @@ -175,8 +175,8 @@ def record(session: Path) -> None: todo = comms.goals.todos.get("P1") cli( "record-lead", - CommsTodoTool( - id="P1", + CommsChangeProblemTool( + problem="P1", operation=StrikeTodo( todo.revision, comms.registry.require("record-lead").incarnation, @@ -199,8 +199,8 @@ def record(session: Path) -> None: todo = comms.goals.todos.get("P1") cli( "record-lead", - CommsTodoTool( - id="P1", + CommsChangeProblemTool( + problem="P1", operation=VoidStrikeTodo( todo.revision, comms.registry.require("record-lead").incarnation, diff --git a/tools/proofs/cn_progress_read_only.py b/tools/proofs/cn_progress_read_only.py index 9c7174c10c..3849c5db88 100644 --- a/tools/proofs/cn_progress_read_only.py +++ b/tools/proofs/cn_progress_read_only.py @@ -10,7 +10,7 @@ from agent_comms.field_codec import FieldCodec from agent_comms.goals import CheckedCommit, CountCheckResult, OrderCheck from agent_comms.todos import CorrectCheckTodo, DistanceReadings, PushedProgress, Todo -from agent_comms.tools import CommsTodoListTool, CommsTodoTool, TodoList +from agent_comms.tools import CommsTodoListTool, CommsChangeProblemTool, TodoList MARKER = ROOT / "explicit-check-count" MARKER_CONTENT = b"x" @@ -25,8 +25,8 @@ def prepare() -> None: ) cli( "record-lead", - CommsTodoTool( - id="P6", + CommsChangeProblemTool( + problem="P6", operation=CorrectCheckTodo( todo.revision, comms.registry.require("record-lead").incarnation, From f07823dce566037bb2f398390703fc19b317c8da Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 19 Oct 2026 20:03:49 -0400 Subject: [PATCH 22/22] Sort the P254 proof imports Co-Authored-By: Claude Opus 5.5 --- tools/proofs/cn_phase3_record.py | 2 +- tools/proofs/cn_progress_read_only.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/proofs/cn_phase3_record.py b/tools/proofs/cn_phase3_record.py index cc0a0fff73..d925660ffd 100644 --- a/tools/proofs/cn_phase3_record.py +++ b/tools/proofs/cn_phase3_record.py @@ -35,10 +35,10 @@ ) from agent_comms.tool_caller import ToolCaller from agent_comms.tools import ( + CommsChangeProblemTool, CommsFireTool, CommsOrderDoneTool, CommsTodoCreateTool, - CommsChangeProblemTool, CommsVerdictTool, ToolRequest, ) diff --git a/tools/proofs/cn_progress_read_only.py b/tools/proofs/cn_progress_read_only.py index 3849c5db88..5065895634 100644 --- a/tools/proofs/cn_progress_read_only.py +++ b/tools/proofs/cn_progress_read_only.py @@ -10,7 +10,7 @@ from agent_comms.field_codec import FieldCodec from agent_comms.goals import CheckedCommit, CountCheckResult, OrderCheck from agent_comms.todos import CorrectCheckTodo, DistanceReadings, PushedProgress, Todo -from agent_comms.tools import CommsTodoListTool, CommsChangeProblemTool, TodoList +from agent_comms.tools import CommsChangeProblemTool, CommsTodoListTool, TodoList MARKER = ROOT / "explicit-check-count" MARKER_CONTENT = b"x"