diff --git a/docs/evidence/cn-phase3-record-results.jsonl b/docs/evidence/cn-phase3-record-results.jsonl new file mode 100644 index 0000000000..5bcf1fcd3d --- /dev/null +++ b/docs/evidence/cn-phase3-record-results.jsonl @@ -0,0 +1,19 @@ +{"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"} +{"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/docs/evidence/cn-phase3-record.md b/docs/evidence/cn-phase3-record.md new file mode 100644 index 0000000000..bdab4af770 --- /dev/null +++ b/docs/evidence/cn-phase3-record.md @@ -0,0 +1,174 @@ +# 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. +- [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. +- [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. +- [ ] 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 + +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. `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. 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 +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; +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 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` (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, +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. +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 + +- 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; + 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. +- 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. +- 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 + 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. 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 +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. + +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/cli_commands.py b/src/agent_comms/cli_commands.py index c69ae0b9d8..69a3808d4f 100644 --- a/src/agent_comms/cli_commands.py +++ b/src/agent_comms/cli_commands.py @@ -77,6 +77,7 @@ from .pi_vocabulary import ServiceTier, ThinkingLevelField 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 .shared_ledger import LedgerDocument @@ -1462,7 +1463,7 @@ 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 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 4e13531a9c..d779293ab8 100644 --- a/src/agent_comms/commander/order.py +++ b/src/agent_comms/commander/order.py @@ -47,7 +47,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 6f56bb955d..d8c53d3ac8 100644 --- a/src/agent_comms/commander/standing.py +++ b/src/agent_comms/commander/standing.py @@ -16,6 +16,7 @@ from ..coordinator import Coordination from ..goal_actions import OrderGoalAction from ..goals import Goal +from ..repo import Repo from ..standing_rules import ( DatedStandingRule, ReasonedStandingRule, @@ -29,7 +30,7 @@ ) from ..tools import CommsUnjudgedTool from ..turn_digests import TurnDigestRead, TurnsSince -from .config import CommanderConfig, Repo +from .config import CommanderConfig @dataclass(frozen=True) diff --git a/src/agent_comms/external_transcript.py b/src/agent_comms/external_transcript.py index d254b81736..0c9d70fd36 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 @@ -263,8 +263,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, ...] @@ -471,8 +471,13 @@ 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): + 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 85653a9dcc..fbfc145f31 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 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``: the result a git note on the commit records, or one run in - a throwaway detached checkout and recorded there for every later reader.""" + """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 checked in CheckedCommit.stored(repository, sha): - if checked.command == self.command: - return checked.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/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/repo.py b/src/agent_comms/repo.py new file mode 100644 index 0000000000..b79abe7083 --- /dev/null +++ b/src/agent_comms/repo.py @@ -0,0 +1,41 @@ +"""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 7ac53b9284..da922b028a 100644 --- a/src/agent_comms/todos.py +++ b/src/agent_comms/todos.py @@ -32,10 +32,9 @@ 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 +from .goals import CheckedCommit, CountCheckResult, OrderCheck, ProblemGoalOrder from .recorded_time import EventTime, RecordedEventTime, UnrecordedEventTime from .thread_identity import RecordedThreadIncarnation, ThreadIncarnation from .threads import Thread @@ -43,11 +42,13 @@ from .standing_rules import UndatedStandingRule from .storage_rules import StorageRule from .plan_phases import OrderPhase, UnplannedPhase +from .work_branches import WorkBranchError, WorkBranches if TYPE_CHECKING: - from .commander.config import Repo from .comms import Comms from .goal_management import Goals + from .registration import Registration + from .repo import Repo @dataclass(frozen=True) @@ -81,15 +82,20 @@ 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): 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,9 +182,101 @@ 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: + 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) + + 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.checked().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: + 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 + 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 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 @@ -190,48 +288,106 @@ 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) - @classmethod - @boundary_decoder - def listed(cls, number: int, branch: str, head: str, merge: str, merged: str, - updated: str, body: 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)) + 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")) +@dataclass(frozen=True) +class PullRequestCommit: + """GitHub's mergeCommit object on a merged request.""" + oid: str + + +@dataclass(frozen=True) +class ListedPullRequest(DeclaredFamily, affix="ListedPullRequest"): + """The external query row; conversion lives here, never in its consumers.""" + number: int + branch: str + head: str + updated: str + body: str + base: str + + @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: """Pull requests queried by immutable order identity, cached within one board read.""" 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 self._read[key] + 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(): @@ -239,27 +395,41 @@ 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 | 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 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], ...]], + 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(): - self._read[repo, state, candidate.id] = tuple( - PullRequest.listed(*row) for row in rows[alias]) - return self._read[repo, state, order.id] + self._read[repo, state, candidate.id] = tuple(row.parsed() for row in rows[alias]) + return self._read[repo, state, checked.id] 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 @@ -272,6 +442,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: @@ -282,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 @@ -350,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: ... @@ -362,7 +533,7 @@ def pushes(self, since: float) -> Iterator[tuple[str, float]]: ... 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: @@ -374,30 +545,35 @@ def pushes(self, since: float) -> Iterator[tuple[str, float]]: 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) def pushes(self, since: float) -> Iterator[tuple[str, float]]: """A push-ref update has its own time, not the commit's author time.""" @@ -426,14 +602,14 @@ def of(cls, todo: Todo, storage: StorageRule, pulls: PullRequests) -> Self: @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.""" 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]]: + def commits(self, *excluded: str) -> Iterator[ProgressPoint]: return self.checkout.commits(self.since, *excluded) @cached_property @@ -442,21 +618,13 @@ def pushes(self) -> tuple[PushedProgress, ...]: @cached_property def moved_at(self) -> float: - return max((self.since, *(at for _, at in self.commits()), + return max((self.since, *(commit.at for commit in self.commits()), *(push.at for push in self.pushes))) def readings(self) -> DistanceReadings: """Progress observation does not run the order's Check.""" return DistanceReadings() - def reading(self, checkout: PresentCheckout, base: str, sha: str, at: float) -> DistanceReading: - """Keep the check command with its measured or missing distance.""" - 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) - class MeasuredProgressSource(ProgressSource): """The board's distance view runs the Check; the stall watcher only reads activity.""" @@ -482,7 +650,7 @@ def from_source(cls, source: ProgressSource) -> OrderProgress: try: return next((progress for member in members for progress in member.observations(source)), NoCommitProgress()) - except subprocess.TimeoutExpired as unread: + except (WorkBranchError, subprocess.TimeoutExpired) as unread: return UnreadProgress(str(unread)) @property @@ -605,8 +773,8 @@ class CommittedProgress(GitProgress): @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: @@ -621,9 +789,9 @@ class PushedProgress(CommittedProgress): @classmethod def found(cls, source: ProgressSource) -> Iterator[Self]: moves = (*source.commits("origin/HEAD"), - *((push.sha, push.at) for push in source.pushes)) - return (cls(sha[:9], at=at, readings=source.readings()) - for sha, at in sorted(moves, key=lambda move: move[1], reverse=True)[:1]) + *(ProgressPoint(push.sha, push.at) for push in source.pushes)) + return (cls(move.sha[:9], at=move.at, readings=source.readings()) + for move in sorted(moves, key=lambda move: move.at, reverse=True)[:1]) @property def state(self) -> str: @@ -657,8 +825,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: @@ -1193,6 +1361,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.") @@ -1254,6 +1425,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 @@ -1428,7 +1602,10 @@ 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.line}, waiting on install" def require_transfer(self, todo: Todo) -> None: owner = (todo.assignment or todo.released[-1].previous).owner @@ -2028,7 +2205,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): diff --git a/src/agent_comms/transcript_sources.py b/src/agent_comms/transcript_sources.py index e6a7600250..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 @@ -296,6 +296,14 @@ class NoDraft(TranscriptDraft): pass +class TranscriptRecord: + """A source position; only native records can project a decoded native entry.""" + + def project[Result](self, projection: Callable[[NativeRecord], Result]) -> Result | tuple[()]: + """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/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_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" diff --git a/tests/test_commander_problem_orders.py b/tests/test_commander_problem_orders.py index 7129b5f60d..011dc160e1 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) == ( @@ -1463,7 +1464,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..6bb69fc69c --- /dev/null +++ b/tests/test_delivery_merge.py @@ -0,0 +1,42 @@ +"""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, MergedListedPullRequest, MergedPullRequest, OrderDelivery, PullRequestCommit, + UnmergedDeliveryMerge, UnmergedListedPullRequest, UnmergedPullRequest, +) + + +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(): + 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' + 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(): + 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)) == () + assert FieldCodec.decode(ListedPullRequest, FieldCodec.encode(row)) == row diff --git a/tests/test_external_transcript.py b/tests/test_external_transcript.py index 56c644fc3c..52c990c51c 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,12 +127,12 @@ 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.finish(turn_id="t", item_id="other", events=(NoticeTranscript("Tool", position='other'),)) + 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", 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") @@ -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 diff --git a/tests/test_installed_merge.py b/tests/test_installed_merge.py index 53399af943..e6b21fd9fb 100644 --- a/tests/test_installed_merge.py +++ b/tests/test_installed_merge.py @@ -4,10 +4,10 @@ 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) + 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')) diff --git a/tests/test_order_progress.py b/tests/test_order_progress.py index 96cbed66d2..41bcef4074 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,13 +9,17 @@ 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, - PullRequest, PullRequests, PushedProgress, UnreadProgress) + MergedPullRequest, PresentCheckout, ProgressPoint, ProgressSource, PullRequest, PullRequests, + PushedProgress, UnmergedPullRequest, 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 @@ -67,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 @@ -80,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'") @@ -109,7 +115,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) @@ -119,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) @@ -136,38 +142,63 @@ 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' 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_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.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 == 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' / '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() + + 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. @@ -176,7 +207,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() @@ -201,15 +232,16 @@ 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): 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: @@ -319,7 +351,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) @@ -361,8 +393,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 = 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: (foreign, merged) if state == "merged" else ()) diff --git a/tests/test_standing_rules.py b/tests/test_standing_rules.py index 8889d9c55e..36e0e07eb5 100644 --- a/tests/test_standing_rules.py +++ b/tests/test_standing_rules.py @@ -12,7 +12,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 @@ -24,7 +25,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, CommsChangeProblemTool @@ -56,13 +58,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")) - opened = PullRequest.listed(1624, "ratchet/none-absence", "c" * 40, "", "", - stamp, pending.order.pull_request_body("Declared measure")) - other = PullRequest.listed(1629, "ratchet/decoder-marker", "e" * 40, "f" * 40, - stamp, stamp, "Agent-Comms-Order: another-order\n") + merged = MergedPullRequest(1581, "docs/module-layers", "b" * 40, merge="d" * 40, + merged_at=500, updated_at=500, orders=(owned.order.id,)) + opened = UnmergedPullRequest(1624, "ratchet/none-absence", "c" * 40, + updated_at=500, orders=(pending.order.id,)) + 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): return (merged, other) if state == "merged" else (opened,) @@ -190,20 +191,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) 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 diff --git a/tools/proofs/cn_phase3_record.py b/tools/proofs/cn_phase3_record.py new file mode 100644 index 0000000000..d925660ffd --- /dev/null +++ b/tools/proofs/cn_phase3_record.py @@ -0,0 +1,252 @@ +"""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 +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 +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.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 ( + CommsChangeProblemTool, + CommsFireTool, + CommsOrderDoneTool, + CommsTodoCreateTool, + 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" +RESULTS = ROOT.parent / "cn-phase3-record-results.jsonl" + + +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")}, + ) + print(result.stdout, end="", flush=True) + if result.returncode: + raise RuntimeError(result.stderr or result.stdout) + with RESULTS.open("a") as output: + output.write(result.stdout) + return result.stdout + + +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", + ), + ) + create_order() + + +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) -> 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) + ) + todo = comms.goals.todos.get("P1") + 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( + problem="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", + CommsChangeProblemTool( + problem="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", + CommsChangeProblemTool( + problem="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 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: + create_order(problem) + 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() + 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", 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 new file mode 100644 index 0000000000..5065895634 --- /dev/null +++ b/tools/proofs/cn_progress_read_only.py @@ -0,0 +1,89 @@ +"""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, DistanceReadings, PushedProgress, Todo +from agent_comms.tools import CommsChangeProblemTool, CommsTodoListTool, TodoList + +MARKER = ROOT / "explicit-check-count" +MARKER_CONTENT = b"x" + + +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", + CommsChangeProblemTool( + problem="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(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() -> None: + comms = Comms(ROOT) + 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, + ) + try: + assert not board(todo).readings + assert not MARKER.exists() + 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(todo).readings) == 1 + assert MARKER.read_bytes() == MARKER_CONTENT + 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 + ) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + 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() + args.action()