From 09d630df2bc6bc41057f095d9731b3770f405f90 Mon Sep 17 00:00:00 2001 From: ogt Date: Sat, 11 Jul 2026 17:58:56 +0800 Subject: [PATCH] fix(agent): keep completed milestones monotonic --- .../awoooi_priority_work_order_readback.py | 164 ++++++++++++++++++ ...awoooi_priority_work_order_readback_api.py | 36 ++++ 2 files changed, 200 insertions(+) diff --git a/apps/api/src/services/awoooi_priority_work_order_readback.py b/apps/api/src/services/awoooi_priority_work_order_readback.py index e3c9d82ce..e28bf3094 100644 --- a/apps/api/src/services/awoooi_priority_work_order_readback.py +++ b/apps/api/src/services/awoooi_priority_work_order_readback.py @@ -271,6 +271,14 @@ _AI_AUTOMATION_PROGRAM_DEPENDENCIES = { "AIA-P0-010": ["AIA-P0-006", "AIA-P0-011"], "AIA-P0-007": ["AIA-P0-002", "AIA-P0-003", "AIA-P0-004", "AIA-P0-005", "AIA-P0-006", "AIA-P0-008"], } +_AI_AUTOMATION_MONOTONIC_CLOSURE_ORDER = ( + "AIA-P0-001", + "AIA-P0-011", + "AIA-P0-002", + "AIA-P0-003", + "AIA-P0-004", + "AIA-P0-005", +) _AI_SINGLE_WRITER_REQUIRED_CONTROL_IDS = ( "strict_runtime_same_run_closed", "runtime_contract_compatible_with_deployed_source_boundary", @@ -7300,6 +7308,59 @@ def _align_authoritative_current_p0_execution_focus( ) +def _apply_monotonic_program_closure_floor( + work_items: list[dict[str, Any]], + closure_proofs: list[dict[str, Any]], +) -> None: + deepest_closed_index = -1 + for index, proof in enumerate(closure_proofs): + if proof.get("closed") is True: + deepest_closed_index = index + + if deepest_closed_index < 0: + return + + successor = closure_proofs[deepest_closed_index] + successor_work_item_id = str(successor.get("work_item_id") or "") + successor_run_id = str(successor.get("automation_run_id") or "") + evidence_refs = [ + ref.strip() + for ref in _list(successor.get("evidence_refs")) + if isinstance(ref, str) and ref.strip() + ] + if successor_run_id: + evidence_refs.insert(0, f"automation-run:{successor_run_id}") + evidence_refs = list(dict.fromkeys(ref for ref in evidence_refs if ref)) + if not successor_work_item_id or not evidence_refs: + return + + items_by_id = {str(item.get("id") or ""): item for item in work_items} + for work_item_id in _AI_AUTOMATION_MONOTONIC_CLOSURE_ORDER[ + : deepest_closed_index + 1 + ]: + item = items_by_id[work_item_id] + if item.get("status") == "done": + continue + runtime_progress = _dict(item.get("runtime_progress")) + runtime_progress["latest_runtime_closed"] = ( + runtime_progress.get("runtime_closed") is True + ) + runtime_progress["monotonic_program_closed"] = True + runtime_progress["monotonic_successor_work_item_id"] = ( + successor_work_item_id + ) + item["runtime_progress"] = runtime_progress + item["status"] = "done" + item["completion_evidence"] = { + "source": "agent-autonomous-runtime-control", + "proof_kind": "monotonic_successor_runtime_closure", + "successor_work_item_id": successor_work_item_id, + "successor_automation_run_id": successor_run_id, + "latest_runtime_state_preserved": True, + } + item["runtime_evidence_refs"] = evidence_refs + + def _apply_ai_automation_program_ledger(payload: dict[str, Any]) -> None: summary = _dict(payload.setdefault("summary", {})) rollups = _dict(payload.setdefault("rollups", {})) @@ -7929,6 +7990,109 @@ def _apply_ai_automation_program_ledger(payload: dict[str, Any]) -> None: or canonical_learning_work_item["status"] == "done" ): retry_rollback_work_item["status"] = "in_progress" + _apply_monotonic_program_closure_floor( + work_items, + [ + { + "work_item_id": "AIA-P0-001", + "closed": strict_runtime_closed, + "automation_run_id": summary.get( + "ai_agent_autonomous_runtime_automation_run_id" + ), + "evidence_refs": [ + "agent-autonomous-runtime-control:strict_runtime_completion" + ], + }, + { + "work_item_id": "AIA-P0-011", + "closed": trust_boundary_runtime_closed, + "automation_run_id": summary.get( + "ai_agent_autonomous_runtime_automation_run_id" + ), + "evidence_refs": [ + summary.get("ai_agent_executor_trust_boundary_receipt_ref") + ], + }, + { + "work_item_id": "AIA-P0-002", + "closed": ( + single_writer_closed + and all(single_writer_controls.values()) + and not list( + summary.get("ai_agent_single_writer_active_blockers") + or [] + ) + ), + "automation_run_id": summary.get( + "ai_agent_single_writer_automation_run_id" + ), + "evidence_refs": [ + summary.get("ai_agent_single_writer_source_receipt_ref") + ], + }, + { + "work_item_id": "AIA-P0-003", + "closed": ( + post_verifier_closed + and all(post_verifier_controls.values()) + and not list( + summary.get( + "ai_agent_independent_post_verifier_active_blockers" + ) + or [] + ) + ), + "automation_run_id": summary.get( + "ai_agent_independent_post_verifier_automation_run_id" + ), + "evidence_refs": [ + summary.get( + "ai_agent_independent_post_verifier_source_receipt_ref" + ), + summary.get( + "ai_agent_independent_post_verifier_evidence_id" + ), + ], + }, + { + "work_item_id": "AIA-P0-004", + "closed": ( + canonical_learning_closed + and all(canonical_learning_controls.values()) + and not list( + summary.get("ai_agent_canonical_learning_active_blockers") + or [] + ) + ), + "automation_run_id": summary.get( + "ai_agent_canonical_learning_automation_run_id" + ), + "evidence_refs": [ + summary.get("ai_agent_canonical_learning_source_receipt_ref"), + summary.get("ai_agent_canonical_learning_learning_operation_id"), + summary.get("ai_agent_canonical_learning_knowledge_entry_id"), + summary.get("ai_agent_canonical_learning_canonical_playbook_id"), + ], + }, + { + "work_item_id": "AIA-P0-005", + "closed": ( + retry_rollback_closed + and all(retry_rollback_controls.values()) + and not list( + summary.get("ai_agent_retry_rollback_active_blockers") + or [] + ) + ), + "automation_run_id": summary.get( + "ai_agent_retry_rollback_automation_run_id" + ), + "evidence_refs": summary.get( + "ai_agent_retry_rollback_evidence_refs" + ), + }, + ], + ) priority_rank = {"P0": 0, "P1": 1, "P2": 2, "P3": 3} for item in work_items: item_id = str(item["id"]) diff --git a/apps/api/tests/test_awoooi_priority_work_order_readback_api.py b/apps/api/tests/test_awoooi_priority_work_order_readback_api.py index e9e131303..deddc9324 100644 --- a/apps/api/tests/test_awoooi_priority_work_order_readback_api.py +++ b/apps/api/tests/test_awoooi_priority_work_order_readback_api.py @@ -2393,6 +2393,42 @@ def test_ai_automation_retry_rollback_terminal_advances_order() -> None: assert payload["current_p0_execution_focus"]["workplan_id"] == "AIA-P0-006" +def test_retry_terminal_keeps_prior_program_milestones_monotonic() -> None: + payload = load_latest_awoooi_priority_work_order_readback() + runtime_control = _autonomous_runtime_control_retry_rollback_closed() + verifier = runtime_control["autonomous_independent_post_verifier"] + verifier["closed"] = False + verifier["controls"]["verification_result_success"] = False + verifier["controls"]["all_postconditions_passed"] = False + verifier["active_blockers"] = [ + "verification_result_success_not_verified", + "all_postconditions_passed_not_verified", + ] + + apply_ai_automation_live_closure_readbacks( + payload, + autonomous_runtime_control=runtime_control, + ) + + program = payload["ai_automation_program_ledger"] + items = {item["id"]: item for item in program["work_items"]} + verifier_item = items["AIA-P0-003"] + assert verifier_item["status"] == "done" + assert verifier_item["runtime_progress"]["runtime_closed"] is False + assert verifier_item["runtime_progress"]["latest_runtime_closed"] is False + assert verifier_item["runtime_progress"]["monotonic_program_closed"] is True + assert verifier_item["completion_evidence"]["proof_kind"] == ( + "monotonic_successor_runtime_closure" + ) + assert verifier_item["completion_evidence"]["successor_work_item_id"] == ( + "AIA-P0-005" + ) + assert program["summary"]["done_count"] == 6 + assert program["summary"]["completion_percent"] == 30 + assert program["summary"]["next_work_item_id"] == "AIA-P0-006" + assert program["summary"]["invalid_done_without_runtime_evidence_count"] == 0 + + def test_ai_automation_retry_rollback_terminal_fails_closed() -> None: payload = load_latest_awoooi_priority_work_order_readback() runtime_control = _autonomous_runtime_control_retry_rollback_closed()