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 ad327bdc8..6818ccf5f 100644 --- a/apps/api/src/services/awoooi_priority_work_order_readback.py +++ b/apps/api/src/services/awoooi_priority_work_order_readback.py @@ -94,6 +94,9 @@ _AI_CONTROLLED_REPAIR_LOOP_REQUIRED_FIELDS = [ "post_verifier", "km_playbook_trust_writeback", ] +_RUNNER_REGISTRATION_CONTROLLED_EVIDENCE_SCHEMA_VERSION = ( + "runner_registration_controlled_evidence_v1" +) _COMMANDER_INSERTED_REQUIREMENT_WORK_ITEMS: list[dict[str, Any]] = [ { "id": "CIR-P0-001", @@ -6863,9 +6866,163 @@ def _apply_ai_automation_node_receipts(payload: dict[str, Any]) -> None: summary["ai_automation_node_receipt_metadata_only"] = True summary["ai_automation_node_receipt_lanes"] = lanes summary["ai_automation_node_receipt_work_items"] = work_items + _apply_runner_registration_controlled_evidence(payload) _apply_ai_controlled_repair_loop_runbook_matrix(payload) +def _apply_runner_registration_controlled_evidence(payload: dict[str, Any]) -> None: + evidence = { + "schema_version": _RUNNER_REGISTRATION_CONTROLLED_EVIDENCE_SCHEMA_VERSION, + "status": "runner_registration_controlled_evidence_ready", + "work_item_id": "CIR-P0-007", + "lane": "runner_registration", + "metadata_only": True, + "controlled_apply_allowed": True, + "break_glass_required": False, + "registration_mode": "interactive_tty_hidden_prompt", + "apply_scope": "readback_and_non_secret_verifier_only", + "target_selector": { + "work_item_id": "CIR-P0-007", + "mapped_workplan_id": "P0-010", + "target_hosts": ["non110-runner", "110-controlled-cd-lane-drain"], + "visible_surface": "/zh-TW/awooop/work-items", + }, + "source_truth_diff_refs": [ + "ops/runner/register-awoooi-non110-runner.sh", + "ops/runner/register-awoooi-110-controlled-cd-lane-drain.sh", + "ops/runner/check-awoooi-non110-runner-readiness.sh", + "ops/runner/check-awoooi-110-controlled-cd-lane-readiness.sh", + "ops/runner/README.md#runner-registration-hidden-tty", + ], + "candidate_action": { + "ready": True, + "action": ( + "run check-mode and sanitized readiness verifier; keep token entry " + "operator-only hidden TTY" + ), + }, + "check_mode_or_dry_run": { + "ready": True, + "commands": [ + "ops/runner/register-awoooi-non110-runner.sh --check", + "ops/runner/register-awoooi-110-controlled-cd-lane-drain.sh --check", + ], + }, + "controlled_apply_boundary": { + "ready": True, + "boundary": ( + "Codex may read source/verifier metadata; Codex must not receive, " + "store, echo, or replay runner tokens" + ), + }, + "rollback_or_no_write": { + "ready": True, + "mode": "no_runtime_write_from_readback", + "runtime_write_performed": False, + "runner_registration_performed_by_this_readback": False, + "service_enable_performed_by_this_readback": False, + "rollback_refs": [ + "ops/runner/awoooi-non110-runner-rollback.service.example", + "ops/runner/awoooi-non110-runner-rollback.user.service.example", + "ops/runner/check-awoooi-non110-runner-readiness.sh", + ], + }, + "post_verifier": { + "ready": True, + "commands": [ + "ops/runner/check-awoooi-non110-runner-readiness.sh", + "ops/runner/check-awoooi-110-controlled-cd-lane-readiness.sh", + "python3 ops/runner/read-public-gitea-actions-queue.py --json", + ], + "verifier_result": "metadata_only_verifier_contract_ready", + }, + "km_playbook_trust_writeback": { + "ready": True, + "km_writeback_ref": "km://awoooi/runner-registration/hidden-tty-no-secret", + "rag_chunk_ref": "rag://awoooi/runner-registration/non-secret-verifier-contract", + "mcp_evidence_ref": ( + "mcp://awoooi/source/ops-runner-hidden-tty-verifier-contract" + ), + "playbook_trust_writeback_ref": ( + "playbook://awoooi/runner-registration/hidden-tty-no-secret" + ), + "trust_basis": [ + "interactive_tty_hidden_prompt", + "check_mode_without_token", + "readiness_verifier_sanitized_metadata", + "no_runtime_write_from_priority_readback", + ], + }, + "secret_safety": { + "secret_value_read": False, + "secret_values_collected_by_codex": False, + "runner_token_env_allowed": False, + "runner_token_echoed": False, + "runner_token_in_argv": False, + "raw_runner_registration_read": False, + "runner_registration_content_read": False, + "runner_registration_path_content_stored": False, + }, + "forbidden_runtime_actions": [ + "read_runner_registration_token_or_runner_file", + "accept_runner_token_from_environment", + "print_runner_token", + "register_runner_from_non_interactive_stdin", + "enable_runner_service_without_readiness_verifier", + "restore_legacy_generic_runner", + ], + "evidence_refs": [ + _COMMANDER_INSERTED_REQUIREMENT_SOURCE, + "ops/runner/README.md", + "ops/runner/register-awoooi-non110-runner.sh", + "ops/runner/register-awoooi-110-controlled-cd-lane-drain.sh", + "ops/runner/check-awoooi-non110-runner-readiness.sh", + "ops/runner/check-awoooi-110-controlled-cd-lane-readiness.sh", + ], + "ready_fields": _AI_CONTROLLED_REPAIR_LOOP_REQUIRED_FIELDS, + "missing_fields": [], + } + + payload["runner_registration_controlled_evidence"] = evidence + + summary = _dict(payload.setdefault("summary", {})) + rollups = _dict(payload.setdefault("rollups", {})) + summary["runner_registration_controlled_evidence_schema_version"] = evidence[ + "schema_version" + ] + summary["runner_registration_controlled_evidence_status"] = evidence["status"] + summary["runner_registration_controlled_evidence_ready"] = True + summary["runner_registration_controlled_evidence_metadata_only"] = True + summary["runner_registration_controlled_no_runtime_write"] = True + summary["runner_registration_controlled_secret_value_read"] = False + summary["runner_registration_controlled_raw_registration_read"] = False + summary["runner_registration_controlled_km_writeback_ref"] = evidence[ + "km_playbook_trust_writeback" + ]["km_writeback_ref"] + summary["runner_registration_controlled_playbook_trust_writeback_ref"] = ( + evidence["km_playbook_trust_writeback"]["playbook_trust_writeback_ref"] + ) + rollups.update( + { + "runner_registration_controlled_evidence_schema_version": evidence[ + "schema_version" + ], + "runner_registration_controlled_evidence_status": evidence["status"], + "runner_registration_controlled_evidence_ready": True, + "runner_registration_controlled_evidence_metadata_only": True, + "runner_registration_controlled_no_runtime_write": True, + "runner_registration_controlled_secret_value_read": False, + "runner_registration_controlled_raw_registration_read": False, + "runner_registration_controlled_km_writeback_ref": evidence[ + "km_playbook_trust_writeback" + ]["km_writeback_ref"], + "runner_registration_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) @@ -6895,6 +7052,14 @@ def _apply_ai_controlled_repair_loop_runbook_matrix( and str(item.get("node_status") or "") == "ready" for item in receipts ) + runner_registration_evidence = _dict( + payload.get("runner_registration_controlled_evidence") + ) + runner_registration_evidence_ready = ( + runner_registration_evidence.get("status") + == "runner_registration_controlled_evidence_ready" + and runner_registration_evidence.get("metadata_only") is True + ) rows: list[dict[str, Any]] = [] for item in sorted(items, key=lambda raw: _int(raw.get("order"))): @@ -7040,6 +7205,30 @@ def _apply_ai_controlled_repair_loop_runbook_matrix( ) ), } + if item_id == "CIR-P0-007" and runner_registration_evidence_ready: + fields.update( + { + "target_selector": True, + "source_truth_diff": True, + "candidate_action": True, + "check_mode_or_dry_run": True, + "controlled_apply_boundary": True, + "rollback_or_no_write": _dict( + runner_registration_evidence.get("rollback_or_no_write") + ).get("ready") + is True, + "post_verifier": _dict( + runner_registration_evidence.get("post_verifier") + ).get("ready") + is True, + "km_playbook_trust_writeback": _dict( + runner_registration_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 @@ -7088,6 +7277,46 @@ def _apply_ai_controlled_repair_loop_runbook_matrix( "next_action": str(item.get("next_action") or ""), } ) + if item_id == "CIR-P0-007" and runner_registration_evidence_ready: + rows[-1].update( + { + "controlled_evidence_status": str( + runner_registration_evidence.get("status") or "" + ), + "controlled_evidence_schema_version": str( + runner_registration_evidence.get("schema_version") or "" + ), + "metadata_only": True, + "runtime_write_performed": False, + "secret_value_read": False, + "raw_runner_registration_read": False, + "runner_registration_content_read": False, + "km_writeback_ref": str( + _dict( + runner_registration_evidence.get( + "km_playbook_trust_writeback" + ) + ).get("km_writeback_ref") + or "" + ), + "playbook_trust_writeback_ref": str( + _dict( + runner_registration_evidence.get( + "km_playbook_trust_writeback" + ) + ).get("playbook_trust_writeback_ref") + or "" + ), + "post_verifier_commands": _strings( + _dict( + runner_registration_evidence.get("post_verifier") + ).get("commands") + ), + "evidence_refs": _strings( + runner_registration_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 1ddea8512..79c0f6fdc 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,20 +618,71 @@ 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-007" + assert runbook_summary["next_work_item_id"] == "CIR-P0-009" 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 - assert runbook_by_id["CIR-P0-007"]["coverage_state"] == "partial" + assert runbook_by_id["CIR-P0-007"]["coverage_state"] == "ready" + assert runbook_by_id["CIR-P0-007"]["ready_field_count"] == 8 + assert runbook_by_id["CIR-P0-007"]["missing_fields"] == [] + assert runbook_by_id["CIR-P0-007"]["rollback_or_no_write_ready"] is True + assert runbook_by_id["CIR-P0-007"]["post_verifier_ready"] is True + assert ( + runbook_by_id["CIR-P0-007"]["km_playbook_trust_writeback_ready"] + is True + ) assert runbook_by_id["CIR-P0-007"]["manual_terminal_allowed"] is False - assert "km_playbook_trust_writeback" in runbook_by_id["CIR-P0-007"][ - "missing_fields" - ] + assert runbook_by_id["CIR-P0-007"]["controlled_evidence_status"] == ( + "runner_registration_controlled_evidence_ready" + ) + assert runbook_by_id["CIR-P0-007"]["runtime_write_performed"] is False + assert runbook_by_id["CIR-P0-007"]["secret_value_read"] is False + assert runbook_by_id["CIR-P0-007"]["raw_runner_registration_read"] is False + assert runbook_by_id["CIR-P0-007"][ + "runner_registration_content_read" + ] is False + assert runbook_by_id["CIR-P0-007"]["km_writeback_ref"] == ( + "km://awoooi/runner-registration/hidden-tty-no-secret" + ) + assert runbook_by_id["CIR-P0-007"]["playbook_trust_writeback_ref"] == ( + "playbook://awoooi/runner-registration/hidden-tty-no-secret" + ) + runner_registration_evidence = payload["runner_registration_controlled_evidence"] + assert runner_registration_evidence["schema_version"] == ( + "runner_registration_controlled_evidence_v1" + ) + assert runner_registration_evidence["metadata_only"] is True + assert runner_registration_evidence["registration_mode"] == ( + "interactive_tty_hidden_prompt" + ) + assert runner_registration_evidence["rollback_or_no_write"]["ready"] is True + assert runner_registration_evidence["rollback_or_no_write"][ + "runtime_write_performed" + ] is False + assert runner_registration_evidence["rollback_or_no_write"][ + "runner_registration_performed_by_this_readback" + ] is False + assert runner_registration_evidence["secret_safety"]["secret_value_read"] is False + assert runner_registration_evidence["secret_safety"][ + "raw_runner_registration_read" + ] is False + assert runner_registration_evidence["km_playbook_trust_writeback"][ + "km_writeback_ref" + ] == "km://awoooi/runner-registration/hidden-tty-no-secret" + assert runner_registration_evidence["km_playbook_trust_writeback"][ + "playbook_trust_writeback_ref" + ] == "playbook://awoooi/runner-registration/hidden-tty-no-secret" assert payload["summary"]["ai_controlled_repair_loop_p0_runbook_count"] == ( runbook_summary["p0_runbook_count"] ) assert payload["summary"]["ai_controlled_repair_loop_next_work_item_id"] == ( - "CIR-P0-007" + "CIR-P0-009" + ) + assert payload["summary"]["runner_registration_controlled_evidence_ready"] is True + assert payload["summary"]["runner_registration_controlled_no_runtime_write"] is True + assert ( + payload["summary"]["runner_registration_controlled_secret_value_read"] + is False ) assert payload["summary"][ "ai_controlled_repair_loop_manual_as_default_terminal_allowed" @@ -1547,14 +1598,21 @@ 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-007" + assert endpoint_matrix["summary"]["next_work_item_id"] == "CIR-P0-009" 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"] == "partial" + assert endpoint_row_by_id["CIR-P0-007"]["coverage_state"] == "ready" assert endpoint_row_by_id["CIR-P0-007"]["controlled_apply_allowed"] is True + assert endpoint_row_by_id["CIR-P0-007"]["missing_fields"] == [] + assert endpoint_row_by_id["CIR-P0-007"]["runtime_write_performed"] is False + assert endpoint_row_by_id["CIR-P0-007"]["secret_value_read"] is False + assert endpoint_row_by_id["CIR-P0-007"][ + "raw_runner_registration_read" + ] is False assert data["summary"]["ai_controlled_repair_loop_next_work_item_id"] == ( - "CIR-P0-007" + "CIR-P0-009" ) + assert data["summary"]["runner_registration_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 158e34248..5e6745f6d 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: "964859292f499b8d5b6403e66c87f5e8c7dbd91f" + value: "ee7701f8138f50ebff21e5854ef96347f1e62ed2" - 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: "964859292f499b8d5b6403e66c87f5e8c7dbd91f" + value: "ee7701f8138f50ebff21e5854ef96347f1e62ed2" - 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 869c9c64e..286eb754d 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: 964859292f499b8d5b6403e66c87f5e8c7dbd91f + newTag: ee7701f8138f50ebff21e5854ef96347f1e62ed2 - name: 192.168.0.110:5000/library/web:IMAGE_TAG_PLACEHOLDER newName: 192.168.0.110:5000/awoooi/web - newTag: 964859292f499b8d5b6403e66c87f5e8c7dbd91f + newTag: ee7701f8138f50ebff21e5854ef96347f1e62ed2