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 c6ec21975..09fe07d73 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 @@ -139,6 +139,9 @@ def _readback_payload() -> dict: def test_gitea_capability_readback_rolls_up_complete_feature_surface() -> None: payload = _readback_payload() + workflow_inventory = payload["workflow_source_inventory"] + workflow_rows = workflow_inventory["workflow_rows"] + workflow_file_count = len(workflow_rows) assert ( payload["schema_version"] == "gitea_capability_ci_cd_optimization_readback_v1" @@ -148,20 +151,53 @@ 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"] == 14 - professional_feature_rollups = payload["workflow_source_inventory"][ + assert workflow_inventory["workflow_file_count"] == workflow_file_count + professional_feature_rollups = workflow_inventory[ "professional_feature_rollups" ] - assert professional_feature_rollups["gitea_ignored_syntax_workflow_count"] == 11 - 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"] == 5 + ignored_syntax_keys = { + "declares_concurrency", + "declares_permissions", + "declares_timeout_minutes", + "declares_continue_on_error", + "declares_environment", + } + assert professional_feature_rollups[ + "gitea_ignored_syntax_workflow_count" + ] == sum( + any(row.get(key) is True for key in ignored_syntax_keys) + for row in workflow_rows + ) + assert professional_feature_rollups["otel_workflow_count"] == sum( + row.get("uses_otel") is True for row in workflow_rows + ) + assert professional_feature_rollups["argocd_gitops_workflow_count"] == sum( + row.get("uses_argocd_gitops") is True for row in workflow_rows + ) + assert professional_feature_rollups[ + "notification_or_webhook_workflow_count" + ] == sum( + row.get("uses_notification_or_webhook") is True for row in workflow_rows + ) + assert ( + professional_feature_rollups["registry_or_package_channel_workflow_count"] + == sum( + row.get("uses_registry_or_package_channel") is True + for row in workflow_rows + ) + ) + assert professional_feature_rollups[ + "package_release_promotion_workflow_count" + ] == sum( + row.get("uses_package_release_promotion_contract") is True + for row in workflow_rows + ) + assert professional_feature_rollups["sbom_or_provenance_workflow_count"] == sum( + row.get("uses_sbom_or_provenance") is True for row in workflow_rows + ) + assert professional_feature_rollups["artifact_transfer_workflow_count"] == sum( + row.get("uses_artifact_transfer") is True for row in workflow_rows ) - assert professional_feature_rollups["package_release_promotion_workflow_count"] == 1 - assert professional_feature_rollups["sbom_or_provenance_workflow_count"] == 1 - assert professional_feature_rollups["artifact_transfer_workflow_count"] == 1 assert ( payload["workflow_source_inventory"]["secret_variable_rollups"][ "secret_values_collected" @@ -203,8 +239,8 @@ def test_gitea_capability_readback_rolls_up_complete_feature_surface() -> None: assert capability_by_id["wiki_knowledge_base"]["status"] == "source_ready" assert capability_by_id["gitea_actions_workflows"]["status"] == "verified_ready" assert capability_by_id["gitea_actions_workflows"]["metrics"] == { - "workflow_source_file_count": 14, - "workflow_contract_count": 14, + "workflow_source_file_count": workflow_file_count, + "workflow_contract_count": workflow_file_count, } 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 ee9734a94..a65eb96fe 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,14 @@ 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"]) == 13 - assert data["rollups"]["workflows_with_schedule"] == 5 - assert data["rollups"]["workflows_with_workflow_dispatch"] == 13 + workflow_records = data["workflow_records"] + assert data["rollups"]["total_workflows"] == len(workflow_records) + assert data["rollups"]["workflows_with_schedule"] == sum( + "schedule" in row["triggers"] for row in workflow_records + ) + assert data["rollups"]["workflows_with_workflow_dispatch"] == sum( + "workflow_dispatch" in row["triggers"] for row in workflow_records + ) assert data["rollups"]["workflow_ids_requiring_runner_attestation"] == [] assert data["rollups"]["runner_contracts_requiring_action"] == [] assert data["rollups"]["notification_contracts_quiet_success_count"] == 3 @@ -64,5 +69,16 @@ def test_gitea_workflow_runner_health_endpoint_returns_committed_snapshot(): if row["workflow_id"] == "harbor_110_local_repair" ) assert harbor_repair["runner_evidence_status"] == "non110_host_runner_mapped" + mcp_artifact_mirror = next( + row + for row in workflow_records + if row["workflow_id"] == "mcp_external_artifact_mirror" + ) + assert mcp_artifact_mirror["runner_labels"] == ["awoooi-non110-host"] + assert mcp_artifact_mirror["runner_evidence_status"] == ( + "non110_host_runner_mapped" + ) + assert mcp_artifact_mirror["notification_policy"] == "read_only_no_notify" + assert mcp_artifact_mirror["notify_bridge_calls"] == 0 assert "workflow 修改批准" in data["operator_contract"]["must_not_interpret_as"] assert "Secret 已讀取或可輸出" 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 86a4ff9b3..fe7b0fd49 100644 --- a/docs/evaluations/gitea_workflow_runner_health_2026-06-05.json +++ b/docs/evaluations/gitea_workflow_runner_health_2026-06-05.json @@ -1,6 +1,6 @@ { "schema_version": "gitea_workflow_runner_health_v1", - "generated_at": "2026-07-15T09:54:44+08:00", + "generated_at": "2026-07-15T09:57:22+08:00", "program_status": { "overall_completion_percent": 100, "current_priority": "P1", @@ -24,6 +24,7 @@ ".gitea/workflows/mcp-external-artifact-mirror.yaml", ".gitea/workflows/run-migration.yml", ".gitea/workflows/type-sync-check.yaml", + "config/mcp/playwright-mcp-artifact-policy.json", "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", @@ -48,7 +49,7 @@ "workflow_ids_requiring_runner_attestation": [], "total_runner_contracts": 5, "runner_contracts_requiring_action": [], - "notification_contracts_total": 7, + "notification_contracts_total": 8, "notification_contracts_quiet_success_count": 3, "notification_contracts_quiet_success_ids": [ "agent_market_watch_actionable_only", @@ -342,30 +343,31 @@ "workflow_id": "mcp_external_artifact_mirror", "file_ref": ".gitea/workflows/mcp-external-artifact-mirror.yaml", "display_name": "MCP External Artifact Mirror", - "scope": "以 exact npm registry artifact、digest、簽章、SLSA subject、SBOM 與 OSV 查核建立 Harbor scratch OCI bundle;獨立 verifier 只讀回 layer data,不啟動 MCP、browser 或 container。", + "scope": "以 exact npm registry URL、committed SHA-512 與 Harbor digest 執行受控外部 MCP artifact mirror;只寫獨立 audit branch,不啟動 MCP、不改 production route。", "status": "manifest_mapped", "risk_level": "high", "triggers": [ + "push:main", "workflow_dispatch", - "schedule", - "push" + "schedule" ], - "schedule_cadence": "每週三 10:13 Asia/Taipei 重驗 pinned artifact;cron=13 2 * * 3 UTC", + "schedule_cadence": "每週三 10:13 Asia/Taipei;cron=13 2 * * 3 UTC", "runner_labels": [ "awoooi-non110-host" ], "runner_evidence_status": "non110_host_runner_mapped", "job_count": 1, - "notification_policy": "durable_receipt_only_no_notify", + "notification_policy": "read_only_no_notify", "notify_bridge_calls": 0, - "secrets_policy_status": "只引用既有 Harbor 與 Gitea push secret 名稱;password-stdin/ephemeral askpass 且不輸出、不保存值。", + "secrets_policy_status": "只使用 workflow secret references 進行 Harbor 登入與獨立 audit branch normal push;本 snapshot 與 readback 不讀取、保存或顯示任何值。", "evidence_refs": [ ".gitea/workflows/mcp-external-artifact-mirror.yaml", "config/mcp/playwright-mcp-artifact-policy.json", "scripts/security/external_mcp_artifact_controller.py", - "scripts/security/verify_external_mcp_artifact_receipt.py" + "scripts/security/verify_external_mcp_artifact_receipt.py", + "scripts/ci/wait-host-web-build-pressure.sh" ], - "next_action": "先完成 audit branch mirror/verifier receipt;在 registered public-origin replay adapter 與 rollback receipt 完成前維持 deployment/shadow/canary=false。" + "next_action": "維持 bounded capacity wait、獨立 verifier 與 mcp-artifact-receipts audit branch;不得啟動 artifact、寫 RAG、切 production route 或推回 deploy main。" }, { "workflow_id": "run_migration", @@ -429,7 +431,8 @@ "used_by_workflows": [ "awoooi_onboarding_warning_step", "cd_pipeline", - "harbor_110_local_repair" + "harbor_110_local_repair", + "mcp_external_artifact_mirror" ], "health_contract": "host-level controlled workflows use the explicit awoooi-non110-host label; this snapshot does not restart or mutate the runner.", "guardrail_refs": [ @@ -439,7 +442,8 @@ "evidence_refs": [ ".gitea/workflows/cd.yaml", ".gitea/workflows/harbor-110-local-repair.yaml", - ".gitea/workflows/awoooi-onboarding-warning-step.yaml" + ".gitea/workflows/awoooi-onboarding-warning-step.yaml", + ".gitea/workflows/mcp-external-artifact-mirror.yaml" ], "next_action": "維持 explicit non110 host label;任何 runner/service 變更另走維護窗口。" }, @@ -643,6 +647,22 @@ ], "next_action": "後續補 alert/repair receipt;本 snapshot 不發通知、不觸發 migration 或 repair。" }, + { + "contract_id": "mcp_artifact_mirror_no_notify", + "display_name": "MCP artifact mirror no-notify", + "status": "preserved", + "policy_kind": "read_only_no_notify", + "success_noise_policy": "artifact mirror 與 verifier 成功只寫獨立 audit branch,不向 Telegram 或產品告警群組發送成功訊息。", + "failure_policy": "失敗留在 Gitea workflow 與脫敏 receipt;不得因通知失敗而重送 artifact、切 production route 或啟動 MCP。", + "workflow_refs": [ + "mcp_external_artifact_mirror" + ], + "evidence_refs": [ + ".gitea/workflows/mcp-external-artifact-mirror.yaml", + "config/mcp/playwright-mcp-artifact-policy.json" + ], + "next_action": "維持 no-notify 與獨立 audit branch;只在受控營運 readback 顯示 terminal receipt。" + }, { "contract_id": "lint_and_typecheck_no_notify", "display_name": "Lint / typecheck no-notify",