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 6818ccf5f..ee6a09a08 100644 --- a/apps/api/src/services/awoooi_priority_work_order_readback.py +++ b/apps/api/src/services/awoooi_priority_work_order_readback.py @@ -97,6 +97,9 @@ _AI_CONTROLLED_REPAIR_LOOP_REQUIRED_FIELDS = [ _RUNNER_REGISTRATION_CONTROLLED_EVIDENCE_SCHEMA_VERSION = ( "runner_registration_controlled_evidence_v1" ) +_GITEA_CD_BASELINE_CONTROLLED_EVIDENCE_SCHEMA_VERSION = ( + "gitea_cd_baseline_controlled_evidence_v1" +) _COMMANDER_INSERTED_REQUIREMENT_WORK_ITEMS: list[dict[str, Any]] = [ { "id": "CIR-P0-001", @@ -6867,6 +6870,7 @@ def _apply_ai_automation_node_receipts(payload: dict[str, Any]) -> None: summary["ai_automation_node_receipt_lanes"] = lanes summary["ai_automation_node_receipt_work_items"] = work_items _apply_runner_registration_controlled_evidence(payload) + _apply_gitea_cd_baseline_controlled_evidence(payload) _apply_ai_controlled_repair_loop_runbook_matrix(payload) @@ -7023,6 +7027,186 @@ def _apply_runner_registration_controlled_evidence(payload: dict[str, Any]) -> N ) +def _apply_gitea_cd_baseline_controlled_evidence(payload: dict[str, Any]) -> None: + summary = _dict(payload.setdefault("summary", {})) + latest_source_sha = str( + summary.get("latest_successful_deployed_source_sha") + or summary.get("current_main_latest_source_sha") + or "" + ) + latest_source_short = ( + str(summary.get("latest_successful_deployed_source_short_sha") or "") + or latest_source_sha[:10] + ) + latest_deploy_marker = str(summary.get("latest_successful_deploy_marker") or "") + latest_cd_run_id = str( + summary.get("ai_loop_current_blocker_current_cd_run_id") + or summary.get("current_main_cd_run_id") + or "" + ) + latest_cd_run_status = str( + summary.get("ai_loop_current_blocker_current_cd_run_status") + or summary.get("current_main_cd_run_status") + or "" + ) + production_readback_matched = bool( + summary.get("ai_loop_current_blocker_deploy_marker_resolved_by_production_readback") + is True + or summary.get("production_source_truth_available") is True + ) + + evidence = { + "schema_version": _GITEA_CD_BASELINE_CONTROLLED_EVIDENCE_SCHEMA_VERSION, + "status": "gitea_cd_baseline_controlled_evidence_ready", + "work_item_id": "CIR-P0-009", + "lane": "gitea_cd", + "metadata_only": True, + "controlled_apply_allowed": True, + "break_glass_required": False, + "target_selector": { + "work_item_id": "CIR-P0-009", + "mapped_workplan_id": "P0-004", + "repo": "wooo/awoooi", + "branch": "main", + "workflow": "cd.yaml", + "visible_surface": "/zh-TW/awooop/work-items", + }, + "source_truth_diff_refs": [ + ".gitea/workflows/cd.yaml", + "ops/runner/read-public-gitea-actions-queue.py", + "scripts/ci/generate-cicd-artifact-receipts.py", + "apps/api/src/services/gitea_capability_ci_cd_optimization_readback.py", + "apps/api/src/services/awoooi_priority_work_order_readback.py", + ], + "candidate_action": { + "ready": True, + "action": ( + "poll public Gitea CD queue, verify deploy marker, then read " + "production API/UI without credentials" + ), + }, + "check_mode_or_dry_run": { + "ready": True, + "commands": [ + "python3 ops/runner/read-public-gitea-actions-queue.py --json", + "git fetch gitea-ssh main --prune", + ( + "curl -fsS https://awoooi.wooo.work/api/v1/agents/" + "awoooi-priority-work-order-readback" + ), + ], + }, + "controlled_apply_boundary": { + "ready": True, + "boundary": ( + "Gitea source push may trigger controlled CD; this readback " + "does not dispatch workflows, force-push refs, change visibility, " + "or perform host writes" + ), + }, + "rollback_or_no_write": { + "ready": True, + "mode": "no_runtime_write_from_readback_with_deploy_marker_rollback_ref", + "runtime_write_performed": False, + "workflow_dispatch_performed": False, + "force_push_or_ref_delete_performed": False, + "repo_visibility_changed": False, + "rollback_refs": [ + latest_deploy_marker or "latest_successful_deploy_marker_readback", + latest_source_short or "latest_successful_deployed_source_sha", + "docs/runbooks/FULL-STACK-COLD-START-SOP.md#deploy-storm-note", + ], + }, + "post_verifier": { + "ready": True, + "commands": [ + "python3 ops/runner/read-public-gitea-actions-queue.py --json", + ( + "curl -fsS https://awoooi.wooo.work/api/v1/agents/" + "awoooi-priority-work-order-readback" + ), + "Playwright desktop/mobile smoke /zh-TW/awooop/work-items", + ], + "latest_cd_run_id": latest_cd_run_id, + "latest_cd_run_status": latest_cd_run_status, + "latest_successful_deployed_source_sha": latest_source_sha, + "latest_successful_deploy_marker": latest_deploy_marker, + "production_readback_matched": production_readback_matched, + }, + "km_playbook_trust_writeback": { + "ready": True, + "km_writeback_ref": "km://awoooi/gitea-cd/baseline-readback", + "rag_chunk_ref": "rag://awoooi/gitea-cd/deploy-marker-production-readback", + "mcp_evidence_ref": "mcp://awoooi/gitea/public-actions-queue-readback", + "playbook_trust_writeback_ref": ( + "playbook://awoooi/gitea-cd/baseline-readback" + ), + "trust_basis": [ + "public_gitea_queue_readback", + "deploy_marker_readback", + "production_api_readback", + "desktop_mobile_smoke", + "artifact_receipt_source_contract", + ], + }, + "forbidden_runtime_actions": [ + "github_api_or_gh_cli", + "workflow_dispatch", + "force_push_or_ref_delete", + "repo_visibility_change", + "read_runner_token_or_runner_file", + "raw_secret_volume_read", + ], + "evidence_refs": [ + _COMMANDER_INSERTED_REQUIREMENT_SOURCE, + "ops/runner/read-public-gitea-actions-queue.py", + ".gitea/workflows/cd.yaml", + "scripts/ci/generate-cicd-artifact-receipts.py", + "apps/api/src/services/gitea_capability_ci_cd_optimization_readback.py", + ], + "ready_fields": _AI_CONTROLLED_REPAIR_LOOP_REQUIRED_FIELDS, + "missing_fields": [], + } + + payload["gitea_cd_baseline_controlled_evidence"] = evidence + + rollups = _dict(payload.setdefault("rollups", {})) + summary["gitea_cd_baseline_controlled_evidence_schema_version"] = evidence[ + "schema_version" + ] + summary["gitea_cd_baseline_controlled_evidence_status"] = evidence["status"] + summary["gitea_cd_baseline_controlled_evidence_ready"] = True + summary["gitea_cd_baseline_controlled_evidence_metadata_only"] = True + summary["gitea_cd_baseline_controlled_no_runtime_write"] = True + summary["gitea_cd_baseline_controlled_workflow_dispatch_performed"] = False + summary["gitea_cd_baseline_controlled_force_push_performed"] = False + summary["gitea_cd_baseline_controlled_km_writeback_ref"] = evidence[ + "km_playbook_trust_writeback" + ]["km_writeback_ref"] + summary["gitea_cd_baseline_controlled_playbook_trust_writeback_ref"] = evidence[ + "km_playbook_trust_writeback" + ]["playbook_trust_writeback_ref"] + rollups.update( + { + "gitea_cd_baseline_controlled_evidence_schema_version": evidence[ + "schema_version" + ], + "gitea_cd_baseline_controlled_evidence_status": evidence["status"], + "gitea_cd_baseline_controlled_evidence_ready": True, + "gitea_cd_baseline_controlled_evidence_metadata_only": True, + "gitea_cd_baseline_controlled_no_runtime_write": True, + "gitea_cd_baseline_controlled_workflow_dispatch_performed": False, + "gitea_cd_baseline_controlled_force_push_performed": False, + "gitea_cd_baseline_controlled_km_writeback_ref": evidence[ + "km_playbook_trust_writeback" + ]["km_writeback_ref"], + "gitea_cd_baseline_controlled_playbook_trust_writeback_ref": evidence[ + "km_playbook_trust_writeback" + ]["playbook_trust_writeback_ref"], + } + ) + + def _contains_any(value: str, needles: tuple[str, ...]) -> bool: normalized = value.lower() return any(needle.lower() in normalized for needle in needles) @@ -7060,6 +7244,14 @@ def _apply_ai_controlled_repair_loop_runbook_matrix( == "runner_registration_controlled_evidence_ready" and runner_registration_evidence.get("metadata_only") is True ) + gitea_cd_baseline_evidence = _dict( + payload.get("gitea_cd_baseline_controlled_evidence") + ) + gitea_cd_baseline_evidence_ready = ( + gitea_cd_baseline_evidence.get("status") + == "gitea_cd_baseline_controlled_evidence_ready" + and gitea_cd_baseline_evidence.get("metadata_only") is True + ) rows: list[dict[str, Any]] = [] for item in sorted(items, key=lambda raw: _int(raw.get("order"))): @@ -7229,6 +7421,31 @@ def _apply_ai_controlled_repair_loop_runbook_matrix( is True, } ) + if item_id == "CIR-P0-009" and gitea_cd_baseline_evidence_ready: + fields.update( + { + "target_selector": True, + "source_truth_diff": True, + "candidate_action": True, + "check_mode_or_dry_run": _dict( + gitea_cd_baseline_evidence.get("check_mode_or_dry_run") + ).get("ready") + is True, + "controlled_apply_boundary": True, + "rollback_or_no_write": _dict( + gitea_cd_baseline_evidence.get("rollback_or_no_write") + ).get("ready") + is True, + "post_verifier": _dict( + gitea_cd_baseline_evidence.get("post_verifier") + ).get("ready") + is True, + "km_playbook_trust_writeback": _dict( + gitea_cd_baseline_evidence.get("km_playbook_trust_writeback") + ).get("ready") + is True, + } + ) ready_field_count = sum(1 for ready in fields.values() if ready) missing_fields = [ field @@ -7317,6 +7534,45 @@ def _apply_ai_controlled_repair_loop_runbook_matrix( ), } ) + if item_id == "CIR-P0-009" and gitea_cd_baseline_evidence_ready: + rows[-1].update( + { + "controlled_evidence_status": str( + gitea_cd_baseline_evidence.get("status") or "" + ), + "controlled_evidence_schema_version": str( + gitea_cd_baseline_evidence.get("schema_version") or "" + ), + "metadata_only": True, + "runtime_write_performed": False, + "workflow_dispatch_performed": False, + "force_push_or_ref_delete_performed": False, + "km_writeback_ref": str( + _dict( + gitea_cd_baseline_evidence.get( + "km_playbook_trust_writeback" + ) + ).get("km_writeback_ref") + or "" + ), + "playbook_trust_writeback_ref": str( + _dict( + gitea_cd_baseline_evidence.get( + "km_playbook_trust_writeback" + ) + ).get("playbook_trust_writeback_ref") + or "" + ), + "post_verifier_commands": _strings( + _dict(gitea_cd_baseline_evidence.get("post_verifier")).get( + "commands" + ) + ), + "evidence_refs": _strings( + gitea_cd_baseline_evidence.get("evidence_refs") + ), + } + ) required_total = len(rows) * len(_AI_CONTROLLED_REPAIR_LOOP_REQUIRED_FIELDS) ready_total = sum(_int(row.get("ready_field_count")) for row in rows) 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 79c0f6fdc..6e0d9ef30 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 @@ -618,7 +618,10 @@ def test_awoooi_priority_work_order_readback_surfaces_gitea_bundle_truth(): assert runbook_summary["break_glass_runbook_count"] == sum( 1 for item in runbook_rows if item["coverage_state"] == "break_glass" ) - assert runbook_summary["next_work_item_id"] == "CIR-P0-009" + assert runbook_summary["ready_field_total"] == 192 + assert runbook_summary["completion_percent"] == 71 + assert runbook_summary["next_work_item_id"] == "CIR-P0-010" + assert runbook_summary["next_missing_fields"] == ["km_playbook_trust_writeback"] assert runbook_by_id["CIR-P0-006"]["break_glass_required"] is True assert runbook_by_id["CIR-P0-006"]["controlled_apply_allowed"] is False assert runbook_by_id["CIR-P0-007"]["controlled_apply_allowed"] is True @@ -676,7 +679,7 @@ def test_awoooi_priority_work_order_readback_surfaces_gitea_bundle_truth(): runbook_summary["p0_runbook_count"] ) assert payload["summary"]["ai_controlled_repair_loop_next_work_item_id"] == ( - "CIR-P0-009" + "CIR-P0-010" ) assert payload["summary"]["runner_registration_controlled_evidence_ready"] is True assert payload["summary"]["runner_registration_controlled_no_runtime_write"] is True @@ -684,6 +687,58 @@ def test_awoooi_priority_work_order_readback_surfaces_gitea_bundle_truth(): payload["summary"]["runner_registration_controlled_secret_value_read"] is False ) + assert runbook_by_id["CIR-P0-009"]["coverage_state"] == "ready" + assert runbook_by_id["CIR-P0-009"]["ready_field_count"] == 8 + assert runbook_by_id["CIR-P0-009"]["missing_fields"] == [] + assert runbook_by_id["CIR-P0-009"]["check_mode_or_dry_run_ready"] is True + assert runbook_by_id["CIR-P0-009"]["rollback_or_no_write_ready"] is True + assert ( + runbook_by_id["CIR-P0-009"]["km_playbook_trust_writeback_ready"] + is True + ) + assert runbook_by_id["CIR-P0-009"]["runtime_write_performed"] is False + assert runbook_by_id["CIR-P0-009"]["workflow_dispatch_performed"] is False + assert runbook_by_id["CIR-P0-009"][ + "force_push_or_ref_delete_performed" + ] is False + assert runbook_by_id["CIR-P0-009"]["km_writeback_ref"] == ( + "km://awoooi/gitea-cd/baseline-readback" + ) + assert runbook_by_id["CIR-P0-009"]["playbook_trust_writeback_ref"] == ( + "playbook://awoooi/gitea-cd/baseline-readback" + ) + gitea_cd_evidence = payload["gitea_cd_baseline_controlled_evidence"] + assert gitea_cd_evidence["schema_version"] == ( + "gitea_cd_baseline_controlled_evidence_v1" + ) + assert gitea_cd_evidence["metadata_only"] is True + assert gitea_cd_evidence["check_mode_or_dry_run"]["ready"] is True + assert gitea_cd_evidence["rollback_or_no_write"]["ready"] is True + assert gitea_cd_evidence["rollback_or_no_write"][ + "runtime_write_performed" + ] is False + assert gitea_cd_evidence["rollback_or_no_write"][ + "workflow_dispatch_performed" + ] is False + assert gitea_cd_evidence["rollback_or_no_write"][ + "force_push_or_ref_delete_performed" + ] is False + assert gitea_cd_evidence["km_playbook_trust_writeback"][ + "km_writeback_ref" + ] == "km://awoooi/gitea-cd/baseline-readback" + assert gitea_cd_evidence["km_playbook_trust_writeback"][ + "playbook_trust_writeback_ref" + ] == "playbook://awoooi/gitea-cd/baseline-readback" + assert payload["summary"]["gitea_cd_baseline_controlled_evidence_ready"] is True + assert payload["summary"]["gitea_cd_baseline_controlled_no_runtime_write"] is True + assert ( + payload["summary"]["gitea_cd_baseline_controlled_workflow_dispatch_performed"] + is False + ) + assert ( + payload["summary"]["gitea_cd_baseline_controlled_force_push_performed"] + is False + ) assert payload["summary"][ "ai_controlled_repair_loop_manual_as_default_terminal_allowed" ] is False @@ -1598,7 +1653,13 @@ def test_awoooi_priority_work_order_readback_endpoint_returns_snapshot( 1 for item in inserted_items if item["priority"] == "P0" ) assert endpoint_matrix["summary"]["manual_terminal_count"] == 0 - assert endpoint_matrix["summary"]["next_work_item_id"] == "CIR-P0-009" + assert endpoint_matrix["summary"]["ready_field_total"] == 217 + assert endpoint_matrix["summary"]["completion_percent"] == 80 + assert endpoint_matrix["summary"]["next_work_item_id"] == "CIR-P0-012" + assert endpoint_matrix["summary"]["next_missing_fields"] == [ + "check_mode_or_dry_run", + "rollback_or_no_write", + ] assert endpoint_row_by_id["CIR-P0-006"]["coverage_state"] == "break_glass" assert endpoint_row_by_id["CIR-P0-006"]["controlled_apply_allowed"] is False assert endpoint_row_by_id["CIR-P0-007"]["coverage_state"] == "ready" @@ -1609,10 +1670,20 @@ def test_awoooi_priority_work_order_readback_endpoint_returns_snapshot( assert endpoint_row_by_id["CIR-P0-007"][ "raw_runner_registration_read" ] is False + assert endpoint_row_by_id["CIR-P0-009"]["coverage_state"] == "ready" + assert endpoint_row_by_id["CIR-P0-009"]["missing_fields"] == [] + assert endpoint_row_by_id["CIR-P0-009"]["runtime_write_performed"] is False + assert endpoint_row_by_id["CIR-P0-009"][ + "workflow_dispatch_performed" + ] is False + assert endpoint_row_by_id["CIR-P0-009"][ + "force_push_or_ref_delete_performed" + ] is False assert data["summary"]["ai_controlled_repair_loop_next_work_item_id"] == ( - "CIR-P0-009" + "CIR-P0-012" ) assert data["summary"]["runner_registration_controlled_evidence_ready"] is True + assert data["summary"]["gitea_cd_baseline_controlled_evidence_ready"] is True assert data["summary"]["ai_controlled_repair_loop_manual_terminal_count"] == 0 diff --git a/k8s/awoooi-prod/06-deployment-api.yaml b/k8s/awoooi-prod/06-deployment-api.yaml index d138ee5df..e9a7edd6e 100644 --- a/k8s/awoooi-prod/06-deployment-api.yaml +++ b/k8s/awoooi-prod/06-deployment-api.yaml @@ -84,12 +84,12 @@ spec: - name: AWOOOI_BUILD_COMMIT_SHA # 2026-06-29 Codex: CD rewrites this to the deployed image tag so # production deploy readback does not rely on a stale static snapshot. - value: "337eff5f0cda0934717167e4ae6350a8179f04ad" + value: "7d90d6383ffc77ddd81c2be35cbb919b8fc3f02f" - name: AWOOOI_DESIRED_API_IMAGE_TAG # 2026-06-30 Codex: CD rewrites this alongside AWOOOI_BUILD_COMMIT_SHA. # Production readback compares runtime image truth against this # GitOps desired tag instead of doing a slow Gitea raw fetch. - value: "337eff5f0cda0934717167e4ae6350a8179f04ad" + value: "7d90d6383ffc77ddd81c2be35cbb919b8fc3f02f" - name: DATABASE_POOL_SIZE # 2026-07-01 Codex: production role `awoooi` currently has a low # connection limit. Keep API pool conservative until DB role diff --git a/k8s/awoooi-prod/kustomization.yaml b/k8s/awoooi-prod/kustomization.yaml index 155fc8634..ae7d67461 100644 --- a/k8s/awoooi-prod/kustomization.yaml +++ b/k8s/awoooi-prod/kustomization.yaml @@ -41,7 +41,7 @@ resources: images: - name: 192.168.0.110:5000/library/api:IMAGE_TAG_PLACEHOLDER newName: 192.168.0.110:5000/awoooi/api - newTag: 337eff5f0cda0934717167e4ae6350a8179f04ad + newTag: 7d90d6383ffc77ddd81c2be35cbb919b8fc3f02f - name: 192.168.0.110:5000/library/web:IMAGE_TAG_PLACEHOLDER newName: 192.168.0.110:5000/awoooi/web - newTag: 337eff5f0cda0934717167e4ae6350a8179f04ad + newTag: 7d90d6383ffc77ddd81c2be35cbb919b8fc3f02f