From 81e00dc0237952474d3fe2f1919204a476f4ddd3 Mon Sep 17 00:00:00 2001 From: ogt Date: Fri, 10 Jul 2026 10:56:54 +0800 Subject: [PATCH] fix(gitea): include agent lifecycle workflow in runner contract --- ..._capability_ci_cd_optimization_readback.py | 3 ++ ...ions_secret_variable_name_inventory_api.py | 19 +++++++--- ...ability_ci_cd_optimization_readback_api.py | 9 +++-- .../test_gitea_workflow_runner_health_api.py | 30 +++++++++++---- ...tea_workflow_runner_health_2026-06-05.json | 38 ++++++++++++++++--- 5 files changed, 78 insertions(+), 21 deletions(-) diff --git a/apps/api/src/services/gitea_capability_ci_cd_optimization_readback.py b/apps/api/src/services/gitea_capability_ci_cd_optimization_readback.py index 151e40f3c..3931a9b81 100644 --- a/apps/api/src/services/gitea_capability_ci_cd_optimization_readback.py +++ b/apps/api/src/services/gitea_capability_ci_cd_optimization_readback.py @@ -261,6 +261,9 @@ def build_gitea_workflow_source_inventory(repo_root: Path) -> dict[str, Any]: "has_type_sync_check": "type-sync-check" in workflow_ids, "has_onboarding_warning": "awoooi-onboarding-warning-step" in workflow_ids, "has_agent_market_watch": "agent-market-watch" in workflow_ids, + "has_agent_version_lifecycle_watch": ( + "agent-version-lifecycle-watch" in workflow_ids + ), "has_ai_technology_watch": "ai-technology-watch" in workflow_ids, }, "professional_feature_rollups": { diff --git a/apps/api/tests/test_gitea_actions_secret_variable_name_inventory_api.py b/apps/api/tests/test_gitea_actions_secret_variable_name_inventory_api.py index 0cea78e89..bdaea07ac 100644 --- a/apps/api/tests/test_gitea_actions_secret_variable_name_inventory_api.py +++ b/apps/api/tests/test_gitea_actions_secret_variable_name_inventory_api.py @@ -12,10 +12,12 @@ from src.services.gitea_actions_secret_variable_name_inventory import ( def test_gitea_actions_secret_variable_name_inventory_loader_is_value_safe() -> None: payload = load_latest_gitea_actions_secret_variable_name_inventory() - assert payload["schema_version"] == "gitea_actions_secret_variable_name_inventory_v1" + assert ( + payload["schema_version"] == "gitea_actions_secret_variable_name_inventory_v1" + ) assert payload["status"] == "gitea_actions_secret_variable_name_inventory_ready" assert payload["ready"] is True - assert payload["summary"]["workflow_file_count"] == 12 + assert payload["summary"]["workflow_file_count"] == 13 assert payload["summary"]["referenced_secret_name_count"] == 26 assert payload["summary"]["referenced_variable_name_count"] == 0 assert payload["summary"]["secret_values_collected"] is False @@ -29,13 +31,18 @@ def test_gitea_actions_secret_variable_name_inventory_loader_is_value_safe() -> assert "TELEGRAM_BOT_TOKEN" in names assert "AWOOOI_GITEA_API_TOKEN" in names assert "DATABASE_URL" in names - assert all(row["value_read_status"] == "not_read_by_design" for row in payload["secret_name_rows"]) + assert all( + row["value_read_status"] == "not_read_by_design" + for row in payload["secret_name_rows"] + ) assert payload["metadata_verification_next_step"]["status"] == ( "redacted_configuration_metadata_required" ) -def test_gitea_actions_secret_variable_name_inventory_endpoint_returns_payload() -> None: +def test_gitea_actions_secret_variable_name_inventory_endpoint_returns_payload() -> ( + None +): app = FastAPI() app.include_router(router, prefix="/api/v1") client = TestClient(app) @@ -45,5 +52,7 @@ def test_gitea_actions_secret_variable_name_inventory_endpoint_returns_payload() assert response.status_code == 200 data = response.json() assert data["schema_version"] == "gitea_actions_secret_variable_name_inventory_v1" - assert data["summary"]["referenced_secret_name_count"] == len(data["secret_name_rows"]) + assert data["summary"]["referenced_secret_name_count"] == len( + data["secret_name_rows"] + ) assert data["summary"]["secret_values_collected"] is False diff --git a/apps/api/tests/test_gitea_capability_ci_cd_optimization_readback_api.py b/apps/api/tests/test_gitea_capability_ci_cd_optimization_readback_api.py index 1a3fb252c..8f08ef5d0 100644 --- a/apps/api/tests/test_gitea_capability_ci_cd_optimization_readback_api.py +++ b/apps/api/tests/test_gitea_capability_ci_cd_optimization_readback_api.py @@ -148,13 +148,14 @@ def test_gitea_capability_readback_rolls_up_complete_feature_surface() -> None: assert payload["rollups"]["expected_product_count"] == 12 assert payload["rollups"]["ssh_verified_product_repo_count"] == 12 assert payload["rollups"]["dev_prod_environment_split_ready_count"] == 12 - assert payload["workflow_source_inventory"]["workflow_file_count"] == 12 + assert payload["workflow_source_inventory"]["workflow_file_count"] == 13 professional_feature_rollups = payload["workflow_source_inventory"][ "professional_feature_rollups" ] - assert professional_feature_rollups["gitea_ignored_syntax_workflow_count"] == 9 + assert professional_feature_rollups["gitea_ignored_syntax_workflow_count"] == 10 assert professional_feature_rollups["otel_workflow_count"] == 3 assert professional_feature_rollups["argocd_gitops_workflow_count"] == 1 + assert professional_feature_rollups["notification_or_webhook_workflow_count"] == 9 assert ( professional_feature_rollups["registry_or_package_channel_workflow_count"] == 4 ) @@ -186,8 +187,8 @@ def test_gitea_capability_readback_rolls_up_complete_feature_surface() -> None: assert capability_by_id["dev_prod_environment_split"]["status"] == "verified_ready" assert capability_by_id["gitea_actions_workflows"]["status"] == "verified_ready" assert capability_by_id["gitea_actions_workflows"]["metrics"] == { - "workflow_source_file_count": 12, - "workflow_contract_count": 12, + "workflow_source_file_count": 13, + "workflow_contract_count": 13, } assert capability_by_id["gitea_actions_runners"]["status"] == "verified_ready" assert ( diff --git a/apps/api/tests/test_gitea_workflow_runner_health_api.py b/apps/api/tests/test_gitea_workflow_runner_health_api.py index ac3d7c1e1..ee9734a94 100644 --- a/apps/api/tests/test_gitea_workflow_runner_health_api.py +++ b/apps/api/tests/test_gitea_workflow_runner_health_api.py @@ -20,9 +20,9 @@ def test_gitea_workflow_runner_health_endpoint_returns_committed_snapshot(): assert data["program_status"]["current_task_id"] == "P1-002" assert data["program_status"]["next_task_id"] == "P1-003" assert data["program_status"]["read_only_mode"] is True - assert data["rollups"]["total_workflows"] == len(data["workflow_records"]) == 12 - assert data["rollups"]["workflows_with_schedule"] == 4 - assert data["rollups"]["workflows_with_workflow_dispatch"] == 12 + assert data["rollups"]["total_workflows"] == len(data["workflow_records"]) == 13 + assert data["rollups"]["workflows_with_schedule"] == 5 + assert data["rollups"]["workflows_with_workflow_dispatch"] == 13 assert data["rollups"]["workflow_ids_requiring_runner_attestation"] == [] assert data["rollups"]["runner_contracts_requiring_action"] == [] assert data["rollups"]["notification_contracts_quiet_success_count"] == 3 @@ -34,18 +34,34 @@ def test_gitea_workflow_runner_health_endpoint_returns_committed_snapshot(): assert data["approval_boundaries"]["workflow_modification_authorized"] is False assert data["approval_boundaries"]["runner_mutation_authorized"] is False assert data["approval_boundaries"]["migration_trigger_authorized"] is False - cd_pipeline = next(row for row in data["workflow_records"] if row["workflow_id"] == "cd_pipeline") + cd_pipeline = next( + row for row in data["workflow_records"] if row["workflow_id"] == "cd_pipeline" + ) assert cd_pipeline["runner_evidence_status"] == "non110_host_runner_mapped" assert cd_pipeline["runner_labels"] == ["awoooi-non110-host"] assert cd_pipeline["job_count"] == 5 - e2e_health = next(row for row in data["workflow_records"] if row["workflow_id"] == "e2e_health") + e2e_health = next( + row for row in data["workflow_records"] if row["workflow_id"] == "e2e_health" + ) assert e2e_health["notification_policy"] == "failure_only" ai_technology_watch = next( - row for row in data["workflow_records"] if row["workflow_id"] == "ai_technology_watch" + row + for row in data["workflow_records"] + if row["workflow_id"] == "ai_technology_watch" ) assert ai_technology_watch["runner_labels"] == ["awoooi-non110-ubuntu"] + agent_version_lifecycle_watch = next( + row + for row in data["workflow_records"] + if row["workflow_id"] == "agent_version_lifecycle_watch" + ) + assert agent_version_lifecycle_watch["runner_labels"] == ["awoooi-non110-ubuntu"] + assert agent_version_lifecycle_watch["notification_policy"] == "read_only_no_notify" + assert agent_version_lifecycle_watch["notify_bridge_calls"] == 0 harbor_repair = next( - row for row in data["workflow_records"] if row["workflow_id"] == "harbor_110_local_repair" + row + for row in data["workflow_records"] + if row["workflow_id"] == "harbor_110_local_repair" ) assert harbor_repair["runner_evidence_status"] == "non110_host_runner_mapped" assert "workflow 修改批准" in data["operator_contract"]["must_not_interpret_as"] diff --git a/docs/evaluations/gitea_workflow_runner_health_2026-06-05.json b/docs/evaluations/gitea_workflow_runner_health_2026-06-05.json index f7db9a4d7..694f595bd 100644 --- a/docs/evaluations/gitea_workflow_runner_health_2026-06-05.json +++ b/docs/evaluations/gitea_workflow_runner_health_2026-06-05.json @@ -11,6 +11,7 @@ "source_refs": [ "docs/schemas/gitea_workflow_runner_health_v1.schema.json", ".gitea/workflows/agent-market-watch.yaml", + ".gitea/workflows/agent-version-lifecycle-watch.yaml", ".gitea/workflows/ai-technology-watch.yaml", ".gitea/workflows/ansible-lint.yml", ".gitea/workflows/awoooi-onboarding-warning-step.yaml", @@ -22,6 +23,7 @@ ".gitea/workflows/harbor-110-local-repair.yaml", ".gitea/workflows/run-migration.yml", ".gitea/workflows/type-sync-check.yaml", + "docs/evaluations/ai_agent_version_lifecycle_update_proposal_2026-07-10.json", "scripts/ci/check-gitea-step-env-secrets.js", "scripts/ci/cleanup-host-runner-workspace.sh", "scripts/ci/wait-host-web-build-pressure.sh", @@ -30,16 +32,16 @@ "scripts/ops/stop-stale-gitea-actions-jobs.sh" ], "rollups": { - "total_workflows": 12, + "total_workflows": 13, "by_workflow_status": { - "manifest_mapped": 12 + "manifest_mapped": 13 }, "by_runner_evidence_status": { - "non110_runner_mapped": 9, + "non110_runner_mapped": 10, "non110_host_runner_mapped": 3 }, - "workflows_with_schedule": 4, - "workflows_with_workflow_dispatch": 12, + "workflows_with_schedule": 5, + "workflows_with_workflow_dispatch": 13, "workflows_with_notify_bridge": 6, "workflows_with_actionable_or_failure_quiet_policy": 3, "workflow_ids_requiring_runner_attestation": [], @@ -80,6 +82,32 @@ ], "next_action": "保留 actionable-only;維持 non110 runner label,不啟用額外通知或 paid API。" }, + { + "workflow_id": "agent_version_lifecycle_watch", + "file_ref": ".gitea/workflows/agent-version-lifecycle-watch.yaml", + "display_name": "Agent Version Lifecycle Watch", + "scope": "每週 AI Agent / SDK / framework 版本生命週期治理;只驗證 committed proposal queue,不安裝 SDK、不呼叫 paid/model API、不改 workflow、不切 production provider route。", + "status": "manifest_mapped", + "risk_level": "medium", + "triggers": [ + "workflow_dispatch", + "schedule" + ], + "schedule_cadence": "每週二 09:00 Asia/Taipei;cron=0 1 * * 2 UTC", + "runner_labels": [ + "awoooi-non110-ubuntu" + ], + "runner_evidence_status": "non110_runner_mapped", + "job_count": 1, + "notification_policy": "read_only_no_notify", + "notify_bridge_calls": 0, + "secrets_policy_status": "未讀取 Secret;runtime_boundaries 明確禁止 package upgrade、workflow write、paid API、provider route switch 與 production write。", + "evidence_refs": [ + ".gitea/workflows/agent-version-lifecycle-watch.yaml", + "docs/evaluations/ai_agent_version_lifecycle_update_proposal_2026-07-10.json" + ], + "next_action": "維持 no-write proposal validation;若要升級成自動套版,必須先補 replay/shadow/canary 與成本/路由批准。" + }, { "workflow_id": "ai_technology_watch", "file_ref": ".gitea/workflows/ai-technology-watch.yaml",