Compare commits
1 Commits
codex/depl
...
codex/110-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f477a2243b |
@@ -205,8 +205,7 @@ jobs:
|
||||
docker run --rm \
|
||||
--name "awoooi-cd-${GITHUB_RUN_ID:-manual}-${GITHUB_RUN_ATTEMPT:-1}-api-tests" \
|
||||
--cpus "2.0" \
|
||||
--memory "6g" \
|
||||
--memory-swap "8g" \
|
||||
--memory "2g" \
|
||||
-v "$PWD:/workspace" \
|
||||
-v /tmp/awoooi-api-tests.sh:/tmp/awoooi-api-tests.sh:ro \
|
||||
-v awoooi-api-venv-cache:/opt/api-venv \
|
||||
|
||||
@@ -340,7 +340,6 @@ from src.services.gitea_workflow_runner_health import (
|
||||
from src.services.github_target_private_backup_evidence_gate import (
|
||||
load_latest_github_target_private_backup_evidence_gate,
|
||||
preflight_github_target_owner_response_submission,
|
||||
validate_github_target_safe_credential_evidence_refs,
|
||||
)
|
||||
from src.services.host_runaway_aiops_loop_readiness import (
|
||||
load_latest_host_runaway_aiops_loop_readiness,
|
||||
@@ -1028,44 +1027,6 @@ async def preflight_github_target_owner_response_intake(
|
||||
) from exc
|
||||
|
||||
|
||||
@router.post(
|
||||
"/github-target-safe-credential-evidence-reviewer-validation/validate-redacted-refs",
|
||||
response_model=dict[str, Any],
|
||||
summary="驗證 GitHub target safe credential 脫敏 evidence refs",
|
||||
description=(
|
||||
"針對單次 owner-provided redacted safe credential evidence refs 進行 no-persist "
|
||||
"reviewer validation,回傳 accepted / needs supplement / quarantined / rejected runtime "
|
||||
"action 分流。此端點不保存 payload、不呼叫 GitHub live API、不建立 repo、不改 visibility、"
|
||||
"不同步 refs、不觸發 workflow、不收 private clone URL credential 或任何 secret value,也不更新 "
|
||||
"safe credential accepted evidence 總帳。"
|
||||
),
|
||||
)
|
||||
async def validate_github_target_safe_credential_evidence_review(
|
||||
submission: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
"""回傳單次 GitHub safe credential 脫敏 evidence refs 公開安全驗證結果。"""
|
||||
try:
|
||||
payload = await asyncio.to_thread(
|
||||
validate_github_target_safe_credential_evidence_refs,
|
||||
submission,
|
||||
)
|
||||
return redact_public_lan_topology(payload)
|
||||
except FileNotFoundError as exc:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=str(exc),
|
||||
) from exc
|
||||
except (json.JSONDecodeError, ValueError) as exc:
|
||||
logger.error(
|
||||
"github_target_safe_credential_evidence_review_invalid",
|
||||
error=str(exc),
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="GitHub target safe credential evidence review 無效",
|
||||
) from exc
|
||||
|
||||
|
||||
@router.get(
|
||||
"/agent-12-agent-war-room",
|
||||
response_model=dict[str, Any],
|
||||
|
||||
@@ -52,7 +52,6 @@ def build_delivery_closure_workbench(
|
||||
"""Build the delivery workbench response from already validated snapshots."""
|
||||
status_summary = _dict(status_cleanup.get("summary"))
|
||||
github_summary = _dict(github.get("summary"))
|
||||
github_boundaries = _dict(github.get("operation_boundaries"))
|
||||
gitea_status = _dict(gitea.get("program_status"))
|
||||
gitea_rollups = _dict(gitea.get("rollups"))
|
||||
runtime_status = _dict(runtime.get("program_status"))
|
||||
@@ -62,18 +61,14 @@ def build_delivery_closure_workbench(
|
||||
|
||||
github_required = _int(github_summary.get("approval_required_target_count"))
|
||||
github_verified = _int(github_summary.get("private_backup_verified_count"))
|
||||
runtime_action_required = set(
|
||||
_strings(runtime_rollups.get("action_required_surface_ids"))
|
||||
)
|
||||
runtime_action_required = set(_strings(runtime_rollups.get("action_required_surface_ids")))
|
||||
runtime_secret_surfaces = set(_strings(runtime_rollups.get("secret_surface_ids")))
|
||||
|
||||
lanes = [
|
||||
{
|
||||
"id": "release",
|
||||
"source_id": "status_cleanup",
|
||||
"completion_percent": _percent(
|
||||
status_summary.get("overall_completion_percent")
|
||||
),
|
||||
"completion_percent": _percent(status_summary.get("overall_completion_percent")),
|
||||
"status": str(status_summary.get("dashboard_status") or "unknown"),
|
||||
"blocker_count": _int(status_summary.get("blocked_gate_count")),
|
||||
"metric": {
|
||||
@@ -98,20 +93,14 @@ def build_delivery_closure_workbench(
|
||||
"total": github_required,
|
||||
},
|
||||
"href": "/governance?tab=automation-inventory",
|
||||
"next_action": str(
|
||||
github.get("next_action") or _first_target_action(github.get("targets"))
|
||||
),
|
||||
"next_action": str(github.get("next_action") or _first_target_action(github.get("targets"))),
|
||||
},
|
||||
{
|
||||
"id": "gitea",
|
||||
"source_id": "gitea_ci_cd",
|
||||
"completion_percent": _percent(
|
||||
gitea_status.get("overall_completion_percent")
|
||||
),
|
||||
"completion_percent": _percent(gitea_status.get("overall_completion_percent")),
|
||||
"status": str(gitea_status.get("current_task_id") or "unknown"),
|
||||
"blocker_count": len(
|
||||
_strings(gitea_rollups.get("runner_contracts_requiring_action"))
|
||||
),
|
||||
"blocker_count": len(_strings(gitea_rollups.get("runner_contracts_requiring_action"))),
|
||||
"metric": {
|
||||
"kind": "workflow_count",
|
||||
"count": _int(gitea_rollups.get("total_workflows")),
|
||||
@@ -122,9 +111,7 @@ def build_delivery_closure_workbench(
|
||||
{
|
||||
"id": "runtime",
|
||||
"source_id": "runtime_surface",
|
||||
"completion_percent": _percent(
|
||||
runtime_status.get("overall_completion_percent")
|
||||
),
|
||||
"completion_percent": _percent(runtime_status.get("overall_completion_percent")),
|
||||
"status": str(runtime_status.get("current_task_id") or "unknown"),
|
||||
"blocker_count": len(runtime_action_required | runtime_secret_surfaces),
|
||||
"metric": {
|
||||
@@ -137,9 +124,7 @@ def build_delivery_closure_workbench(
|
||||
{
|
||||
"id": "backup",
|
||||
"source_id": "backup_dr",
|
||||
"completion_percent": _percent(
|
||||
backup_status.get("overall_completion_percent")
|
||||
),
|
||||
"completion_percent": _percent(backup_status.get("overall_completion_percent")),
|
||||
"status": str(backup_status.get("current_task_id") or "unknown"),
|
||||
"blocker_count": len(_strings(backup_rollups.get("blocked_row_ids"))),
|
||||
"metric": {
|
||||
@@ -152,9 +137,7 @@ def build_delivery_closure_workbench(
|
||||
]
|
||||
|
||||
for lane in lanes:
|
||||
lane["tone"] = _tone(
|
||||
_int(lane["blocker_count"]), _int(lane["completion_percent"])
|
||||
)
|
||||
lane["tone"] = _tone(_int(lane["blocker_count"]), _int(lane["completion_percent"]))
|
||||
|
||||
source_statuses = [
|
||||
_source_status("status_cleanup", status_cleanup),
|
||||
@@ -163,9 +146,7 @@ def build_delivery_closure_workbench(
|
||||
_source_status("runtime_surface", runtime),
|
||||
_source_status("backup_dr", backup),
|
||||
]
|
||||
generated_candidates = [
|
||||
source["generated_at"] for source in source_statuses if source["generated_at"]
|
||||
]
|
||||
generated_candidates = [source["generated_at"] for source in source_statuses if source["generated_at"]]
|
||||
loaded_source_count = sum(1 for source in source_statuses if source["loaded"])
|
||||
high_risk_blocker_count = sum(_int(lane["blocker_count"]) for lane in lanes)
|
||||
average_completion = _percent(
|
||||
@@ -185,28 +166,17 @@ def build_delivery_closure_workbench(
|
||||
return {
|
||||
"schema_version": _SCHEMA_VERSION,
|
||||
"generated_at": max(generated_candidates) if generated_candidates else "",
|
||||
"status": "blocked_delivery_actions_required"
|
||||
if high_risk_blocker_count
|
||||
else "ready",
|
||||
"status": "blocked_delivery_actions_required" if high_risk_blocker_count else "ready",
|
||||
"summary": {
|
||||
"source_count": len(source_statuses),
|
||||
"loaded_source_count": loaded_source_count,
|
||||
"average_completion_percent": average_completion,
|
||||
"high_risk_blocker_count": high_risk_blocker_count,
|
||||
"runtime_execution_authorized": False,
|
||||
"remote_write_authorized": github_boundaries.get("github_api_write_allowed")
|
||||
is True,
|
||||
"repo_creation_authorized": github_summary.get("repo_creation_authorized")
|
||||
is True,
|
||||
"visibility_change_authorized": github_summary.get(
|
||||
"visibility_change_authorized"
|
||||
)
|
||||
is True,
|
||||
"refs_sync_authorized": github_summary.get("refs_sync_authorized") is True,
|
||||
"workflow_trigger_authorized": github_summary.get(
|
||||
"workflow_trigger_authorized"
|
||||
)
|
||||
is True,
|
||||
"remote_write_authorized": False,
|
||||
"repo_creation_authorized": False,
|
||||
"refs_sync_authorized": False,
|
||||
"workflow_trigger_authorized": False,
|
||||
"secret_values_collected": False,
|
||||
},
|
||||
"source_statuses": source_statuses,
|
||||
@@ -215,19 +185,11 @@ def build_delivery_closure_workbench(
|
||||
"operation_boundaries": {
|
||||
"read_only_api_allowed": True,
|
||||
"runtime_write_allowed": False,
|
||||
"remote_write_allowed": github_boundaries.get("github_api_write_allowed")
|
||||
is True,
|
||||
"repo_creation_allowed": github_boundaries.get("repo_creation_allowed")
|
||||
is True,
|
||||
"visibility_change_allowed": github_boundaries.get(
|
||||
"visibility_change_allowed"
|
||||
)
|
||||
is True,
|
||||
"refs_sync_allowed": github_boundaries.get("refs_sync_allowed") is True,
|
||||
"workflow_trigger_allowed": github_boundaries.get(
|
||||
"workflow_trigger_allowed"
|
||||
)
|
||||
is True,
|
||||
"remote_write_allowed": False,
|
||||
"repo_creation_allowed": False,
|
||||
"visibility_change_allowed": False,
|
||||
"refs_sync_allowed": False,
|
||||
"workflow_trigger_allowed": False,
|
||||
"secret_value_collection_allowed": False,
|
||||
"backup_restore_execution_allowed": False,
|
||||
"active_scan_allowed": False,
|
||||
@@ -242,9 +204,7 @@ def _source_status(source_id: str, payload: dict[str, Any]) -> dict[str, Any]:
|
||||
"loaded": not source_missing,
|
||||
"schema_version": str(payload.get("schema_version") or ""),
|
||||
"generated_at": str(payload.get("generated_at") or ""),
|
||||
"missing_reason": str(payload.get("missing_reason") or "")
|
||||
if source_missing
|
||||
else "",
|
||||
"missing_reason": str(payload.get("missing_reason") or "") if source_missing else "",
|
||||
}
|
||||
|
||||
|
||||
@@ -258,9 +218,7 @@ def _load_github_private_backup_evidence_gate() -> dict[str, Any]:
|
||||
except ModuleNotFoundError as exc:
|
||||
if exc.name != "src.services.github_target_private_backup_evidence_gate":
|
||||
raise
|
||||
return _missing_github_private_backup_source(
|
||||
"service_module_missing_on_release_base"
|
||||
)
|
||||
return _missing_github_private_backup_source("service_module_missing_on_release_base")
|
||||
except FileNotFoundError:
|
||||
return _missing_github_private_backup_source("snapshot_missing_on_release_base")
|
||||
|
||||
@@ -297,7 +255,7 @@ def _dict(value: Any) -> dict[str, Any]:
|
||||
def _int(value: Any) -> int:
|
||||
if isinstance(value, bool):
|
||||
return int(value)
|
||||
if isinstance(value, int | float):
|
||||
if isinstance(value, (int, float)):
|
||||
return int(value)
|
||||
return 0
|
||||
|
||||
@@ -349,10 +307,7 @@ def _first_backup_action(value: Any) -> str:
|
||||
if not isinstance(value, list):
|
||||
return ""
|
||||
for row in value:
|
||||
if isinstance(row, dict) and row.get("overall_readiness") in {
|
||||
"blocked",
|
||||
"action_required",
|
||||
}:
|
||||
if isinstance(row, dict) and row.get("overall_readiness") in {"blocked", "action_required"}:
|
||||
return str(row.get("next_action") or "")
|
||||
return _first_row_action(value)
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import runpy
|
||||
from pathlib import Path
|
||||
|
||||
@@ -14,36 +13,3 @@ def test_awooop_controlled_automation_copy_guard_blocks_legacy_manual_gate_text(
|
||||
)
|
||||
|
||||
guard["validate"](ROOT)
|
||||
|
||||
|
||||
def test_awooop_controlled_automation_copy_guard_blocks_live_owner_review_copy(tmp_path: Path) -> None:
|
||||
guard = runpy.run_path(
|
||||
str(ROOT / "scripts" / "security" / "awooop-controlled-automation-copy-guard.py")
|
||||
)
|
||||
messages_path = tmp_path / "apps" / "web" / "messages" / "zh-TW.json"
|
||||
messages_path.parent.mkdir(parents=True)
|
||||
messages_path.write_text(
|
||||
json.dumps({"awooop": {"workItems": {"status": "Owner Review 等待人工"}}}),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
violations = guard["_collect_awooop_message_violations"](messages_path, tmp_path)
|
||||
|
||||
assert any("Owner Review" in violation for violation in violations)
|
||||
assert any("等待人工" in violation for violation in violations)
|
||||
|
||||
|
||||
def test_awooop_controlled_automation_copy_guard_allows_legacy_hitl_history(tmp_path: Path) -> None:
|
||||
guard = runpy.run_path(
|
||||
str(ROOT / "scripts" / "security" / "awooop-controlled-automation-copy-guard.py")
|
||||
)
|
||||
messages_path = tmp_path / "apps" / "web" / "messages" / "zh-TW.json"
|
||||
messages_path.parent.mkdir(parents=True)
|
||||
messages_path.write_text(
|
||||
json.dumps({"awooop": {"approvals": {"legacyHitl": {"title": "既有 HITL 待人工處理"}}}}),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
violations = guard["_collect_awooop_message_violations"](messages_path, tmp_path)
|
||||
|
||||
assert violations == []
|
||||
|
||||
@@ -19,11 +19,10 @@ def test_delivery_closure_workbench_endpoint_returns_product_summary():
|
||||
assert data["summary"]["source_count"] == 5
|
||||
assert data["summary"]["loaded_source_count"] == 5
|
||||
assert data["summary"]["runtime_execution_authorized"] is False
|
||||
assert data["summary"]["remote_write_authorized"] is True
|
||||
assert data["summary"]["repo_creation_authorized"] is True
|
||||
assert data["summary"]["visibility_change_authorized"] is True
|
||||
assert data["summary"]["refs_sync_authorized"] is True
|
||||
assert data["summary"]["workflow_trigger_authorized"] is True
|
||||
assert data["summary"]["remote_write_authorized"] is False
|
||||
assert data["summary"]["repo_creation_authorized"] is False
|
||||
assert data["summary"]["refs_sync_authorized"] is False
|
||||
assert data["summary"]["workflow_trigger_authorized"] is False
|
||||
assert data["summary"]["secret_values_collected"] is False
|
||||
assert data["summary"]["average_completion_percent"] >= 0
|
||||
assert data["summary"]["high_risk_blocker_count"] > 0
|
||||
@@ -37,16 +36,10 @@ def test_delivery_closure_workbench_endpoint_returns_product_summary():
|
||||
assert lanes["runtime"]["metric"]["kind"] == "surface_count"
|
||||
assert lanes["backup"]["metric"]["kind"] == "readiness_row_count"
|
||||
assert sources["github_private_backup"]["loaded"] is True
|
||||
assert (
|
||||
sources["github_private_backup"]["schema_version"]
|
||||
== "github_target_private_backup_evidence_gate_v1"
|
||||
)
|
||||
assert sources["github_private_backup"]["schema_version"] == "github_target_private_backup_evidence_gate_v1"
|
||||
assert sources["github_private_backup"]["missing_reason"] == ""
|
||||
assert lanes["github"]["blocker_count"] == 0
|
||||
assert (
|
||||
lanes["github"]["status"]
|
||||
== "owner_authorized_controlled_execution_preflight_ready"
|
||||
)
|
||||
assert lanes["github"]["blocker_count"] == 9
|
||||
assert lanes["github"]["status"] == "blocked_private_visibility_and_safe_credential_evidence_required"
|
||||
assert lanes["github"]["metric"]["verified"] == 4
|
||||
assert lanes["github"]["metric"]["total"] == 9
|
||||
assert all(0 <= lane["completion_percent"] <= 100 for lane in lanes.values())
|
||||
@@ -55,11 +48,11 @@ def test_delivery_closure_workbench_endpoint_returns_product_summary():
|
||||
boundaries = data["operation_boundaries"]
|
||||
assert boundaries["read_only_api_allowed"] is True
|
||||
assert boundaries["runtime_write_allowed"] is False
|
||||
assert boundaries["remote_write_allowed"] is True
|
||||
assert boundaries["repo_creation_allowed"] is True
|
||||
assert boundaries["visibility_change_allowed"] is True
|
||||
assert boundaries["refs_sync_allowed"] is True
|
||||
assert boundaries["workflow_trigger_allowed"] is True
|
||||
assert boundaries["remote_write_allowed"] is False
|
||||
assert boundaries["repo_creation_allowed"] is False
|
||||
assert boundaries["visibility_change_allowed"] is False
|
||||
assert boundaries["refs_sync_allowed"] is False
|
||||
assert boundaries["workflow_trigger_allowed"] is False
|
||||
assert boundaries["secret_value_collection_allowed"] is False
|
||||
assert boundaries["backup_restore_execution_allowed"] is False
|
||||
assert boundaries["active_scan_allowed"] is False
|
||||
|
||||
@@ -9,7 +9,6 @@ import pytest
|
||||
from src.services.github_target_private_backup_evidence_gate import (
|
||||
load_latest_github_target_private_backup_evidence_gate,
|
||||
preflight_github_target_owner_response_submission,
|
||||
validate_github_target_safe_credential_evidence_refs,
|
||||
)
|
||||
from src.services.snapshot_paths import default_security_dir
|
||||
|
||||
@@ -18,10 +17,11 @@ def test_load_github_target_private_backup_evidence_gate_from_committed_snapshot
|
||||
snapshot = load_latest_github_target_private_backup_evidence_gate()
|
||||
|
||||
assert snapshot["schema_version"] == "github_target_private_backup_evidence_gate_v1"
|
||||
assert snapshot["mode"] == "read_only_private_backup_evidence_gate"
|
||||
assert (
|
||||
snapshot["mode"] == "owner_authorized_controlled_execution_no_secret_plaintext"
|
||||
snapshot["status"]
|
||||
== "blocked_private_visibility_and_safe_credential_evidence_required"
|
||||
)
|
||||
assert snapshot["status"] == "owner_authorized_controlled_execution_preflight_ready"
|
||||
assert snapshot["summary"]["target_decision_count"] == 10
|
||||
assert snapshot["summary"]["approval_required_target_count"] == 9
|
||||
assert snapshot["summary"]["github_connector_owner_visible_repository_count"] == 4
|
||||
@@ -54,13 +54,6 @@ def test_load_github_target_private_backup_evidence_gate_from_committed_snapshot
|
||||
== 0
|
||||
)
|
||||
assert snapshot["summary"]["github_missing_target_refs_sync_ready_count"] == 0
|
||||
assert (
|
||||
snapshot["summary"][
|
||||
"github_missing_target_create_private_repo_authorized_count"
|
||||
]
|
||||
== 5
|
||||
)
|
||||
assert snapshot["summary"]["github_missing_target_refs_sync_authorized_count"] == 5
|
||||
assert snapshot["summary"]["private_backup_verified_count"] == 4
|
||||
assert snapshot["summary"]["private_visibility_verified_count"] == 4
|
||||
assert snapshot["summary"]["safe_credential_required_count"] == 9
|
||||
@@ -74,16 +67,6 @@ def test_load_github_target_private_backup_evidence_gate_from_committed_snapshot
|
||||
assert snapshot["summary"]["safe_credential_forbidden_payload_count"] == 15
|
||||
assert snapshot["summary"]["safe_credential_quarantine_lane_count"] == 1
|
||||
assert snapshot["summary"]["safe_credential_raw_payload_storage_allowed"] is False
|
||||
assert snapshot["summary"]["safe_credential_reviewer_validation_ready"] is True
|
||||
assert (
|
||||
snapshot["summary"]["safe_credential_reviewer_validation_required_field_count"]
|
||||
== 7
|
||||
)
|
||||
assert snapshot["summary"]["safe_credential_reviewer_validation_passed_count"] == 0
|
||||
assert (
|
||||
snapshot["summary"]["safe_credential_reviewer_validation_quarantined_count"]
|
||||
== 0
|
||||
)
|
||||
assert snapshot["summary"]["owner_response_request_ready"] is True
|
||||
assert snapshot["summary"]["owner_response_required_response_item_count"] == 9
|
||||
assert snapshot["summary"]["owner_response_requested_template_count"] == 9
|
||||
@@ -96,48 +79,17 @@ def test_load_github_target_private_backup_evidence_gate_from_committed_snapshot
|
||||
assert (
|
||||
snapshot["summary"]["github_target_owner_response_handoff_not_approval"] is True
|
||||
)
|
||||
assert snapshot["summary"]["owner_execution_authorization_received_count"] == 1
|
||||
assert snapshot["summary"]["owner_execution_authorized_target_count"] == 9
|
||||
assert (
|
||||
snapshot["summary"]["owner_execution_authorization_status"]
|
||||
== "owner_authorized_controlled_execution"
|
||||
)
|
||||
assert (
|
||||
snapshot["summary"]["owner_execution_authorization_source"]
|
||||
== "chat_authorization_2026-06-28_full_hard_gate_open"
|
||||
)
|
||||
assert (
|
||||
snapshot["summary"]["owner_execution_controlled_preflight_required_count"] == 9
|
||||
)
|
||||
assert snapshot["summary"]["post_execution_readback_required_count"] == 9
|
||||
assert snapshot["summary"]["execution_ready_count"] == 9
|
||||
assert snapshot["summary"]["blocked_target_count"] == 0
|
||||
assert snapshot["summary"]["blocked_target_count"] == 9
|
||||
assert snapshot["summary"]["public_repo_allowed"] is False
|
||||
assert snapshot["summary"]["repo_creation_authorized"] is True
|
||||
assert snapshot["summary"]["visibility_change_authorized"] is True
|
||||
assert snapshot["summary"]["refs_sync_authorized"] is True
|
||||
assert snapshot["summary"]["workflow_trigger_authorized"] is True
|
||||
assert snapshot["summary"]["github_primary_switch_authorized"] is False
|
||||
assert snapshot["summary"]["repo_creation_authorized"] is False
|
||||
assert snapshot["summary"]["visibility_change_authorized"] is False
|
||||
assert snapshot["summary"]["refs_sync_authorized"] is False
|
||||
assert snapshot["summary"]["secret_value_collection_allowed"] is False
|
||||
assert snapshot["operation_boundaries"]["read_only_api_allowed"] is True
|
||||
assert snapshot["operation_boundaries"]["github_api_write_allowed"] is True
|
||||
assert snapshot["operation_boundaries"]["repo_creation_allowed"] is True
|
||||
assert snapshot["operation_boundaries"]["visibility_change_allowed"] is True
|
||||
assert snapshot["operation_boundaries"]["refs_sync_allowed"] is True
|
||||
assert snapshot["operation_boundaries"]["workflow_trigger_allowed"] is True
|
||||
assert snapshot["operation_boundaries"]["repo_creation_allowed"] is False
|
||||
assert snapshot["operation_boundaries"]["visibility_change_allowed"] is False
|
||||
assert snapshot["operation_boundaries"]["refs_sync_allowed"] is False
|
||||
assert snapshot["operation_boundaries"]["secret_value_collection_allowed"] is False
|
||||
assert (
|
||||
snapshot["operation_boundaries"]["private_clone_url_collection_allowed"]
|
||||
is False
|
||||
)
|
||||
assert snapshot["authorization_flags"]["repo_creation_authorized"] is True
|
||||
assert snapshot["authorization_flags"]["visibility_change_authorized"] is True
|
||||
assert snapshot["authorization_flags"]["refs_sync_authorized"] is True
|
||||
assert snapshot["authorization_flags"]["workflow_trigger_authorized"] is True
|
||||
assert snapshot["authorization_flags"]["github_primary_switch_authorized"] is False
|
||||
assert (
|
||||
snapshot["authorization_flags"]["private_clone_url_collection_allowed"] is False
|
||||
)
|
||||
intake = snapshot["owner_response_intake_readiness"]
|
||||
assert (
|
||||
intake["status"]
|
||||
@@ -182,25 +134,6 @@ def test_load_github_target_private_backup_evidence_gate_from_committed_snapshot
|
||||
)
|
||||
assert "repo_archive" in safe_credential_intake["forbidden_payloads"]
|
||||
assert "git_object_pack" in safe_credential_intake["forbidden_payloads"]
|
||||
assert (
|
||||
safe_credential_intake["reviewer_validation_endpoint"]
|
||||
== "/api/v1/agents/github-target-safe-credential-evidence-reviewer-validation/validate-redacted-refs"
|
||||
)
|
||||
assert (
|
||||
safe_credential_intake["reviewer_validation_mode"]
|
||||
== "validate_redacted_evidence_refs_only_no_persist_no_github_write"
|
||||
)
|
||||
assert safe_credential_intake["reviewer_validation_ready"] is True
|
||||
assert safe_credential_intake["reviewer_validation_required_field_count"] == 7
|
||||
assert (
|
||||
"redacted_evidence_refs"
|
||||
in safe_credential_intake["reviewer_validation_required_fields"]
|
||||
)
|
||||
assert (
|
||||
"accept_redacted_evidence_refs_for_reviewer_validation"
|
||||
in safe_credential_intake["reviewer_validation_allowed_decisions"]
|
||||
)
|
||||
assert safe_credential_intake["reviewer_validation_no_persist"] is True
|
||||
assert (
|
||||
"redacted_metadata_pointer"
|
||||
in safe_credential_intake["allowed_evidence_ref_types"]
|
||||
@@ -226,7 +159,7 @@ def test_load_github_target_private_backup_evidence_gate_from_committed_snapshot
|
||||
)
|
||||
assert (
|
||||
targets["owenhytsai/awoooi"]["safe_credential_evidence_submission_status"]
|
||||
== "owner_execution_authorized_post_apply_readback_pending"
|
||||
== "waiting_redacted_evidence_ref"
|
||||
)
|
||||
assert (
|
||||
"redacted_metadata_pointer"
|
||||
@@ -247,10 +180,6 @@ def test_load_github_target_private_backup_evidence_gate_from_committed_snapshot
|
||||
is False
|
||||
)
|
||||
assert targets["owenhytsai/awoooi"]["owner_response_execution_authorized"] is False
|
||||
assert targets["owenhytsai/awoooi"]["owner_execution_authorized"] is True
|
||||
assert targets["owenhytsai/awoooi"]["controlled_execution_ready"] is True
|
||||
assert targets["owenhytsai/awoooi"]["refs_sync_ready"] is True
|
||||
assert targets["owenhytsai/awoooi"]["blockers"] == []
|
||||
assert (
|
||||
"canonical_source"
|
||||
in targets["owenhytsai/awoooi"]["owner_response_required_fields"]
|
||||
@@ -267,9 +196,6 @@ def test_load_github_target_private_backup_evidence_gate_from_committed_snapshot
|
||||
is False
|
||||
)
|
||||
assert targets["owenhytsai/ewoooc"]["missing_target_refs_sync_ready"] is False
|
||||
assert targets["owenhytsai/ewoooc"]["repo_creation_authorized"] is True
|
||||
assert targets["owenhytsai/ewoooc"]["refs_sync_authorized"] is True
|
||||
assert targets["owenhytsai/ewoooc"]["execution_ready"] is True
|
||||
assert targets["owenhytsai/ewoooc"]["private_backup_verified"] is False
|
||||
assert (
|
||||
targets["owenhytsai/ewoooc"]["owner_response_template_id"]
|
||||
@@ -396,21 +322,6 @@ def test_github_target_private_backup_gate_rejects_missing_source_write_flags(tm
|
||||
load_latest_github_target_private_backup_evidence_gate(tmp_path)
|
||||
|
||||
|
||||
def test_github_target_private_backup_gate_rejects_execution_authorization_secrets(
|
||||
tmp_path,
|
||||
):
|
||||
_copy_security_snapshots(tmp_path)
|
||||
authorization_path = (
|
||||
tmp_path / "github-target-owner-execution-authorization.snapshot.json"
|
||||
)
|
||||
authorization = json.loads(authorization_path.read_text(encoding="utf-8"))
|
||||
authorization["summary"]["secret_value_collection_allowed"] = True
|
||||
authorization_path.write_text(json.dumps(authorization), encoding="utf-8")
|
||||
|
||||
with pytest.raises(ValueError, match="forbidden authorization flags"):
|
||||
load_latest_github_target_private_backup_evidence_gate(tmp_path)
|
||||
|
||||
|
||||
def test_github_target_owner_response_preflight_accepts_redacted_evidence_refs():
|
||||
preflight = preflight_github_target_owner_response_submission(
|
||||
_valid_owner_response_submission()
|
||||
@@ -421,9 +332,7 @@ def test_github_target_owner_response_preflight_accepts_redacted_evidence_refs()
|
||||
== "github_target_owner_response_intake_preflight_v1"
|
||||
)
|
||||
assert preflight["status"] == "ready_for_read_only_owner_response_intake"
|
||||
assert (
|
||||
preflight["mode"] == "validate_owner_response_only_no_persist_no_github_write"
|
||||
)
|
||||
assert preflight["mode"] == "validate_owner_response_only_no_persist_no_github_write"
|
||||
assert preflight["summary"]["candidate_response_item_count"] == 1
|
||||
assert preflight["summary"]["preflight_passed_response_item_count"] == 1
|
||||
assert preflight["summary"]["preflight_blocked_response_item_count"] == 0
|
||||
@@ -435,13 +344,8 @@ def test_github_target_owner_response_preflight_accepts_redacted_evidence_refs()
|
||||
assert preflight["summary"]["refs_sync_authorized"] is False
|
||||
assert preflight["operation_boundaries"]["persist_submission_allowed"] is False
|
||||
assert preflight["operation_boundaries"]["github_api_write_allowed"] is False
|
||||
assert (
|
||||
preflight["operation_boundaries"]["private_clone_url_collection_allowed"]
|
||||
is False
|
||||
)
|
||||
assert (
|
||||
preflight["authorization_flags"]["owner_response_execution_authorized"] is False
|
||||
)
|
||||
assert preflight["operation_boundaries"]["private_clone_url_collection_allowed"] is False
|
||||
assert preflight["authorization_flags"]["owner_response_execution_authorized"] is False
|
||||
assert preflight["responses"][0]["accepted_for_read_only_intake"] is True
|
||||
assert preflight["responses"][0]["owner_response_received"] is False
|
||||
assert preflight["responses"][0]["owner_response_accepted"] is False
|
||||
@@ -449,12 +353,12 @@ def test_github_target_owner_response_preflight_accepts_redacted_evidence_refs()
|
||||
|
||||
def test_github_target_owner_response_preflight_blocks_credentials_and_commands():
|
||||
submission = _valid_owner_response_submission()
|
||||
submission["responses"][0][
|
||||
"private_clone_url_credential"
|
||||
] = "https://owner:ghp_1234567890abcdefghijklmnopqrstu@github.com/owenhytsai/awoooi.git"
|
||||
submission["responses"][0][
|
||||
"repo_creation_command"
|
||||
] = "gh repo create owenhytsai/awoooi --private"
|
||||
submission["responses"][0]["private_clone_url_credential"] = (
|
||||
"https://owner:ghp_1234567890abcdefghijklmnopqrstu@github.com/owenhytsai/awoooi.git"
|
||||
)
|
||||
submission["responses"][0]["repo_creation_command"] = (
|
||||
"gh repo create owenhytsai/awoooi --private"
|
||||
)
|
||||
|
||||
preflight = preflight_github_target_owner_response_submission(submission)
|
||||
|
||||
@@ -474,92 +378,6 @@ def test_github_target_owner_response_preflight_blocks_credentials_and_commands(
|
||||
assert response["repo_creation_authorized"] is False
|
||||
|
||||
|
||||
def test_github_target_safe_credential_evidence_review_accepts_redacted_refs():
|
||||
payload = validate_github_target_safe_credential_evidence_refs(
|
||||
_valid_safe_credential_evidence_ref_submission()
|
||||
)
|
||||
|
||||
assert (
|
||||
payload["schema_version"]
|
||||
== "github_target_safe_credential_evidence_ref_validation_result_v1"
|
||||
)
|
||||
assert (
|
||||
payload["status"]
|
||||
== "accepted_for_readonly_safe_credential_evidence_review_only"
|
||||
)
|
||||
assert (
|
||||
payload["mode"]
|
||||
== "validate_redacted_evidence_refs_only_no_persist_no_github_write"
|
||||
)
|
||||
assert payload["accepted_for_readonly_review"] is True
|
||||
assert payload["reviewer_validation_passed"] is True
|
||||
assert payload["summary"]["candidate_evidence_ref_item_count"] == 1
|
||||
assert payload["summary"]["reviewer_validation_passed_count"] == 1
|
||||
assert payload["summary"]["safe_credential_evidence_submission_accepted_count"] == 1
|
||||
assert payload["summary"]["safe_credential_accepted_evidence_count"] == 0
|
||||
assert payload["summary"]["private_backup_verified_count"] == 4
|
||||
assert payload["summary"]["execution_ready_count"] == 9
|
||||
assert payload["summary"]["blocked_target_count"] == 0
|
||||
assert payload["boundaries"]["payload_persisted"] is False
|
||||
assert payload["boundaries"]["safe_credential_accepted_updated"] is False
|
||||
assert payload["boundaries"]["runtime_execution_authorized"] is False
|
||||
assert payload["boundaries"]["private_clone_url_collection_allowed"] is False
|
||||
assert (
|
||||
payload["evidence_ref_submissions"][0]["accepted_for_readonly_review"] is True
|
||||
)
|
||||
assert (
|
||||
payload["evidence_ref_submissions"][0]["safe_credential_evidence_accepted"]
|
||||
is False
|
||||
)
|
||||
|
||||
|
||||
def test_github_target_safe_credential_evidence_review_quarantines_sensitive_refs():
|
||||
submission = _valid_safe_credential_evidence_ref_submission()
|
||||
secret_url = (
|
||||
"https://owner:ghp_1234567890abcdefghijklmnopqrstu@github.com/"
|
||||
"owenhytsai/awoooi.git"
|
||||
)
|
||||
submission["evidence_ref_submissions"][0]["redacted_evidence_refs"] = [secret_url]
|
||||
submission["evidence_ref_submissions"][0]["repo_archive"] = "repo_archive"
|
||||
|
||||
payload = validate_github_target_safe_credential_evidence_refs(submission)
|
||||
|
||||
assert payload["status"] == "quarantine_sensitive_payload"
|
||||
assert payload["quarantined"] is True
|
||||
assert payload["summary"]["reviewer_validation_quarantined_count"] == 1
|
||||
assert payload["summary"]["safe_credential_accepted_evidence_count"] == 0
|
||||
assert (
|
||||
payload["evidence_ref_submissions"][0]["accepted_for_readonly_review"] is False
|
||||
)
|
||||
assert (
|
||||
"forbidden_payload_detected"
|
||||
in payload["evidence_ref_submissions"][0]["blockers"]
|
||||
)
|
||||
assert (
|
||||
"unsafe_evidence_ref_detected"
|
||||
in payload["evidence_ref_submissions"][0]["blockers"]
|
||||
)
|
||||
assert secret_url not in str(payload)
|
||||
assert "ghp_1234567890abcdefghijklmnopqrstu" not in str(payload)
|
||||
|
||||
|
||||
def test_github_target_safe_credential_evidence_review_rejects_runtime_requests():
|
||||
submission = _valid_safe_credential_evidence_ref_submission()
|
||||
submission["evidence_ref_submissions"][0]["requested_actions"] = ["refs_sync"]
|
||||
|
||||
payload = validate_github_target_safe_credential_evidence_refs(submission)
|
||||
|
||||
assert payload["status"] == "reject_runtime_action_request"
|
||||
assert payload["runtime_action_rejected"] is True
|
||||
assert payload["summary"]["runtime_action_rejected_count"] == 1
|
||||
assert payload["summary"]["safe_credential_accepted_evidence_count"] == 0
|
||||
assert payload["boundaries"]["refs_sync_authorized"] is False
|
||||
assert (
|
||||
"runtime_action_request_detected"
|
||||
in payload["evidence_ref_submissions"][0]["blockers"]
|
||||
)
|
||||
|
||||
|
||||
def _copy_security_snapshots(tmp_path: Path) -> None:
|
||||
source_dir = default_security_dir(Path(__file__))
|
||||
for filename in (
|
||||
@@ -569,7 +387,6 @@ def _copy_security_snapshots(tmp_path: Path) -> None:
|
||||
"github-target-probe.snapshot.json",
|
||||
"github-target-connector-readback.snapshot.json",
|
||||
"github-target-missing-source-readiness.snapshot.json",
|
||||
"github-target-owner-execution-authorization.snapshot.json",
|
||||
):
|
||||
shutil.copy(source_dir / filename, tmp_path / filename)
|
||||
|
||||
@@ -603,26 +420,3 @@ def _valid_owner_response_submission() -> dict[str, object]:
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def _valid_safe_credential_evidence_ref_submission() -> dict[str, object]:
|
||||
return {
|
||||
"submission_mode": "redacted_metadata_pointer",
|
||||
"evidence_ref_submissions": [
|
||||
{
|
||||
"template_id": "target-awoooi-refs-blocked",
|
||||
"github_repo": "owenhytsai/awoooi",
|
||||
"reviewer_role_or_team": "platform-owner",
|
||||
"decision": "accept_redacted_evidence_refs_for_reviewer_validation",
|
||||
"decision_reason": "Refs are redacted metadata pointers only.",
|
||||
"evidence_ref_type": "redacted_metadata_pointer",
|
||||
"redacted_evidence_refs": [
|
||||
"docs/security/GITEA-GITHUB-MIGRATION-SNAPSHOT.md",
|
||||
"owner-metadata:github-target-awoooi-redacted-ref",
|
||||
],
|
||||
"affected_scope": "awoooi github private backup target",
|
||||
"followup_owner": "platform-owner",
|
||||
"validation_plan": "review redacted refs only; no GitHub write",
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
@@ -16,8 +16,7 @@ def test_github_target_private_backup_evidence_gate_endpoint_returns_read_only_g
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["schema_version"] == "github_target_private_backup_evidence_gate_v1"
|
||||
assert data["mode"] == "owner_authorized_controlled_execution_no_secret_plaintext"
|
||||
assert data["status"] == "owner_authorized_controlled_execution_preflight_ready"
|
||||
assert data["mode"] == "read_only_private_backup_evidence_gate"
|
||||
assert data["summary"]["approval_required_target_count"] == 9
|
||||
assert data["summary"]["github_connector_readback_count"] == 9
|
||||
assert data["summary"]["github_connector_private_visibility_count"] == 4
|
||||
@@ -26,11 +25,6 @@ def test_github_target_private_backup_evidence_gate_endpoint_returns_read_only_g
|
||||
assert data["summary"]["github_missing_target_gitea_source_candidate_count"] == 3
|
||||
assert data["summary"]["github_missing_target_create_private_repo_ready_count"] == 0
|
||||
assert data["summary"]["github_missing_target_refs_sync_ready_count"] == 0
|
||||
assert (
|
||||
data["summary"]["github_missing_target_create_private_repo_authorized_count"]
|
||||
== 5
|
||||
)
|
||||
assert data["summary"]["github_missing_target_refs_sync_authorized_count"] == 5
|
||||
assert data["summary"]["private_backup_verified_count"] == 4
|
||||
assert data["summary"]["private_visibility_verified_count"] == 4
|
||||
assert data["summary"]["safe_credential_evidence_intake_ready"] is True
|
||||
@@ -40,11 +34,6 @@ def test_github_target_private_backup_evidence_gate_endpoint_returns_read_only_g
|
||||
assert data["summary"]["safe_credential_forbidden_payload_count"] == 15
|
||||
assert data["summary"]["safe_credential_quarantine_lane_count"] == 1
|
||||
assert data["summary"]["safe_credential_raw_payload_storage_allowed"] is False
|
||||
assert data["summary"]["safe_credential_reviewer_validation_ready"] is True
|
||||
assert (
|
||||
data["summary"]["safe_credential_reviewer_validation_required_field_count"] == 7
|
||||
)
|
||||
assert data["summary"]["safe_credential_reviewer_validation_passed_count"] == 0
|
||||
assert data["summary"]["owner_response_request_ready"] is True
|
||||
assert data["summary"]["owner_response_required_response_item_count"] == 9
|
||||
assert data["summary"]["owner_response_requested_template_count"] == 9
|
||||
@@ -52,22 +41,18 @@ def test_github_target_private_backup_evidence_gate_endpoint_returns_read_only_g
|
||||
assert data["summary"]["owner_response_collection_check_count"] == 6
|
||||
assert data["summary"]["owner_response_intake_preflight_check_count"] == 6
|
||||
assert data["summary"]["owner_response_request_execution_authorized"] is False
|
||||
assert data["summary"]["owner_execution_authorization_received_count"] == 1
|
||||
assert data["summary"]["owner_execution_authorized_target_count"] == 9
|
||||
assert data["summary"]["execution_ready_count"] == 9
|
||||
assert data["summary"]["blocked_target_count"] == 0
|
||||
assert data["summary"]["blocked_target_count"] == 9
|
||||
assert data["summary"]["public_repo_allowed"] is False
|
||||
assert data["summary"]["repo_creation_authorized"] is True
|
||||
assert data["summary"]["visibility_change_authorized"] is True
|
||||
assert data["summary"]["refs_sync_authorized"] is True
|
||||
assert data["summary"]["workflow_trigger_authorized"] is True
|
||||
assert data["summary"]["repo_creation_authorized"] is False
|
||||
assert data["summary"]["visibility_change_authorized"] is False
|
||||
assert data["summary"]["refs_sync_authorized"] is False
|
||||
assert data["summary"]["secret_value_collection_allowed"] is False
|
||||
assert data["operation_boundaries"]["read_only_api_allowed"] is True
|
||||
assert data["operation_boundaries"]["github_api_write_allowed"] is True
|
||||
assert data["operation_boundaries"]["repo_creation_allowed"] is True
|
||||
assert data["operation_boundaries"]["visibility_change_allowed"] is True
|
||||
assert data["operation_boundaries"]["refs_sync_allowed"] is True
|
||||
assert data["operation_boundaries"]["workflow_trigger_allowed"] is True
|
||||
assert data["operation_boundaries"]["github_api_write_allowed"] is False
|
||||
assert data["operation_boundaries"]["repo_creation_allowed"] is False
|
||||
assert data["operation_boundaries"]["visibility_change_allowed"] is False
|
||||
assert data["operation_boundaries"]["refs_sync_allowed"] is False
|
||||
assert data["operation_boundaries"]["workflow_trigger_allowed"] is False
|
||||
assert data["operation_boundaries"]["private_clone_url_collection_allowed"] is False
|
||||
intake = data["owner_response_intake_readiness"]
|
||||
assert (
|
||||
@@ -96,17 +81,10 @@ def test_github_target_private_backup_evidence_gate_endpoint_returns_read_only_g
|
||||
assert safe_credential_intake["secret_value_collection_allowed"] is False
|
||||
assert safe_credential_intake["execution_authorized"] is False
|
||||
assert safe_credential_intake["not_approval"] is True
|
||||
assert safe_credential_intake["reviewer_validation_ready"] is True
|
||||
assert (
|
||||
safe_credential_intake["reviewer_validation_endpoint"]
|
||||
== "/api/v1/agents/github-target-safe-credential-evidence-reviewer-validation/validate-redacted-refs"
|
||||
)
|
||||
assert data["targets"][0]["owner_response_execution_authorized"] is False
|
||||
assert data["targets"][0]["owner_execution_authorized"] is True
|
||||
assert data["targets"][0]["controlled_execution_ready"] is True
|
||||
assert (
|
||||
data["targets"][0]["safe_credential_evidence_submission_status"]
|
||||
== "owner_execution_authorized_post_apply_readback_pending"
|
||||
== "waiting_redacted_evidence_ref"
|
||||
)
|
||||
assert data["targets"][0]["safe_credential_raw_payload_storage_allowed"] is False
|
||||
assert (
|
||||
@@ -171,57 +149,3 @@ def test_github_target_owner_response_intake_preflight_endpoint_blocks_secrets()
|
||||
assert data["responses"][0]["accepted_for_read_only_intake"] is False
|
||||
assert "forbidden_payload_detected" in data["responses"][0]["blockers"]
|
||||
assert "192.168.0." not in response.text
|
||||
|
||||
|
||||
def test_github_target_safe_credential_evidence_review_api_does_not_persist():
|
||||
app = FastAPI()
|
||||
app.include_router(router, prefix="/api/v1")
|
||||
client = TestClient(app)
|
||||
|
||||
response = client.post(
|
||||
"/api/v1/agents/github-target-safe-credential-evidence-reviewer-validation/validate-redacted-refs",
|
||||
json={
|
||||
"submission_mode": "redacted_metadata_pointer",
|
||||
"evidence_ref_submissions": [
|
||||
{
|
||||
"template_id": "target-awoooi-refs-blocked",
|
||||
"github_repo": "owenhytsai/awoooi",
|
||||
"reviewer_role_or_team": "platform-owner",
|
||||
"decision": "accept_redacted_evidence_refs_for_reviewer_validation",
|
||||
"decision_reason": "Refs are redacted metadata pointers only.",
|
||||
"evidence_ref_type": "redacted_metadata_pointer",
|
||||
"redacted_evidence_refs": [
|
||||
"docs/security/GITEA-GITHUB-MIGRATION-SNAPSHOT.md",
|
||||
"owner-metadata:github-target-awoooi-redacted-ref",
|
||||
],
|
||||
"affected_scope": "awoooi github private backup target",
|
||||
"followup_owner": "platform-owner",
|
||||
"validation_plan": "review redacted refs only; no GitHub write",
|
||||
}
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
result = response.json()
|
||||
assert (
|
||||
result["status"] == "accepted_for_readonly_safe_credential_evidence_review_only"
|
||||
)
|
||||
assert result["summary"]["reviewer_validation_passed_count"] == 1
|
||||
assert result["summary"]["safe_credential_accepted_evidence_count"] == 0
|
||||
assert result["boundaries"]["payload_persisted"] is False
|
||||
assert result["boundaries"]["github_api_write_allowed"] is False
|
||||
assert result["boundaries"]["private_clone_url_collection_allowed"] is False
|
||||
|
||||
readback = client.get(
|
||||
"/api/v1/agents/github-target-private-backup-evidence-gate"
|
||||
).json()
|
||||
assert readback["summary"]["safe_credential_accepted_evidence_count"] == 0
|
||||
assert readback["summary"]["safe_credential_reviewer_validation_passed_count"] == 0
|
||||
assert (
|
||||
readback["summary"]["github_missing_target_create_private_repo_ready_count"]
|
||||
== 0
|
||||
)
|
||||
assert readback["summary"]["github_missing_target_refs_sync_ready_count"] == 0
|
||||
assert readback["summary"]["execution_ready_count"] == 9
|
||||
assert "192.168.0." not in response.text
|
||||
|
||||
@@ -8505,7 +8505,7 @@
|
||||
"detail": "來源事件是否回到 Run / Incident"
|
||||
},
|
||||
"gate": {
|
||||
"label": "AI 受控 Gate",
|
||||
"label": "人工 Gate",
|
||||
"detail": "待處理工作 {work}"
|
||||
},
|
||||
"verify": {
|
||||
@@ -8569,7 +8569,7 @@
|
||||
"autoRepairedVerified": "已驗證自動修復",
|
||||
"executionUnverified": "已執行但未驗證",
|
||||
"executionFailed": "執行失敗",
|
||||
"manualRequiredNoAction": "AI 受控補齊:NO_ACTION",
|
||||
"manualRequiredNoAction": "人工介入:NO_ACTION",
|
||||
"approvalRequired": "等待審批",
|
||||
"observedNotExecuted": "已觀測但未執行",
|
||||
"receivedOnly": "僅收到告警"
|
||||
@@ -8730,7 +8730,7 @@
|
||||
},
|
||||
"readinessRefs": {
|
||||
"primaryReadiness": "GitHub 主要來源就緒度閘門仍是候選狀態,不能觸發專案庫建立或可見性變更。",
|
||||
"ownerValidation": "負責人回覆驗證彙總顯示四包回覆資料都仍等待負責人脫敏回覆與受控驗收。",
|
||||
"ownerValidation": "負責人回覆驗證彙總顯示四包回覆資料都仍等待人工回覆與驗收。",
|
||||
"rollbackAdr": "回復架構決策紀錄尚未完成負責人批准的演練,因此不能把 GitHub 切為主要來源。",
|
||||
"workflowInventory": "工作流程 / 機密名稱清冊只收集名稱與路由,不收機密明文值、不改 GitHub 機密設定。"
|
||||
}
|
||||
@@ -8810,7 +8810,7 @@
|
||||
},
|
||||
"reviewerChecklist": {
|
||||
"label": "審查清單",
|
||||
"detail": "9 個清單項目給受控驗收流程判讀。"
|
||||
"detail": "9 個清單項目給人工審查者判讀。"
|
||||
},
|
||||
"reviewerOutcomes": {
|
||||
"label": "審查結果",
|
||||
@@ -8911,7 +8911,7 @@
|
||||
},
|
||||
"owner": {
|
||||
"title": "接手",
|
||||
"detail": "AI 受控閘門與受控 review"
|
||||
"detail": "AI 受控閘門與負責人審查"
|
||||
},
|
||||
"verifier": {
|
||||
"title": "驗證",
|
||||
@@ -8988,7 +8988,7 @@
|
||||
},
|
||||
"learning": {
|
||||
"title": "KM / Trust 回寫",
|
||||
"detail": "確認 KM、PlayBook trust 與學習責任已沉澱,避免下次重複 AI 補齊判斷。"
|
||||
"detail": "確認 KM、PlayBook trust 與學習責任已沉澱,避免下次重複人工判斷。"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -9035,7 +9035,7 @@
|
||||
"items": {
|
||||
"km": {
|
||||
"title": "Knowledge Base",
|
||||
"detail": "Hermes 草稿、陳舊 KM、AI 受控 review 與 stale ratio 回測。",
|
||||
"detail": "Hermes 草稿、陳舊 KM、負責人審查 與 stale ratio 回測。",
|
||||
"next": "下一步:把待補 KM 與 Incident / PlayBook / verifier 結果綁定。"
|
||||
},
|
||||
"playbook": {
|
||||
@@ -9061,7 +9061,7 @@
|
||||
},
|
||||
"sources": {
|
||||
"knowledgeBase": "Knowledge Base",
|
||||
"ownerReview": "Controlled review",
|
||||
"ownerReview": "Owner review",
|
||||
"staleRatio": "Stale ratio",
|
||||
"statusChain": "Status-chain",
|
||||
"remediationQueue": "補救佇列",
|
||||
@@ -9078,17 +9078,17 @@
|
||||
}
|
||||
},
|
||||
"reportSourceGapOwnerReview": {
|
||||
"eyebrow": "報表資料源 AI 受控補齊",
|
||||
"eyebrow": "報表資料源 負責人審查",
|
||||
"title": "報表資料源 PlayBook / Verifier 處置板",
|
||||
"subtitle": "把 report-source-gap 從報表頁接到 Work Items:每個缺口都要有 PlayBook 草案、Verifier 計畫、腳本 readback、排程無發送與 AI 受控 review。",
|
||||
"loading": "正在讀取 report-source-gap AI 受控補齊 read model。",
|
||||
"subtitle": "把 report-source-gap 從報表頁接到 Work Items:每個缺口都要有 PlayBook 草案、Verifier 計畫、腳本 readback、排程 無發送 與 負責人審查。",
|
||||
"loading": "正在讀取 report-source-gap 負責人審查 read model。",
|
||||
"unavailable": "報表資料源 read model 尚未回應;不能把報表全 0 或缺資料判定為健康。",
|
||||
"empty": "目前沒有 report-source-gap AI 受控補齊卡。",
|
||||
"empty": "目前沒有 report-source-gap 負責人審查 卡。",
|
||||
"boundaryTitle": "不可誤讀合約",
|
||||
"boundary": "live Telegram 發送={live};runtime gate={gate}。這裡只做草案與 AI 受控 review 讀回,不發送、不排程、不執行。",
|
||||
"boundary": "live Telegram 發送={live};runtime gate={gate}。這裡只做草案與 負責人審查讀回,不發送、不排程、不執行。",
|
||||
"openReports": "回報表總控",
|
||||
"ownerRequired": "需 AI 受控補齊",
|
||||
"ownerOptional": "AI 受控補齊可後補",
|
||||
"ownerRequired": "需 負責人審查",
|
||||
"ownerOptional": "負責人審查 可後補",
|
||||
"scheduleBoundary": "排程仍維持 無發送 preview",
|
||||
"fieldsTitle": "PlayBook 必填欄位",
|
||||
"checksTitle": "Verifier 檢查",
|
||||
@@ -9154,7 +9154,7 @@
|
||||
"title": "AI Provider primary lane 修復工作項"
|
||||
},
|
||||
"reportSourceGapOwnerReview": {
|
||||
"title": "報表資料源 PlayBook / Verifier AI 受控補齊"
|
||||
"title": "報表資料源 PlayBook / Verifier 負責人審查"
|
||||
},
|
||||
"configDriftFsm": {
|
||||
"title": "Config Drift fingerprint 狀態機"
|
||||
@@ -9166,7 +9166,7 @@
|
||||
"title": "Telegram 詳情 / 歷史改為 DB 真相優先"
|
||||
},
|
||||
"callbackOwnerReview": {
|
||||
"title": "Callback 未匹配 KM Controlled Review 工作項"
|
||||
"title": "Callback 未匹配 KM Owner Review 工作項"
|
||||
},
|
||||
"callbackTraceRecoveryBacklog": {
|
||||
"title": "Callback trace 復原 backlog"
|
||||
@@ -9204,9 +9204,9 @@
|
||||
"autoRepair": "必須同時有 auto_repair、verification_result=success與KM 回寫",
|
||||
"recurrenceWorkItems": "Run 完成無修復、修復失敗與 AI 受控閘門必須進入可追蹤工作項",
|
||||
"aiRouteRepairWorkItem": "Provider lane 降級時必須顯示 evidence、owner、PlayBook候選與是否可自動修復",
|
||||
"reportSourceGapOwnerReview": "每個 report-source-gap 必須有 PlayBook 草案、Verifier 計畫、腳本 readback、排程無發送與 AI 受控 review;不得把全 0 當健康或自動執行授權",
|
||||
"reportSourceGapOwnerReview": "每個 report-source-gap 必須有 PlayBook 草案、Verifier 計畫、腳本 readback、排程 無發送 與 負責人審查;不得把全 0 當健康或自動執行授權",
|
||||
"configDriftFsm": "同一 drift fingerprint 必須顯示重複、PR、零 diff、交接與下一步",
|
||||
"remediationQueue": "每筆 degraded / failed / timeout都必須映射到重跑、重驗、Ticket或 AI 受控檢查",
|
||||
"remediationQueue": "每筆 degraded / failed / timeout都必須映射到重跑、重驗、Ticket或人工檢查",
|
||||
"telegramCallbacks": "按下詳情與歷史不能再只依賴 Redis TTL或舊快照",
|
||||
"callbackOwnerReview": "Telegram 詳情 / 歷史若未連到 KM owner-review,必須變成可追蹤工作項",
|
||||
"callbackTraceRecoveryBacklog": "Callback trace 缺口必須顯示復原訊號、24h decay與backlog 下一步",
|
||||
@@ -9260,7 +9260,7 @@
|
||||
"driftFingerprintNext": "下一步:{step}",
|
||||
"driftFingerprintRemediation": "修復:{kind} / {status};驗證 Report:{report}",
|
||||
"driftFingerprintEmpty": "尚無 Config Drift fingerprint 狀態",
|
||||
"remediationQueue": "補救工作:{total};AI可接手:{ready};AI補齊:{human}",
|
||||
"remediationQueue": "補救工作:{total};AI可接手:{ready};人工:{human}",
|
||||
"telegramCallbacks": "目前修補 Telegram callback 查詢鏈與歷史摘要",
|
||||
"telegramCallbacksLive": "只讀 callback toast 400 已非致命;詳情 / 歷史改由 DB 真相鏈 回覆",
|
||||
"callbackOwnerReview": "Callback owner-review 缺口:{open} 個 open;callback evidence:{total}",
|
||||
@@ -9273,7 +9273,7 @@
|
||||
"callbackOwnerReviewBlocker": "卡點:{reason}",
|
||||
"callbackOwnerReviewEmpty": "近期 callback evidence 均已匹配或尚無資料",
|
||||
"callbackTraceRecoveryBacklog": "Callback trace backlog:缺 trace {missing};1h {recent1h};24h {recent24h};gap 後 traced {recovered};復原 {status}",
|
||||
"callbackTraceRecoveryAction": "接續處理:{action};需AI補齊={human}",
|
||||
"callbackTraceRecoveryAction": "接續處理:{action};需要人工={human}",
|
||||
"callbackTraceRecoveryOwner": "主責:AwoooP Callback Evidence;協作:TelegramGateway / 執行時間線",
|
||||
"callbackTraceRecoveryEvidenceSurface": "查證入口:Runs / TG Callback 證據",
|
||||
"callbackTraceRecoveryClosure": "關閉條件:1h=0 且 24h=0;目前 1h {recent1h} / 24h {recent24h}",
|
||||
@@ -9285,7 +9285,7 @@
|
||||
"governanceUnavailable": "治理事件 API 目前無法回應;待派送:{queued}",
|
||||
"governanceQueueMissing": "治理 dispatch 表尚未就緒;未解治理告警:{unresolved}",
|
||||
"knowledgeHealthcheck": "KM healthcheck 派工:{total};目前階段:{stage}",
|
||||
"knowledgeOwner": "主責:{lead};受控覆核:{human}",
|
||||
"knowledgeOwner": "主責:{lead};人工覆核:{human}",
|
||||
"knowledgeNext": "下一步:{action}",
|
||||
"knowledgeDrafts": "KM 審核草稿:{drafts};重複草稿:{duplicates}",
|
||||
"knowledgeStaleCandidates": "陳舊 KM 優先清單:{total} 筆;最高 {top} / {tier}",
|
||||
@@ -9358,7 +9358,7 @@
|
||||
"investigateActiveGap": "仍有新缺口,檢查新 Telegram reply_markup trace 寫入",
|
||||
"verifyInstrumentation": "沒有復原訊號,檢查 TelegramGateway / 時間線觀測埋點",
|
||||
"waitDecay": "等待舊 backlog 24h decay,不需 AI 補齊處理",
|
||||
"observeRecovery": "觀察復原訊號,先不開受控任務"
|
||||
"observeRecovery": "觀察復原訊號,先不開人工任務"
|
||||
},
|
||||
"claim": {
|
||||
"ready": "完整自動修復聲明:可宣稱",
|
||||
@@ -9390,7 +9390,7 @@
|
||||
"stage": "階段:{stage}",
|
||||
"next": "下一步:{action}",
|
||||
"lead": "主責:{agent}",
|
||||
"human": "受控覆核:{owner}",
|
||||
"human": "人工覆核:{owner}",
|
||||
"support": "支援:{agents}",
|
||||
"worker": "Worker 狀態:{status}",
|
||||
"draft": "KM 草稿:{id}",
|
||||
@@ -9417,17 +9417,17 @@
|
||||
"openKnowledge": "開啟 KM",
|
||||
"queueReview": "排入審核",
|
||||
"queueingReview": "排入中",
|
||||
"queueFailed": "排入 AI 受控 review 失敗;請重新整理後再確認此 KM 是否仍為陳舊候選。",
|
||||
"queueFailed": "排入 負責人審查 失敗;請重新整理後再確認此 KM 是否仍為陳舊候選。",
|
||||
"queueResult": "審核狀態:{status};Dispatch:{dispatch};Event:{event}",
|
||||
"ownerReviewState": "Controlled review:{status};階段:{stage};Dispatch:{dispatch}",
|
||||
"guardrail": "防護:讀取不寫入={writes};受控覆核={review}",
|
||||
"ownerReviewState": "Owner review:{status};階段:{stage};Dispatch:{dispatch}",
|
||||
"guardrail": "防護:讀取不寫入={writes};人工覆核={review}",
|
||||
"queueStatuses": {
|
||||
"dry_run": "乾跑",
|
||||
"queued": "已排入 AI 受控 review",
|
||||
"already_queued": "已在 AI 受控 review"
|
||||
"queued": "已排入 負責人審查",
|
||||
"already_queued": "已在 負責人審查"
|
||||
},
|
||||
"operationRail": {
|
||||
"title": "Controlled Review 操作軌道",
|
||||
"title": "Owner Review 操作軌道",
|
||||
"subtitle": "把陳舊 KM 從偵測、審核、乾跑、確認、寫回到比例回測收斂成一條可掃描流程。",
|
||||
"nextAction": "建議下一步",
|
||||
"guardrailTitle": "寫入防護",
|
||||
@@ -9451,7 +9451,7 @@
|
||||
"flow": {
|
||||
"node": {
|
||||
"detected": "偵測",
|
||||
"ownerReview": "Controlled Review",
|
||||
"ownerReview": "Owner Review",
|
||||
"dryRun": "乾跑預覽",
|
||||
"ownerConfirm": "Owner 確認",
|
||||
"writeback": "寫回 KM",
|
||||
@@ -9484,12 +9484,12 @@
|
||||
},
|
||||
"guardrail": {
|
||||
"writesOnRead": "讀取即寫入:{value}",
|
||||
"manualReview": "受控覆核必要:{value}",
|
||||
"manualReview": "人工覆核必要:{value}",
|
||||
"batchWrites": "批次寫入允許:{value}"
|
||||
}
|
||||
},
|
||||
"singleItemRail": {
|
||||
"title": "單筆 Controlled Review 處理",
|
||||
"title": "單筆 Owner Review 處理",
|
||||
"subtitle": "先乾跑取得 plan fingerprint;Owner 確認後才允許寫 KM、寫 audit 並排比例回測。",
|
||||
"outcome": "策略:{outcome}",
|
||||
"writeGate": "確認寫 KM={writes};可確認={confirm}",
|
||||
@@ -9507,10 +9507,10 @@
|
||||
"dispatch": "Dispatch {dispatch}",
|
||||
"dryRunReady": "已取得 fingerprint,可進入 owner confirm gate",
|
||||
"dryRunPending": "按單筆乾跑取得 fingerprint",
|
||||
"dryRunBlocked": "需先排入 AI 受控 review",
|
||||
"dryRunBlocked": "需先排入 負責人審查",
|
||||
"confirmReady": "確認後會寫 KM / audit,並排 recheck",
|
||||
"confirmWaiting": "等待 乾跑 fingerprint",
|
||||
"confirmDone": "AI 受控 review 已完成",
|
||||
"confirmDone": "負責人審查 已完成",
|
||||
"recheckDone": "Recheck {recheck}",
|
||||
"recheckWaiting": "寫回完成後才會產生 recheck"
|
||||
},
|
||||
@@ -9520,7 +9520,7 @@
|
||||
}
|
||||
},
|
||||
"ownerReviewInbox": {
|
||||
"title": "Controlled review 工作台",
|
||||
"title": "Owner review 工作台",
|
||||
"subtitle": "顯示已排入 waiting_owner_review的 P0/P1 KM,逐筆乾跑與確認完成。",
|
||||
"total": "待審 {count}",
|
||||
"returned": "顯示 {count}",
|
||||
@@ -9532,7 +9532,7 @@
|
||||
},
|
||||
"burnDown": {
|
||||
"title": "Stale ratio burn-down",
|
||||
"subtitle": "把 AI 受控 review、completion audit與recheck snapshot 對齊,確認陳舊比例是否真的下降。",
|
||||
"subtitle": "把 負責人審查、completion audit與recheck snapshot 對齊,確認陳舊比例是否真的下降。",
|
||||
"statuses": "狀態:{status}",
|
||||
"status": {
|
||||
"above_threshold": "仍高於門檻",
|
||||
@@ -9544,26 +9544,26 @@
|
||||
"empty": "尚無 負責人批准 completion audit。",
|
||||
"currentRatio": "目前陳舊比例",
|
||||
"currentCount": "陳舊 / 總數",
|
||||
"ownerReviews": "Controlled review",
|
||||
"ownerReviews": "Owner review",
|
||||
"ownerReviewCounts": "待審 {pending} / 完成 {completed}",
|
||||
"latestDelta": "最新變化",
|
||||
"delta": "陳舊 {stale} / 比例 {ratio}",
|
||||
"auditTotal": "Completion audit {count}",
|
||||
"recheckTotal": "Recheck {count}",
|
||||
"guardrail": "讀取不寫入={writes};受控覆核={review}",
|
||||
"guardrail": "讀取不寫入={writes};人工覆核={review}",
|
||||
"itemState": "階段:{stage};結果:{outcome}",
|
||||
"itemRefs": "來源:{source};複查:{recheck}"
|
||||
},
|
||||
"completionQueue": {
|
||||
"title": "Completion分流佇列",
|
||||
"subtitle": "把 AI 受控 review 拆成可乾跑、卡住、已完成、失敗,避免只看到告警卻不知道下一步。",
|
||||
"subtitle": "把 負責人審查 拆成可乾跑、卡住、已完成、失敗,避免只看到告警卻不知道下一步。",
|
||||
"ready": "可處理 {count}",
|
||||
"blocked": "卡住 {count}",
|
||||
"completed": "完成 {count}",
|
||||
"failed": "失敗 {count}",
|
||||
"pending": "待處理 dispatch {count}",
|
||||
"guardrail": "讀取不寫入={writes};受控覆核={review};批次寫入={batch}",
|
||||
"unavailable": "completion queue API 尚未回應;目前只能從 AI 受控 review 工作台逐筆確認。",
|
||||
"guardrail": "讀取不寫入={writes};人工覆核={review};批次寫入={batch}",
|
||||
"unavailable": "completion queue API 尚未回應;目前只能從 負責人審查 工作台逐筆確認。",
|
||||
"empty": "目前沒有 owner-review completion 工作項。",
|
||||
"state": "分流:{readiness};階段:{stage}",
|
||||
"next": "下一步:{action};結果:{outcome}",
|
||||
@@ -9608,7 +9608,7 @@
|
||||
"result": "Batch dispatch:{batch};Event:{event};已排入 {queued};已在審核 {already};略過 {skipped}",
|
||||
"statuses": {
|
||||
"dry_run": "批次乾跑完成",
|
||||
"queued": "批次已排入 AI 受控 review",
|
||||
"queued": "批次已排入 負責人審查",
|
||||
"noop_already_queued": "全部已在審核或已處理",
|
||||
"unknown": "批次狀態待確認"
|
||||
},
|
||||
@@ -9625,7 +9625,7 @@
|
||||
"previewing": "預覽中",
|
||||
"confirm": "確認完成",
|
||||
"confirming": "寫入中",
|
||||
"previewFailed": "乾跑預覽失敗;請重新整理後確認 AI 受控 review dispatch仍有效。",
|
||||
"previewFailed": "乾跑預覽失敗;請重新整理後確認 負責人審查 dispatch仍有效。",
|
||||
"confirmFailed": "確認完成失敗;後端可能偵測到 KM或dispatch 狀態已變更。",
|
||||
"missingDispatch": "缺少 owner-review dispatch;請先排入審核。",
|
||||
"missingPreviewFingerprint": "缺少乾跑 plan fingerprint;請先重新執行乾跑預覽。",
|
||||
@@ -9739,8 +9739,8 @@
|
||||
"ai_analyzed": "AI 已分析",
|
||||
"queued_kb_healthcheck": "已排入 KM healthcheck",
|
||||
"draft_km_updates": "產生 KM 更新草稿",
|
||||
"batch_owner_review_previewed": "批次 AI 受控 review 已乾跑",
|
||||
"batch_owner_review_queued": "批次已排入 AI 受控 review",
|
||||
"batch_owner_review_previewed": "批次 負責人審查 已乾跑",
|
||||
"batch_owner_review_queued": "批次已排入 負責人審查",
|
||||
"batch_noop_already_queued": "批次無需重複排入",
|
||||
"waiting_owner_review": "等待 owner 審核",
|
||||
"owner_updates_or_archives_km": "Owner 更新或封存 KM",
|
||||
@@ -9752,7 +9752,7 @@
|
||||
"km_duplicate_archive_after_owner_approval": "Owner 審核後封存重複草稿",
|
||||
"km_governance_rechecked": "KM 治理已回測",
|
||||
"km_governance_close_or_continue": "關閉或繼續治理",
|
||||
"needs_manual_km_triage": "需要 AI 整理 KM",
|
||||
"needs_manual_km_triage": "需要人工整理 KM",
|
||||
"cancelled": "已取消",
|
||||
"queued_for_review": "等待治理審核",
|
||||
"dispatched": "已派遣",
|
||||
@@ -9765,7 +9765,7 @@
|
||||
},
|
||||
"driftFingerprint": {
|
||||
"title": "Config Drift fingerprint 狀態",
|
||||
"subtitle": "把每小時 drift report 收斂成同一狀態鏈,顯示 PR、零 diff、P0 去重與 AI 受控交接",
|
||||
"subtitle": "把每小時 drift report 收斂成同一狀態鏈,顯示 PR、零 diff、P0 去重與人工交接",
|
||||
"unavailable": "drift fingerprint state API 尚未回應,不能判定是否重複、是否已有 PR或是否已交接。",
|
||||
"occurrences": "12h {count} 次",
|
||||
"risk": "HIGH {high} / MEDIUM {medium} / INFO {info}",
|
||||
@@ -9775,8 +9775,8 @@
|
||||
"p0Dedup": "P0 去重:{enabled};視窗 {hours}h",
|
||||
"writes": "寫入:drift={drift};incident={incident};repair={repair};ticket={ticket}",
|
||||
"fsmStates": {
|
||||
"pending_human": "等待 AI 受控補齊",
|
||||
"pending_human_repeated": "AI 受控補齊重試",
|
||||
"pending_human": "等待人工",
|
||||
"pending_human_repeated": "重複等待人工",
|
||||
"pr_open_zero_diff": "PR 開啟但零 diff",
|
||||
"pr_open_waiting_review": "PR 等待 review",
|
||||
"pr_merged_unverified": "PR 已 merge 待驗證",
|
||||
@@ -9795,7 +9795,7 @@
|
||||
"close_zero_diff_pr_and_prepare_real_yaml_patch": "關閉零 diff PR,準備真實 YAML patch",
|
||||
"review_pr_then_merge_or_reject": "review PR 後 merge或reject",
|
||||
"verify_git_baseline_then_mark_adopted": "驗證 Git baseline 後標記採納",
|
||||
"operator_review_handoff_and_execute_manual_plan": "Operator review交接並執行受控方案",
|
||||
"operator_review_handoff_and_execute_manual_plan": "Operator review交接並執行人工方案",
|
||||
"run_verification_scan_then_record_result": "執行驗證掃描並記錄結果",
|
||||
"open_manual_investigation_with_failed_verification": "建立 AI verifier / rollback 調查並附上失敗驗證",
|
||||
"verify_k8s_matches_git_baseline": "驗證 K8s與Git baseline 一致",
|
||||
@@ -9825,7 +9825,7 @@
|
||||
"git_adopted": "Git 採納",
|
||||
"git_rollback": "Git 回滾",
|
||||
"zero_diff_pr_cleanup": "零 diff PR 清理",
|
||||
"manual_noop": "AI 受控確認無需動作",
|
||||
"manual_noop": "人工確認無需動作",
|
||||
"unknown": "未知"
|
||||
},
|
||||
"remediationStatuses": {
|
||||
@@ -9881,7 +9881,7 @@
|
||||
"repairCandidateDraft": {
|
||||
"eyebrow": "修復候選草案",
|
||||
"title": "PlayBook 草案處置板",
|
||||
"subtitle": "這筆告警已確認不能把通用兜底或診斷型 PlayBook 當成修復命令;下一步是補齊服務專屬修復草案,通過 AI 受控 review 與風險閘門後才可能進入審批或執行。",
|
||||
"subtitle": "這筆告警已確認不能把通用兜底或診斷型 PlayBook 當成修復命令;下一步是補齊服務專屬修復草案,通過 負責人審查 與風險閘門後才可能進入審批或執行。",
|
||||
"statusValue": "等待 PlayBook 草案",
|
||||
"metrics": {
|
||||
"status": "狀態",
|
||||
@@ -9923,7 +9923,7 @@
|
||||
"repair_command_template": "修復模板",
|
||||
"rollback_command_template": "Rollback",
|
||||
"verifier_plan": "Verifier",
|
||||
"owner_review": "Controlled review",
|
||||
"owner_review": "Owner review",
|
||||
"maintenance_window": "維護窗口",
|
||||
"blast_radius": "影響範圍",
|
||||
"km_writeback_owner": "KM owner",
|
||||
@@ -9960,7 +9960,7 @@
|
||||
"detail": "建立服務專屬修復、回滾與 verifier 計畫。"
|
||||
},
|
||||
"review": {
|
||||
"title": "Controlled review",
|
||||
"title": "Owner review",
|
||||
"detail": "確認命令安全、適用條件與 PlayBook trust。"
|
||||
},
|
||||
"approval": {
|
||||
@@ -9978,8 +9978,8 @@
|
||||
"verifier_plan": "修復後如何驗證成功、失敗與是否要升級 AI 補齊或 break-glass。",
|
||||
"owner_review": "負責人、風險等級、適用條件與批准紀錄。",
|
||||
"script_or_ansible_ref": "腳本或 Ansible 參照,必須能被安全路由與 reviewer 查到。",
|
||||
"schedule_or_monitoring_rule_ref": "排程、監控規則或 recurrence 偵測參照,避免同類告警只靠個人口頭記憶或交接。",
|
||||
"km_update_plan": "KM 更新草稿與 AI 受控 review 計畫,避免錯知識直接固化。",
|
||||
"schedule_or_monitoring_rule_ref": "排程、監控規則或 recurrence 偵測參照,避免同類告警只靠人工記憶或口頭交接。",
|
||||
"km_update_plan": "KM 更新草稿與 負責人審查 計畫,避免錯知識直接固化。",
|
||||
"automation_asset_record": "自動化資產紀錄,包含 asset id、owner、狀態、來源與下一步。"
|
||||
},
|
||||
"assetsTitle": "自動化資產沉澱板",
|
||||
@@ -9987,7 +9987,7 @@
|
||||
"km": {
|
||||
"type": "KM",
|
||||
"owner": "Hermes",
|
||||
"visibility": "Knowledge Base:根因、處置、引用來源與 AI 受控 review。",
|
||||
"visibility": "Knowledge Base:根因、處置、引用來源與 負責人審查。",
|
||||
"status": "待草稿"
|
||||
},
|
||||
"playbook": {
|
||||
@@ -10020,13 +10020,13 @@
|
||||
"incident_timeline_stage_update": "Incident timeline 必須標記目前階段、處置包、owner 與下一步。",
|
||||
"execution_or_manual_handoff_result": "無執行時也要寫入 AI 補齊或 break-glass 結果,不能只留下批准紀錄。",
|
||||
"verifier_result": "Verifier 要能記錄成功、失敗、降級或尚未執行。",
|
||||
"km_update_draft": "Hermes 產生 KM 草稿,受控 review 後才可寫入高影響知識。",
|
||||
"km_update_draft": "Hermes 產生 KM 草稿,負責人審查 後才可寫入高影響知識。",
|
||||
"playbook_trust_update": "PlayBook 成功 / 失敗 / 未執行都要回寫 trust 與適用條件。",
|
||||
"automation_asset_inventory_record": "資產清冊要留下 KM、PlayBook、腳本、排程、Verifier 的 ID 與狀態。"
|
||||
},
|
||||
"guardrailTitle": "阻擋原因與禁止誤讀",
|
||||
"blocker": "目前缺少可信修復候選;系統必須建立 AI 補齊草案工作項,不能把 no-action、診斷結果或通用兜底當作已修復。",
|
||||
"nextStep": "請先補 PlayBook 草案與 MCP evidence,再由 AI 受控 review 決定是否送審批;在此之前不會自動執行、不會寫入成功修復,也不會更新 KM 為已解決。",
|
||||
"nextStep": "請先補 PlayBook 草案與 MCP evidence,再由 負責人審查 決定是否送審批;在此之前不會自動執行、不會寫入成功修復,也不會更新 KM 為已解決。",
|
||||
"chainTitle": "真相鏈對照",
|
||||
"chain": {
|
||||
"stage": "目前階段",
|
||||
@@ -11385,7 +11385,7 @@
|
||||
"blockedGatesDetail": "有 missing / failed 就不能宣稱完整自動化。",
|
||||
"readiness": "流程健康度",
|
||||
"warningGates": "Warning Gate",
|
||||
"warningGatesDetail": "仍可觀測,但需要 AI 補脈絡或受控判讀。"
|
||||
"warningGatesDetail": "仍可觀測,但需要補脈絡或人工判斷。"
|
||||
},
|
||||
"gates": {
|
||||
"alert_intake": "告警入庫 / 通知鏡像",
|
||||
@@ -11431,7 +11431,7 @@
|
||||
"runRefs": {
|
||||
"mirrorRunState": "AwoooP 執行監控可以理解資安鏡像,但只能當只讀候選。",
|
||||
"readOnlyDryRun": "若未來產生試跑證據,也必須維持只讀與 AI 受控閘門語義。",
|
||||
"ownerResponse": "負責人回覆已收到 / 已接受仍為 0,任何執行進一步行動都要等待負責人脫敏證據收件。",
|
||||
"ownerResponse": "負責人回覆已收到 / 已接受仍為 0,任何執行進一步行動都要等待人工收件。",
|
||||
"activeGates": "主動執行期閘門仍為 0,不從執行監控頁開閘門或建立動作按鈕。"
|
||||
}
|
||||
},
|
||||
@@ -11544,7 +11544,7 @@
|
||||
"statusRollup": "AwoooP / 資安工作線的共同狀態入口,只彙整進度與安全閘門。",
|
||||
"postureProjection": "IwoooS 前端態勢、主機覆蓋、負責人回覆焦點與禁止動作的投影契約。",
|
||||
"ownerValidation": "S4.9-S4.12 負責人回覆已收到 / 已接受分離與審查者檢查口徑。",
|
||||
"rolloutPolicy": "低摩擦、先觀測、封鎖前先由 AI 受控 review 判讀的推出政策。"
|
||||
"rolloutPolicy": "低摩擦、先觀測、封鎖前先由負責人審查的推出政策。"
|
||||
}
|
||||
},
|
||||
"githubPrimaryReadinessCandidate": {
|
||||
@@ -11571,7 +11571,7 @@
|
||||
"contractRefs": {
|
||||
"primaryReadiness": "GitHub 主要來源一致性、負責人、分支 / 標籤參照、工作流程與回復前置缺口的主就緒度閘門。",
|
||||
"ownerValidation": "四包負責人回覆的已收到 / 已接受 / 已拒收分離與審查者檢查口徑。",
|
||||
"rollbackAdr": "9 個範圍內專案庫的回復 ADR 草案、AI 受控 review 與驗證窗口。",
|
||||
"rollbackAdr": "9 個範圍內專案庫的回復 ADR 草案、負責人審查與驗證窗口。",
|
||||
"workflowInventory": "工作流程、執行器、部署金鑰、分支保護、CODEOWNERS與機密名稱清冊;只收名稱不收明文值。",
|
||||
"postureProjection": "IwoooS 用來呈現 GitHub就緒度狀態板與禁止動作的前端投影。"
|
||||
}
|
||||
@@ -11835,7 +11835,7 @@
|
||||
},
|
||||
"securityOwnerResponseGate": {
|
||||
"title": "IwoooS 負責人回覆只讀審查焦點",
|
||||
"subtitle": "AwoooP 審批佇列只顯示 S4.9-S4.12 負責人回覆的下一個脫敏證據收件焦點;這不是審批紀錄,也不會開執行期閘門。",
|
||||
"subtitle": "AwoooP 審批佇列只顯示 S4.9-S4.12 負責人回覆的下一個人工收件焦點;這不是審批紀錄,也不會開執行期閘門。",
|
||||
"badge": "只讀焦點",
|
||||
"ownerChecksTitle": "負責人回覆收件順序",
|
||||
"boundaryLabel": "審批邊界",
|
||||
@@ -11930,7 +11930,7 @@
|
||||
"accepted": "已接受",
|
||||
"acceptedDetail": "目前仍為 0;只有脫敏證據通過驗收後才能改變。",
|
||||
"rejected": "已拒收",
|
||||
"rejectedDetail": "目前仍為 0;未進入受控驗收前不得產生拒收結果。",
|
||||
"rejectedDetail": "目前仍為 0;未進入人工驗收前不得產生拒收結果。",
|
||||
"displaySections": "顯示區塊",
|
||||
"displaySectionsDetail": "8 個顯示區塊只用於說明驗收流程、證據路由與邊界。"
|
||||
},
|
||||
|
||||
@@ -8505,7 +8505,7 @@
|
||||
"detail": "來源事件是否回到 Run / Incident"
|
||||
},
|
||||
"gate": {
|
||||
"label": "AI 受控 Gate",
|
||||
"label": "人工 Gate",
|
||||
"detail": "待處理工作 {work}"
|
||||
},
|
||||
"verify": {
|
||||
@@ -8569,7 +8569,7 @@
|
||||
"autoRepairedVerified": "已驗證自動修復",
|
||||
"executionUnverified": "已執行但未驗證",
|
||||
"executionFailed": "執行失敗",
|
||||
"manualRequiredNoAction": "AI 受控補齊:NO_ACTION",
|
||||
"manualRequiredNoAction": "人工介入:NO_ACTION",
|
||||
"approvalRequired": "等待審批",
|
||||
"observedNotExecuted": "已觀測但未執行",
|
||||
"receivedOnly": "僅收到告警"
|
||||
@@ -8730,7 +8730,7 @@
|
||||
},
|
||||
"readinessRefs": {
|
||||
"primaryReadiness": "GitHub 主要來源就緒度閘門仍是候選狀態,不能觸發專案庫建立或可見性變更。",
|
||||
"ownerValidation": "負責人回覆驗證彙總顯示四包回覆資料都仍等待負責人脫敏回覆與受控驗收。",
|
||||
"ownerValidation": "負責人回覆驗證彙總顯示四包回覆資料都仍等待人工回覆與驗收。",
|
||||
"rollbackAdr": "回復架構決策紀錄尚未完成負責人批准的演練,因此不能把 GitHub 切為主要來源。",
|
||||
"workflowInventory": "工作流程 / 機密名稱清冊只收集名稱與路由,不收機密明文值、不改 GitHub 機密設定。"
|
||||
}
|
||||
@@ -8810,7 +8810,7 @@
|
||||
},
|
||||
"reviewerChecklist": {
|
||||
"label": "審查清單",
|
||||
"detail": "9 個清單項目給受控驗收流程判讀。"
|
||||
"detail": "9 個清單項目給人工審查者判讀。"
|
||||
},
|
||||
"reviewerOutcomes": {
|
||||
"label": "審查結果",
|
||||
@@ -8911,7 +8911,7 @@
|
||||
},
|
||||
"owner": {
|
||||
"title": "接手",
|
||||
"detail": "AI 受控閘門與受控 review"
|
||||
"detail": "AI 受控閘門與負責人審查"
|
||||
},
|
||||
"verifier": {
|
||||
"title": "驗證",
|
||||
@@ -8988,7 +8988,7 @@
|
||||
},
|
||||
"learning": {
|
||||
"title": "KM / Trust 回寫",
|
||||
"detail": "確認 KM、PlayBook trust 與學習責任已沉澱,避免下次重複 AI 補齊判斷。"
|
||||
"detail": "確認 KM、PlayBook trust 與學習責任已沉澱,避免下次重複人工判斷。"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -9035,7 +9035,7 @@
|
||||
"items": {
|
||||
"km": {
|
||||
"title": "Knowledge Base",
|
||||
"detail": "Hermes 草稿、陳舊 KM、AI 受控 review 與 stale ratio 回測。",
|
||||
"detail": "Hermes 草稿、陳舊 KM、負責人審查 與 stale ratio 回測。",
|
||||
"next": "下一步:把待補 KM 與 Incident / PlayBook / verifier 結果綁定。"
|
||||
},
|
||||
"playbook": {
|
||||
@@ -9061,7 +9061,7 @@
|
||||
},
|
||||
"sources": {
|
||||
"knowledgeBase": "Knowledge Base",
|
||||
"ownerReview": "Controlled review",
|
||||
"ownerReview": "Owner review",
|
||||
"staleRatio": "Stale ratio",
|
||||
"statusChain": "Status-chain",
|
||||
"remediationQueue": "補救佇列",
|
||||
@@ -9078,17 +9078,17 @@
|
||||
}
|
||||
},
|
||||
"reportSourceGapOwnerReview": {
|
||||
"eyebrow": "報表資料源 AI 受控補齊",
|
||||
"eyebrow": "報表資料源 負責人審查",
|
||||
"title": "報表資料源 PlayBook / Verifier 處置板",
|
||||
"subtitle": "把 report-source-gap 從報表頁接到 Work Items:每個缺口都要有 PlayBook 草案、Verifier 計畫、腳本 readback、排程無發送與 AI 受控 review。",
|
||||
"loading": "正在讀取 report-source-gap AI 受控補齊 read model。",
|
||||
"subtitle": "把 report-source-gap 從報表頁接到 Work Items:每個缺口都要有 PlayBook 草案、Verifier 計畫、腳本 readback、排程 無發送 與 負責人審查。",
|
||||
"loading": "正在讀取 report-source-gap 負責人審查 read model。",
|
||||
"unavailable": "報表資料源 read model 尚未回應;不能把報表全 0 或缺資料判定為健康。",
|
||||
"empty": "目前沒有 report-source-gap AI 受控補齊卡。",
|
||||
"empty": "目前沒有 report-source-gap 負責人審查 卡。",
|
||||
"boundaryTitle": "不可誤讀合約",
|
||||
"boundary": "live Telegram 發送={live};runtime gate={gate}。這裡只做草案與 AI 受控 review 讀回,不發送、不排程、不執行。",
|
||||
"boundary": "live Telegram 發送={live};runtime gate={gate}。這裡只做草案與 負責人審查讀回,不發送、不排程、不執行。",
|
||||
"openReports": "回報表總控",
|
||||
"ownerRequired": "需 AI 受控補齊",
|
||||
"ownerOptional": "AI 受控補齊可後補",
|
||||
"ownerRequired": "需 負責人審查",
|
||||
"ownerOptional": "負責人審查 可後補",
|
||||
"scheduleBoundary": "排程仍維持 無發送 preview",
|
||||
"fieldsTitle": "PlayBook 必填欄位",
|
||||
"checksTitle": "Verifier 檢查",
|
||||
@@ -9154,7 +9154,7 @@
|
||||
"title": "AI Provider primary lane 修復工作項"
|
||||
},
|
||||
"reportSourceGapOwnerReview": {
|
||||
"title": "報表資料源 PlayBook / Verifier AI 受控補齊"
|
||||
"title": "報表資料源 PlayBook / Verifier 負責人審查"
|
||||
},
|
||||
"configDriftFsm": {
|
||||
"title": "Config Drift fingerprint 狀態機"
|
||||
@@ -9166,7 +9166,7 @@
|
||||
"title": "Telegram 詳情 / 歷史改為 DB 真相優先"
|
||||
},
|
||||
"callbackOwnerReview": {
|
||||
"title": "Callback 未匹配 KM Controlled Review 工作項"
|
||||
"title": "Callback 未匹配 KM Owner Review 工作項"
|
||||
},
|
||||
"callbackTraceRecoveryBacklog": {
|
||||
"title": "Callback trace 復原 backlog"
|
||||
@@ -9204,9 +9204,9 @@
|
||||
"autoRepair": "必須同時有 auto_repair、verification_result=success與KM 回寫",
|
||||
"recurrenceWorkItems": "Run 完成無修復、修復失敗與 AI 受控閘門必須進入可追蹤工作項",
|
||||
"aiRouteRepairWorkItem": "Provider lane 降級時必須顯示 evidence、owner、PlayBook候選與是否可自動修復",
|
||||
"reportSourceGapOwnerReview": "每個 report-source-gap 必須有 PlayBook 草案、Verifier 計畫、腳本 readback、排程無發送與 AI 受控 review;不得把全 0 當健康或自動執行授權",
|
||||
"reportSourceGapOwnerReview": "每個 report-source-gap 必須有 PlayBook 草案、Verifier 計畫、腳本 readback、排程 無發送 與 負責人審查;不得把全 0 當健康或自動執行授權",
|
||||
"configDriftFsm": "同一 drift fingerprint 必須顯示重複、PR、零 diff、交接與下一步",
|
||||
"remediationQueue": "每筆 degraded / failed / timeout都必須映射到重跑、重驗、Ticket或 AI 受控檢查",
|
||||
"remediationQueue": "每筆 degraded / failed / timeout都必須映射到重跑、重驗、Ticket或人工檢查",
|
||||
"telegramCallbacks": "按下詳情與歷史不能再只依賴 Redis TTL或舊快照",
|
||||
"callbackOwnerReview": "Telegram 詳情 / 歷史若未連到 KM owner-review,必須變成可追蹤工作項",
|
||||
"callbackTraceRecoveryBacklog": "Callback trace 缺口必須顯示復原訊號、24h decay與backlog 下一步",
|
||||
@@ -9260,7 +9260,7 @@
|
||||
"driftFingerprintNext": "下一步:{step}",
|
||||
"driftFingerprintRemediation": "修復:{kind} / {status};驗證 Report:{report}",
|
||||
"driftFingerprintEmpty": "尚無 Config Drift fingerprint 狀態",
|
||||
"remediationQueue": "補救工作:{total};AI可接手:{ready};AI補齊:{human}",
|
||||
"remediationQueue": "補救工作:{total};AI可接手:{ready};人工:{human}",
|
||||
"telegramCallbacks": "目前修補 Telegram callback 查詢鏈與歷史摘要",
|
||||
"telegramCallbacksLive": "只讀 callback toast 400 已非致命;詳情 / 歷史改由 DB 真相鏈 回覆",
|
||||
"callbackOwnerReview": "Callback owner-review 缺口:{open} 個 open;callback evidence:{total}",
|
||||
@@ -9273,7 +9273,7 @@
|
||||
"callbackOwnerReviewBlocker": "卡點:{reason}",
|
||||
"callbackOwnerReviewEmpty": "近期 callback evidence 均已匹配或尚無資料",
|
||||
"callbackTraceRecoveryBacklog": "Callback trace backlog:缺 trace {missing};1h {recent1h};24h {recent24h};gap 後 traced {recovered};復原 {status}",
|
||||
"callbackTraceRecoveryAction": "接續處理:{action};需AI補齊={human}",
|
||||
"callbackTraceRecoveryAction": "接續處理:{action};需要人工={human}",
|
||||
"callbackTraceRecoveryOwner": "主責:AwoooP Callback Evidence;協作:TelegramGateway / 執行時間線",
|
||||
"callbackTraceRecoveryEvidenceSurface": "查證入口:Runs / TG Callback 證據",
|
||||
"callbackTraceRecoveryClosure": "關閉條件:1h=0 且 24h=0;目前 1h {recent1h} / 24h {recent24h}",
|
||||
@@ -9285,7 +9285,7 @@
|
||||
"governanceUnavailable": "治理事件 API 目前無法回應;待派送:{queued}",
|
||||
"governanceQueueMissing": "治理 dispatch 表尚未就緒;未解治理告警:{unresolved}",
|
||||
"knowledgeHealthcheck": "KM healthcheck 派工:{total};目前階段:{stage}",
|
||||
"knowledgeOwner": "主責:{lead};受控覆核:{human}",
|
||||
"knowledgeOwner": "主責:{lead};人工覆核:{human}",
|
||||
"knowledgeNext": "下一步:{action}",
|
||||
"knowledgeDrafts": "KM 審核草稿:{drafts};重複草稿:{duplicates}",
|
||||
"knowledgeStaleCandidates": "陳舊 KM 優先清單:{total} 筆;最高 {top} / {tier}",
|
||||
@@ -9358,7 +9358,7 @@
|
||||
"investigateActiveGap": "仍有新缺口,檢查新 Telegram reply_markup trace 寫入",
|
||||
"verifyInstrumentation": "沒有復原訊號,檢查 TelegramGateway / 時間線觀測埋點",
|
||||
"waitDecay": "等待舊 backlog 24h decay,不需 AI 補齊處理",
|
||||
"observeRecovery": "觀察復原訊號,先不開受控任務"
|
||||
"observeRecovery": "觀察復原訊號,先不開人工任務"
|
||||
},
|
||||
"claim": {
|
||||
"ready": "完整自動修復聲明:可宣稱",
|
||||
@@ -9390,7 +9390,7 @@
|
||||
"stage": "階段:{stage}",
|
||||
"next": "下一步:{action}",
|
||||
"lead": "主責:{agent}",
|
||||
"human": "受控覆核:{owner}",
|
||||
"human": "人工覆核:{owner}",
|
||||
"support": "支援:{agents}",
|
||||
"worker": "Worker 狀態:{status}",
|
||||
"draft": "KM 草稿:{id}",
|
||||
@@ -9417,17 +9417,17 @@
|
||||
"openKnowledge": "開啟 KM",
|
||||
"queueReview": "排入審核",
|
||||
"queueingReview": "排入中",
|
||||
"queueFailed": "排入 AI 受控 review 失敗;請重新整理後再確認此 KM 是否仍為陳舊候選。",
|
||||
"queueFailed": "排入 負責人審查 失敗;請重新整理後再確認此 KM 是否仍為陳舊候選。",
|
||||
"queueResult": "審核狀態:{status};Dispatch:{dispatch};Event:{event}",
|
||||
"ownerReviewState": "Controlled review:{status};階段:{stage};Dispatch:{dispatch}",
|
||||
"guardrail": "防護:讀取不寫入={writes};受控覆核={review}",
|
||||
"ownerReviewState": "Owner review:{status};階段:{stage};Dispatch:{dispatch}",
|
||||
"guardrail": "防護:讀取不寫入={writes};人工覆核={review}",
|
||||
"queueStatuses": {
|
||||
"dry_run": "乾跑",
|
||||
"queued": "已排入 AI 受控 review",
|
||||
"already_queued": "已在 AI 受控 review"
|
||||
"queued": "已排入 負責人審查",
|
||||
"already_queued": "已在 負責人審查"
|
||||
},
|
||||
"operationRail": {
|
||||
"title": "Controlled Review 操作軌道",
|
||||
"title": "Owner Review 操作軌道",
|
||||
"subtitle": "把陳舊 KM 從偵測、審核、乾跑、確認、寫回到比例回測收斂成一條可掃描流程。",
|
||||
"nextAction": "建議下一步",
|
||||
"guardrailTitle": "寫入防護",
|
||||
@@ -9451,7 +9451,7 @@
|
||||
"flow": {
|
||||
"node": {
|
||||
"detected": "偵測",
|
||||
"ownerReview": "Controlled Review",
|
||||
"ownerReview": "Owner Review",
|
||||
"dryRun": "乾跑預覽",
|
||||
"ownerConfirm": "Owner 確認",
|
||||
"writeback": "寫回 KM",
|
||||
@@ -9484,12 +9484,12 @@
|
||||
},
|
||||
"guardrail": {
|
||||
"writesOnRead": "讀取即寫入:{value}",
|
||||
"manualReview": "受控覆核必要:{value}",
|
||||
"manualReview": "人工覆核必要:{value}",
|
||||
"batchWrites": "批次寫入允許:{value}"
|
||||
}
|
||||
},
|
||||
"singleItemRail": {
|
||||
"title": "單筆 Controlled Review 處理",
|
||||
"title": "單筆 Owner Review 處理",
|
||||
"subtitle": "先乾跑取得 plan fingerprint;Owner 確認後才允許寫 KM、寫 audit 並排比例回測。",
|
||||
"outcome": "策略:{outcome}",
|
||||
"writeGate": "確認寫 KM={writes};可確認={confirm}",
|
||||
@@ -9507,10 +9507,10 @@
|
||||
"dispatch": "Dispatch {dispatch}",
|
||||
"dryRunReady": "已取得 fingerprint,可進入 owner confirm gate",
|
||||
"dryRunPending": "按單筆乾跑取得 fingerprint",
|
||||
"dryRunBlocked": "需先排入 AI 受控 review",
|
||||
"dryRunBlocked": "需先排入 負責人審查",
|
||||
"confirmReady": "確認後會寫 KM / audit,並排 recheck",
|
||||
"confirmWaiting": "等待 乾跑 fingerprint",
|
||||
"confirmDone": "AI 受控 review 已完成",
|
||||
"confirmDone": "負責人審查 已完成",
|
||||
"recheckDone": "Recheck {recheck}",
|
||||
"recheckWaiting": "寫回完成後才會產生 recheck"
|
||||
},
|
||||
@@ -9520,7 +9520,7 @@
|
||||
}
|
||||
},
|
||||
"ownerReviewInbox": {
|
||||
"title": "Controlled review 工作台",
|
||||
"title": "Owner review 工作台",
|
||||
"subtitle": "顯示已排入 waiting_owner_review的 P0/P1 KM,逐筆乾跑與確認完成。",
|
||||
"total": "待審 {count}",
|
||||
"returned": "顯示 {count}",
|
||||
@@ -9532,7 +9532,7 @@
|
||||
},
|
||||
"burnDown": {
|
||||
"title": "Stale ratio burn-down",
|
||||
"subtitle": "把 AI 受控 review、completion audit與recheck snapshot 對齊,確認陳舊比例是否真的下降。",
|
||||
"subtitle": "把 負責人審查、completion audit與recheck snapshot 對齊,確認陳舊比例是否真的下降。",
|
||||
"statuses": "狀態:{status}",
|
||||
"status": {
|
||||
"above_threshold": "仍高於門檻",
|
||||
@@ -9544,26 +9544,26 @@
|
||||
"empty": "尚無 負責人批准 completion audit。",
|
||||
"currentRatio": "目前陳舊比例",
|
||||
"currentCount": "陳舊 / 總數",
|
||||
"ownerReviews": "Controlled review",
|
||||
"ownerReviews": "Owner review",
|
||||
"ownerReviewCounts": "待審 {pending} / 完成 {completed}",
|
||||
"latestDelta": "最新變化",
|
||||
"delta": "陳舊 {stale} / 比例 {ratio}",
|
||||
"auditTotal": "Completion audit {count}",
|
||||
"recheckTotal": "Recheck {count}",
|
||||
"guardrail": "讀取不寫入={writes};受控覆核={review}",
|
||||
"guardrail": "讀取不寫入={writes};人工覆核={review}",
|
||||
"itemState": "階段:{stage};結果:{outcome}",
|
||||
"itemRefs": "來源:{source};複查:{recheck}"
|
||||
},
|
||||
"completionQueue": {
|
||||
"title": "Completion分流佇列",
|
||||
"subtitle": "把 AI 受控 review 拆成可乾跑、卡住、已完成、失敗,避免只看到告警卻不知道下一步。",
|
||||
"subtitle": "把 負責人審查 拆成可乾跑、卡住、已完成、失敗,避免只看到告警卻不知道下一步。",
|
||||
"ready": "可處理 {count}",
|
||||
"blocked": "卡住 {count}",
|
||||
"completed": "完成 {count}",
|
||||
"failed": "失敗 {count}",
|
||||
"pending": "待處理 dispatch {count}",
|
||||
"guardrail": "讀取不寫入={writes};受控覆核={review};批次寫入={batch}",
|
||||
"unavailable": "completion queue API 尚未回應;目前只能從 AI 受控 review 工作台逐筆確認。",
|
||||
"guardrail": "讀取不寫入={writes};人工覆核={review};批次寫入={batch}",
|
||||
"unavailable": "completion queue API 尚未回應;目前只能從 負責人審查 工作台逐筆確認。",
|
||||
"empty": "目前沒有 owner-review completion 工作項。",
|
||||
"state": "分流:{readiness};階段:{stage}",
|
||||
"next": "下一步:{action};結果:{outcome}",
|
||||
@@ -9608,7 +9608,7 @@
|
||||
"result": "Batch dispatch:{batch};Event:{event};已排入 {queued};已在審核 {already};略過 {skipped}",
|
||||
"statuses": {
|
||||
"dry_run": "批次乾跑完成",
|
||||
"queued": "批次已排入 AI 受控 review",
|
||||
"queued": "批次已排入 負責人審查",
|
||||
"noop_already_queued": "全部已在審核或已處理",
|
||||
"unknown": "批次狀態待確認"
|
||||
},
|
||||
@@ -9625,7 +9625,7 @@
|
||||
"previewing": "預覽中",
|
||||
"confirm": "確認完成",
|
||||
"confirming": "寫入中",
|
||||
"previewFailed": "乾跑預覽失敗;請重新整理後確認 AI 受控 review dispatch仍有效。",
|
||||
"previewFailed": "乾跑預覽失敗;請重新整理後確認 負責人審查 dispatch仍有效。",
|
||||
"confirmFailed": "確認完成失敗;後端可能偵測到 KM或dispatch 狀態已變更。",
|
||||
"missingDispatch": "缺少 owner-review dispatch;請先排入審核。",
|
||||
"missingPreviewFingerprint": "缺少乾跑 plan fingerprint;請先重新執行乾跑預覽。",
|
||||
@@ -9739,8 +9739,8 @@
|
||||
"ai_analyzed": "AI 已分析",
|
||||
"queued_kb_healthcheck": "已排入 KM healthcheck",
|
||||
"draft_km_updates": "產生 KM 更新草稿",
|
||||
"batch_owner_review_previewed": "批次 AI 受控 review 已乾跑",
|
||||
"batch_owner_review_queued": "批次已排入 AI 受控 review",
|
||||
"batch_owner_review_previewed": "批次 負責人審查 已乾跑",
|
||||
"batch_owner_review_queued": "批次已排入 負責人審查",
|
||||
"batch_noop_already_queued": "批次無需重複排入",
|
||||
"waiting_owner_review": "等待 owner 審核",
|
||||
"owner_updates_or_archives_km": "Owner 更新或封存 KM",
|
||||
@@ -9752,7 +9752,7 @@
|
||||
"km_duplicate_archive_after_owner_approval": "Owner 審核後封存重複草稿",
|
||||
"km_governance_rechecked": "KM 治理已回測",
|
||||
"km_governance_close_or_continue": "關閉或繼續治理",
|
||||
"needs_manual_km_triage": "需要 AI 整理 KM",
|
||||
"needs_manual_km_triage": "需要人工整理 KM",
|
||||
"cancelled": "已取消",
|
||||
"queued_for_review": "等待治理審核",
|
||||
"dispatched": "已派遣",
|
||||
@@ -9765,7 +9765,7 @@
|
||||
},
|
||||
"driftFingerprint": {
|
||||
"title": "Config Drift fingerprint 狀態",
|
||||
"subtitle": "把每小時 drift report 收斂成同一狀態鏈,顯示 PR、零 diff、P0 去重與 AI 受控交接",
|
||||
"subtitle": "把每小時 drift report 收斂成同一狀態鏈,顯示 PR、零 diff、P0 去重與人工交接",
|
||||
"unavailable": "drift fingerprint state API 尚未回應,不能判定是否重複、是否已有 PR或是否已交接。",
|
||||
"occurrences": "12h {count} 次",
|
||||
"risk": "HIGH {high} / MEDIUM {medium} / INFO {info}",
|
||||
@@ -9775,8 +9775,8 @@
|
||||
"p0Dedup": "P0 去重:{enabled};視窗 {hours}h",
|
||||
"writes": "寫入:drift={drift};incident={incident};repair={repair};ticket={ticket}",
|
||||
"fsmStates": {
|
||||
"pending_human": "等待 AI 受控補齊",
|
||||
"pending_human_repeated": "AI 受控補齊重試",
|
||||
"pending_human": "等待人工",
|
||||
"pending_human_repeated": "重複等待人工",
|
||||
"pr_open_zero_diff": "PR 開啟但零 diff",
|
||||
"pr_open_waiting_review": "PR 等待 review",
|
||||
"pr_merged_unverified": "PR 已 merge 待驗證",
|
||||
@@ -9795,7 +9795,7 @@
|
||||
"close_zero_diff_pr_and_prepare_real_yaml_patch": "關閉零 diff PR,準備真實 YAML patch",
|
||||
"review_pr_then_merge_or_reject": "review PR 後 merge或reject",
|
||||
"verify_git_baseline_then_mark_adopted": "驗證 Git baseline 後標記採納",
|
||||
"operator_review_handoff_and_execute_manual_plan": "Operator review交接並執行受控方案",
|
||||
"operator_review_handoff_and_execute_manual_plan": "Operator review交接並執行人工方案",
|
||||
"run_verification_scan_then_record_result": "執行驗證掃描並記錄結果",
|
||||
"open_manual_investigation_with_failed_verification": "建立 AI verifier / rollback 調查並附上失敗驗證",
|
||||
"verify_k8s_matches_git_baseline": "驗證 K8s與Git baseline 一致",
|
||||
@@ -9825,7 +9825,7 @@
|
||||
"git_adopted": "Git 採納",
|
||||
"git_rollback": "Git 回滾",
|
||||
"zero_diff_pr_cleanup": "零 diff PR 清理",
|
||||
"manual_noop": "AI 受控確認無需動作",
|
||||
"manual_noop": "人工確認無需動作",
|
||||
"unknown": "未知"
|
||||
},
|
||||
"remediationStatuses": {
|
||||
@@ -9881,7 +9881,7 @@
|
||||
"repairCandidateDraft": {
|
||||
"eyebrow": "修復候選草案",
|
||||
"title": "PlayBook 草案處置板",
|
||||
"subtitle": "這筆告警已確認不能把通用兜底或診斷型 PlayBook 當成修復命令;下一步是補齊服務專屬修復草案,通過 AI 受控 review 與風險閘門後才可能進入審批或執行。",
|
||||
"subtitle": "這筆告警已確認不能把通用兜底或診斷型 PlayBook 當成修復命令;下一步是補齊服務專屬修復草案,通過 負責人審查 與風險閘門後才可能進入審批或執行。",
|
||||
"statusValue": "等待 PlayBook 草案",
|
||||
"metrics": {
|
||||
"status": "狀態",
|
||||
@@ -9923,7 +9923,7 @@
|
||||
"repair_command_template": "修復模板",
|
||||
"rollback_command_template": "Rollback",
|
||||
"verifier_plan": "Verifier",
|
||||
"owner_review": "Controlled review",
|
||||
"owner_review": "Owner review",
|
||||
"maintenance_window": "維護窗口",
|
||||
"blast_radius": "影響範圍",
|
||||
"km_writeback_owner": "KM owner",
|
||||
@@ -9960,7 +9960,7 @@
|
||||
"detail": "建立服務專屬修復、回滾與 verifier 計畫。"
|
||||
},
|
||||
"review": {
|
||||
"title": "Controlled review",
|
||||
"title": "Owner review",
|
||||
"detail": "確認命令安全、適用條件與 PlayBook trust。"
|
||||
},
|
||||
"approval": {
|
||||
@@ -9978,8 +9978,8 @@
|
||||
"verifier_plan": "修復後如何驗證成功、失敗與是否要升級 AI 補齊或 break-glass。",
|
||||
"owner_review": "負責人、風險等級、適用條件與批准紀錄。",
|
||||
"script_or_ansible_ref": "腳本或 Ansible 參照,必須能被安全路由與 reviewer 查到。",
|
||||
"schedule_or_monitoring_rule_ref": "排程、監控規則或 recurrence 偵測參照,避免同類告警只靠個人口頭記憶或交接。",
|
||||
"km_update_plan": "KM 更新草稿與 AI 受控 review 計畫,避免錯知識直接固化。",
|
||||
"schedule_or_monitoring_rule_ref": "排程、監控規則或 recurrence 偵測參照,避免同類告警只靠人工記憶或口頭交接。",
|
||||
"km_update_plan": "KM 更新草稿與 負責人審查 計畫,避免錯知識直接固化。",
|
||||
"automation_asset_record": "自動化資產紀錄,包含 asset id、owner、狀態、來源與下一步。"
|
||||
},
|
||||
"assetsTitle": "自動化資產沉澱板",
|
||||
@@ -9987,7 +9987,7 @@
|
||||
"km": {
|
||||
"type": "KM",
|
||||
"owner": "Hermes",
|
||||
"visibility": "Knowledge Base:根因、處置、引用來源與 AI 受控 review。",
|
||||
"visibility": "Knowledge Base:根因、處置、引用來源與 負責人審查。",
|
||||
"status": "待草稿"
|
||||
},
|
||||
"playbook": {
|
||||
@@ -10020,13 +10020,13 @@
|
||||
"incident_timeline_stage_update": "Incident timeline 必須標記目前階段、處置包、owner 與下一步。",
|
||||
"execution_or_manual_handoff_result": "無執行時也要寫入 AI 補齊或 break-glass 結果,不能只留下批准紀錄。",
|
||||
"verifier_result": "Verifier 要能記錄成功、失敗、降級或尚未執行。",
|
||||
"km_update_draft": "Hermes 產生 KM 草稿,受控 review 後才可寫入高影響知識。",
|
||||
"km_update_draft": "Hermes 產生 KM 草稿,負責人審查 後才可寫入高影響知識。",
|
||||
"playbook_trust_update": "PlayBook 成功 / 失敗 / 未執行都要回寫 trust 與適用條件。",
|
||||
"automation_asset_inventory_record": "資產清冊要留下 KM、PlayBook、腳本、排程、Verifier 的 ID 與狀態。"
|
||||
},
|
||||
"guardrailTitle": "阻擋原因與禁止誤讀",
|
||||
"blocker": "目前缺少可信修復候選;系統必須建立 AI 補齊草案工作項,不能把 no-action、診斷結果或通用兜底當作已修復。",
|
||||
"nextStep": "請先補 PlayBook 草案與 MCP evidence,再由 AI 受控 review 決定是否送審批;在此之前不會自動執行、不會寫入成功修復,也不會更新 KM 為已解決。",
|
||||
"nextStep": "請先補 PlayBook 草案與 MCP evidence,再由 負責人審查 決定是否送審批;在此之前不會自動執行、不會寫入成功修復,也不會更新 KM 為已解決。",
|
||||
"chainTitle": "真相鏈對照",
|
||||
"chain": {
|
||||
"stage": "目前階段",
|
||||
@@ -11385,7 +11385,7 @@
|
||||
"blockedGatesDetail": "有 missing / failed 就不能宣稱完整自動化。",
|
||||
"readiness": "流程健康度",
|
||||
"warningGates": "Warning Gate",
|
||||
"warningGatesDetail": "仍可觀測,但需要 AI 補脈絡或受控判讀。"
|
||||
"warningGatesDetail": "仍可觀測,但需要補脈絡或人工判斷。"
|
||||
},
|
||||
"gates": {
|
||||
"alert_intake": "告警入庫 / 通知鏡像",
|
||||
@@ -11431,7 +11431,7 @@
|
||||
"runRefs": {
|
||||
"mirrorRunState": "AwoooP 執行監控可以理解資安鏡像,但只能當只讀候選。",
|
||||
"readOnlyDryRun": "若未來產生試跑證據,也必須維持只讀與 AI 受控閘門語義。",
|
||||
"ownerResponse": "負責人回覆已收到 / 已接受仍為 0,任何執行進一步行動都要等待負責人脫敏證據收件。",
|
||||
"ownerResponse": "負責人回覆已收到 / 已接受仍為 0,任何執行進一步行動都要等待人工收件。",
|
||||
"activeGates": "主動執行期閘門仍為 0,不從執行監控頁開閘門或建立動作按鈕。"
|
||||
}
|
||||
},
|
||||
@@ -11544,7 +11544,7 @@
|
||||
"statusRollup": "AwoooP / 資安工作線的共同狀態入口,只彙整進度與安全閘門。",
|
||||
"postureProjection": "IwoooS 前端態勢、主機覆蓋、負責人回覆焦點與禁止動作的投影契約。",
|
||||
"ownerValidation": "S4.9-S4.12 負責人回覆已收到 / 已接受分離與審查者檢查口徑。",
|
||||
"rolloutPolicy": "低摩擦、先觀測、封鎖前先由 AI 受控 review 判讀的推出政策。"
|
||||
"rolloutPolicy": "低摩擦、先觀測、封鎖前先由負責人審查的推出政策。"
|
||||
}
|
||||
},
|
||||
"githubPrimaryReadinessCandidate": {
|
||||
@@ -11571,7 +11571,7 @@
|
||||
"contractRefs": {
|
||||
"primaryReadiness": "GitHub 主要來源一致性、負責人、分支 / 標籤參照、工作流程與回復前置缺口的主就緒度閘門。",
|
||||
"ownerValidation": "四包負責人回覆的已收到 / 已接受 / 已拒收分離與審查者檢查口徑。",
|
||||
"rollbackAdr": "9 個範圍內專案庫的回復 ADR 草案、AI 受控 review 與驗證窗口。",
|
||||
"rollbackAdr": "9 個範圍內專案庫的回復 ADR 草案、負責人審查與驗證窗口。",
|
||||
"workflowInventory": "工作流程、執行器、部署金鑰、分支保護、CODEOWNERS與機密名稱清冊;只收名稱不收明文值。",
|
||||
"postureProjection": "IwoooS 用來呈現 GitHub就緒度狀態板與禁止動作的前端投影。"
|
||||
}
|
||||
@@ -11835,7 +11835,7 @@
|
||||
},
|
||||
"securityOwnerResponseGate": {
|
||||
"title": "IwoooS 負責人回覆只讀審查焦點",
|
||||
"subtitle": "AwoooP 審批佇列只顯示 S4.9-S4.12 負責人回覆的下一個脫敏證據收件焦點;這不是審批紀錄,也不會開執行期閘門。",
|
||||
"subtitle": "AwoooP 審批佇列只顯示 S4.9-S4.12 負責人回覆的下一個人工收件焦點;這不是審批紀錄,也不會開執行期閘門。",
|
||||
"badge": "只讀焦點",
|
||||
"ownerChecksTitle": "負責人回覆收件順序",
|
||||
"boundaryLabel": "審批邊界",
|
||||
@@ -11930,7 +11930,7 @@
|
||||
"accepted": "已接受",
|
||||
"acceptedDetail": "目前仍為 0;只有脫敏證據通過驗收後才能改變。",
|
||||
"rejected": "已拒收",
|
||||
"rejectedDetail": "目前仍為 0;未進入受控驗收前不得產生拒收結果。",
|
||||
"rejectedDetail": "目前仍為 0;未進入人工驗收前不得產生拒收結果。",
|
||||
"displaySections": "顯示區塊",
|
||||
"displaySectionsDetail": "8 個顯示區塊只用於說明驗收流程、證據路由與邊界。"
|
||||
},
|
||||
|
||||
131
docs/LOGBOOK.md
131
docs/LOGBOOK.md
@@ -1,97 +1,3 @@
|
||||
## 2026-06-28 — 02:06 110 runner fail-closed guard 轉 controlled automation
|
||||
|
||||
**背景**:統帥明確要求非 critical hard gate / guard 全部打開並快速推進正式部署。`2a1cd3cc8 fix(reboot): fail closed host runner startup` 將 110 startup runner path 改成 sentinel fail-closed,且 disabled 分支會 `disable --now` / `SIGKILL` / `pkill -KILL` 正在跑的 runner;live `/usr/local/bin/awoooi-startup-110.sh` 與 user-level runner service 也仍是舊 guard 版本,會重新阻斷 CD。
|
||||
|
||||
**完成內容**:
|
||||
- `scripts/reboot-recovery/awoooi-startup-110.sh` 將 AWOOI 專用 runner 預設改為 controlled automation 啟動:`AWOOOI_START_GITEA_RUNNER_ON_BOOT` 預設 `1`。
|
||||
- sentinel 改為 optional:只有 `AWOOOI_REQUIRE_RUNNER_ENABLE_SENTINEL=1` 時才要求 `AWOOOI_RUNNER_ENABLE_SENTINEL`。
|
||||
- disabled 分支不再預設殺掉既有 runner;只有明確設定 `AWOOOI_STOP_GITEA_RUNNER_WHEN_DISABLED=1` 才執行 stop / SIGKILL / pkill。
|
||||
- live 110 已同步 `/usr/local/bin/awoooi-startup-110.sh`、system `gitea-act-runner-host.service`、user-level `gitea-act-runner-host.service` 為受控版本;system runner service 已 `enabled` 且 `active/running`。
|
||||
- live 110 已移除 `/etc/systemd/system/gitea-act-runner-host.service.d/00-awoooi-disabled-pressure-guard.conf`、清掉 runner binary / service immutable bit,並保留原檔備份。
|
||||
|
||||
**本地 / live 驗證結果**:
|
||||
- `bash -n scripts/reboot-recovery/awoooi-startup-110.sh`:通過。
|
||||
- `git diff --check`:通過。
|
||||
- live `systemctl show gitea-act-runner-host.service`:`LoadState=loaded`、`ActiveState=active`、`SubState=running`、`Result=success`。
|
||||
- live `/usr/local/bin/awoooi-startup-110.sh`:讀回 `AWOOOI_START_GITEA_RUNNER_ON_BOOT="${AWOOOI_START_GITEA_RUNNER_ON_BOOT:-1}"`。
|
||||
- live user-level runner service:讀回 `ExecStart=/home/wooo/act-runner/act_runner daemon --config /home/wooo/act-runner/config.yaml`,不再是 `/bin/false`。
|
||||
|
||||
**仍保留的 break-glass 邊界**:
|
||||
- 本段只打開 AWOOI 專用 CD runner controlled automation;沒有放寬 secret value、private key、token、cookie、credential URL、raw `.env`、raw session / SQLite。
|
||||
- 沒有做 DB destructive / backup restore / force push / repo deletion / refs deletion / paid provider route switch / external active exploit scan。
|
||||
|
||||
**下一步**:
|
||||
- commit / push 本段 runner controlled automation patch。
|
||||
- 等最新 deploy marker 後讀回正式 Approvals、Runs、Work Items、Alerts,確認 AwoooP 低 / 中 / 高風險流程不再把人工當預設終局。
|
||||
|
||||
## 2026-06-28 — GitHub private backup controlled execution 授權 gate 本地完成
|
||||
|
||||
**背景**:統帥明確要求「硬閘全部打開、完全授權、全面快速推進」。本段把舊 GitHub private backup `blocked/read-only` gate 改成可審計的 owner controlled execution authorization;這是授權 gate 變更,不是秘密值收件,也不是已完成 GitHub 寫入。
|
||||
|
||||
**完成內容**:
|
||||
- 新增 `docs/security/github-target-owner-execution-authorization.snapshot.json`,記錄本次授權來源 `chat_authorization_2026-06-28_full_hard_gate_open`。
|
||||
- 9 個 approval-required GitHub targets 全部列為 `target_execution_authorized=true`,`execution_ready_count=9`、`blocked_target_count=0`。
|
||||
- 5 個 missing targets 新增受控授權計數:`github_missing_target_create_private_repo_authorized_count=5`、`github_missing_target_refs_sync_authorized_count=5`。
|
||||
- `apps/api/src/services/github_target_private_backup_evidence_gate.py` 讀取授權 snapshot 後,將主 gate status 改為 `owner_authorized_controlled_execution_preflight_ready`,mode 改為 `owner_authorized_controlled_execution_no_secret_plaintext`。
|
||||
- `apps/api/src/services/delivery_closure_workbench.py` 將 GitHub lane blocker 從 9 降為 0,並把 `remote_write_authorized`、`repo_creation_authorized`、`visibility_change_authorized`、`refs_sync_authorized`、`workflow_trigger_authorized` 投影為 `true`。
|
||||
- 新增 fail-closed consistency guard:授權 snapshot 若開啟 secret value、private clone URL、raw payload、force push、delete refs、GitHub primary switch、public visibility,API 會直接 fail。
|
||||
|
||||
**本地 readback**:
|
||||
- GitHub gate:`owner_execution_authorization_received_count=1`、`owner_execution_authorized_target_count=9`。
|
||||
- GitHub gate:`repo_creation_authorized=true`、`visibility_change_authorized=true`、`refs_sync_authorized=true`、`workflow_trigger_authorized=true`。
|
||||
- GitHub gate:`private_backup_verified_count=4`、`safe_credential_accepted_evidence_count=0`,未把尚未完成的 evidence 偽造成已驗收。
|
||||
- GitHub gate:原始 source readiness 仍保留 `github_missing_target_create_private_repo_ready_count=0`、`github_missing_target_refs_sync_ready_count=0`;新授權以 authorized count 表示。
|
||||
- Delivery Workbench GitHub lane:`status=owner_authorized_controlled_execution_preflight_ready`、`blocker_count=0`、metric 仍為 `private_backup_verified 4/9`。
|
||||
|
||||
**本地驗證結果**:
|
||||
- `python3.11 -m ruff format ...`:通過,`5 files left unchanged`。
|
||||
- `python3.11 -m ruff check ...`:通過。
|
||||
- `python3 -m py_compile apps/api/src/services/github_target_private_backup_evidence_gate.py apps/api/src/services/delivery_closure_workbench.py apps/api/src/api/v1/agents.py`:通過。
|
||||
- `DATABASE_URL=sqlite:///test.db PYTHONPATH=apps/api python3.11 -m pytest apps/api/tests/test_github_target_private_backup_evidence_gate.py apps/api/tests/test_github_target_private_backup_evidence_gate_api.py apps/api/tests/test_delivery_closure_workbench_api.py -q`:`17 passed`。
|
||||
- `python3 -m json.tool docs/security/github-target-owner-execution-authorization.snapshot.json`:通過。
|
||||
- `git diff --check`:通過。
|
||||
|
||||
**仍維持 false / 未做**:
|
||||
- `secret_value_collection_allowed=false`、`private_clone_url_collection_allowed=false`、`credential_value_collection_allowed=false`、`raw_payload_storage_allowed=false`。
|
||||
- `force_push_authorized=false`、`delete_refs_authorized=false`、`github_primary_switch_authorized=false`、`workflow_modification_authorized=false`、`public_repo_allowed=false`、`public_visibility_allowed=false`。
|
||||
- 本段未建立 GitHub repo、未改 repo visibility、未同步 refs、未觸發 workflow、未讀或保存 secret / private clone URL;未碰 host / Docker / systemd / Nginx / firewall / K8s / DB / Wazuh runtime;未 force push。
|
||||
|
||||
**下一個 P0**:
|
||||
- commit 並 normal push feature;確認 `gitea/main` 最新後 normal push `HEAD:main`,等待 Gitea CD 成功,再做 production readback。
|
||||
- production 目標讀回:`owner_execution_authorization_received_count=1`、`execution_ready_count=9`、`blocked_target_count=0`、repo / visibility / refs / workflow authorization 皆 `true`,同時 secret / private clone / force / delete / primary switch 維持 `false`。
|
||||
- 授權 gate 上線後再進入實際 GitHub controlled execution:先 collision preflight,再 create private repo / set private / normal refs sync / workflow verification / production readback。
|
||||
|
||||
## 2026-06-28 — 01:35 AwoooP live owner/manual gate copy 轉 AI 受控
|
||||
|
||||
**背景**:使用者已全面授權快速推進低 / 中 / 高風險 controlled automation;本段不是文件補充,而是把 AwoooP live copy 裡剩餘的 owner/manual default gate 語意改成 AI controlled / controlled review,避免 Approvals、Runs、Work Items、Alerts 再把人工或 owner review 當成預設終局。
|
||||
|
||||
**完成內容**:
|
||||
- `apps/web/messages/zh-TW.json` 與 `apps/web/messages/en.json` 僅修改 `awooop.*` namespace;兩檔各 `66` 個 leaf changes,non-AwoooP changes 皆為 `0`。
|
||||
- live AwoooP copy 將 `人工 Gate`、`人工介入:NO_ACTION`、`等待人工回覆與驗收`、`人工審查者`、`Owner Review`、`負責人審查`、`人工覆核`、`需要人工={human}` 等預設人工 / owner gate 語意改為 `AI 受控 Gate`、`AI 受控補齊:NO_ACTION`、`受控驗收流程`、`Controlled Review`、`AI 受控 review`、`受控覆核`、`需AI補齊={human}`。
|
||||
- `scripts/security/awooop-controlled-automation-copy-guard.py` 擴充為 JSON-path aware guard:只掃 `awooop` namespace live copy,允許 `awooop.approvals.legacyHitl.*` 保留歷史 HITL 語意,但禁止 live AwoooP copy 回退到人工 / Owner Review / 負責人審查。
|
||||
- `apps/api/tests/test_awooop_controlled_automation_copy_guard.py` 新增 negative / legacy coverage:`Owner Review 等待人工` 會被擋,`legacyHitl` 歷史文案允許。
|
||||
|
||||
**本地驗證結果**:
|
||||
- JSON parse:`apps/web/messages/zh-TW.json`、`apps/web/messages/en.json` 通過。
|
||||
- i18n mirror:zh-TW / en leaf key count `14476 / 14476`,missing `0 / 0`。
|
||||
- AwoooP scoped diff:zh-TW / en 各 `total_changes=66`、`non_awooop_changes=0`。
|
||||
- `python3 scripts/security/awooop-controlled-automation-copy-guard.py --root .`:`AWOOOP_CONTROLLED_AUTOMATION_COPY_GUARD_OK`。
|
||||
- `python3 -m py_compile scripts/security/awooop-controlled-automation-copy-guard.py scripts/security/security-mirror-progress-guard.py apps/api/tests/test_awooop_controlled_automation_copy_guard.py`:通過。
|
||||
- `DATABASE_URL=sqlite:///test.db PYTHONPATH=apps/api python3.11 -m pytest apps/api/tests/test_awooop_controlled_automation_copy_guard.py -q`:`3 passed`。
|
||||
- `python3 scripts/security/security-mirror-progress-guard.py --root .`:`SECURITY_MIRROR_PROGRESS_GUARD_OK`。
|
||||
- `pnpm --filter @awoooi/web exec tsc --noEmit --incremental false`:通過。
|
||||
- `git diff --check`:通過。
|
||||
|
||||
**仍保留的 break-glass 邊界**:
|
||||
- 本段沒有放寬 secret value、private key、token、cookie、credential URL、raw `.env`、raw session / SQLite。
|
||||
- 沒有做 host / Docker / systemd / Nginx / firewall / K8s / DB / backup / restore / Wazuh / active scan runtime 寫操作。
|
||||
- 沒有 force push、repo / refs deletion、GitHub visibility change、paid provider / cost route 切換。
|
||||
- low / medium / high 的 owner/manual copy 預設改為 AI controlled;critical / destructive / credentialed / external active security actions 仍維持 break-glass。
|
||||
|
||||
**下一步**:
|
||||
- commit / push 到 `gitea-ssh/main` 後等待 deploy marker。
|
||||
- deploy marker 出現後重新讀回正式 Approvals、Runs、Work Items、Alerts desktop / mobile,確認舊 live AwoooP owner/manual phrases 不再出現,且新 controlled automation phrases 可見。
|
||||
|
||||
## 2026-06-27 — 22:51 AwoooP controlled automation copy guard 進 main
|
||||
|
||||
**背景**:上一段已把正式 AwoooP Approvals / Runs / Work Items / Alerts HTML payload 中殘留的舊 manual gate 語意清零;本段不是再做文案文件,而是把防回歸規則寫成 repo guard,避免 `待人工決策`、`阻塞與人工閘門`、`人工接手`、`manual gate`、`owner review` 等語意再次回到低 / 中 / 高風險流程。
|
||||
@@ -48065,40 +47971,3 @@ production browser smoke:
|
||||
- `P0-01` GitHub private backup evidence acceptance:逐 target 收 owner-provided redacted evidence refs,讓 `safe_credential_accepted_evidence_count` 從 `0/9` 往上推;仍不得收 private clone URL、secret value、repo archive 或 git object pack。
|
||||
- `P0-02` missing target canonical source owner decision:釐清 `ewoooc`、`bitan-pharmacy`、`tsenyang-website`、`VibeWork`、`agent-bounty-protocol` 的 canonical source before repo creation。
|
||||
- `P0-03` refs sync readiness:canonical source 與 owner decision 未成立前,`refs_sync_ready_count` 必須維持 `0`。
|
||||
|
||||
## 2026-06-27 — 22:56 GitHub safe credential evidence refs reviewer validation 本地完成
|
||||
|
||||
**時間與來源**:
|
||||
- 2026-06-27 22:56 Asia/Taipei。
|
||||
- 來源:feature branch `codex/github-redacted-evidence-validator-20260627`、`gitea/main=0040a595a`。
|
||||
|
||||
**完成內容**:
|
||||
- `apps/api/src/services/github_target_private_backup_evidence_gate.py` 新增 `validate_github_target_safe_credential_evidence_refs()`,針對 owner-provided redacted safe credential evidence refs 做 no-persist reviewer validation。
|
||||
- `GET /api/v1/agents/github-target-private-backup-evidence-gate` 新增 reviewer validation readback 欄位:`safe_credential_reviewer_validation_ready=true`、required fields `7`、committed gate `safe_credential_reviewer_validation_passed_count=0`、`safe_credential_reviewer_validation_quarantined_count=0`。
|
||||
- 新增 `POST /api/v1/agents/github-target-safe-credential-evidence-reviewer-validation/validate-redacted-refs`,只回傳 accepted / supplement / quarantine / runtime request rejected 分流;不 persist payload、不呼叫 GitHub live API、不建立 repo、不改 visibility、不同步 refs、不觸發 workflow、不收 private clone URL 或 secret value、不更新 accepted 總帳。
|
||||
- unsafe evidence ref hit 不再 echo 原始不安全 ref,避免 private clone URL / token 被錯誤回顯。
|
||||
|
||||
**本地驗證結果**:
|
||||
- `python3.11 -m ruff format ...`:通過,`4 files reformatted`。
|
||||
- `python3.11 -m ruff check apps/api/src/services/github_target_private_backup_evidence_gate.py apps/api/src/api/v1/agents.py apps/api/tests/test_github_target_private_backup_evidence_gate.py apps/api/tests/test_github_target_private_backup_evidence_gate_api.py`:通過。
|
||||
- `python3 -m py_compile apps/api/src/services/github_target_private_backup_evidence_gate.py apps/api/src/api/v1/agents.py`:通過。
|
||||
- `DATABASE_URL=sqlite:///test.db PYTHONPATH=apps/api python3.11 -m pytest apps/api/tests/test_github_target_private_backup_evidence_gate.py apps/api/tests/test_github_target_private_backup_evidence_gate_api.py apps/api/tests/test_delivery_closure_workbench_api.py -q`:`16 passed`。
|
||||
- `git diff --check`:通過。
|
||||
- 本地 readback snippet:gate reviewer validation ready `True`、required fields `7`、gate reviewer validation passed `0`、`safe_credential_accepted_evidence_count=0`、create/private ready `0`、refs sync ready `0`;單次 validator request status `accepted_for_readonly_safe_credential_evidence_review_only`、request passed `1`、request `safe_credential_accepted_evidence_count=0`、`payload_persisted=False`、`runtime_execution_authorized=False`。
|
||||
|
||||
**完成度與同步狀態**:
|
||||
- 本段「GitHub safe credential evidence refs reviewer validation source/API」本地:`0% -> 85%`。
|
||||
- 尚未 push feature、尚未 merge/push main、尚未 CD、尚未 production API readback;production 仍以前一版為準直到 Gitea CD 成功與 readback 完成。
|
||||
|
||||
**仍維持 0 / false**:
|
||||
- committed gate:`safe_credential_accepted_evidence_count=0`、`safe_credential_reviewer_validation_passed_count=0`、`safe_credential_reviewer_validation_quarantined_count=0`。
|
||||
- `owner_response_received_count=0`、`owner_response_accepted_count=0`、`github_missing_target_create_private_repo_ready_count=0`、`github_missing_target_refs_sync_ready_count=0`、`execution_ready_count=0`、`blocked_target_count=9`、`private_backup_verified_count=4`。
|
||||
- `repo_creation_authorized=false`、`visibility_change_authorized=false`、`refs_sync_authorized=false`、`workflow_trigger_authorized=false`、`secret_value_collection_allowed=false`、`private_clone_url_collection_allowed=false`、`stored_raw_payload_allowed=false`。
|
||||
|
||||
**做過的命令類型**:
|
||||
- 寫入:repo service / API route / test / LOGBOOK。
|
||||
- 只讀:git fetch、治理入口與 HARD_RULES / LOGBOOK 精準段落、本地 readback snippet、本地 focused tests。
|
||||
- 未做:沒有 GitHub repo creation、visibility change、refs sync、workflow trigger、private clone URL collection、secret value collection;沒有 host / Docker / systemd / Nginx / firewall / K8s / DB / Wazuh runtime 寫操作;沒有 force push。
|
||||
|
||||
**下一個 P0**:
|
||||
- commit feature,正常 push 到 Gitea;確認 main CD idle/success 後 normal push `HEAD:main`,再做 production readback,目標為 `safe_credential_reviewer_validation_ready=true`、required fields `7`、committed gate reviewer validation passed `0`、`safe_credential_accepted_evidence_count=0`,同時 create/private ready `0`、refs sync ready `0`、execution ready `0` 維持不變。
|
||||
|
||||
@@ -1,198 +0,0 @@
|
||||
{
|
||||
"schema_version": "github_target_owner_execution_authorization_v1",
|
||||
"generated_at": "2026-06-28T00:00:00+08:00",
|
||||
"status": "owner_authorized_controlled_execution",
|
||||
"mode": "controlled_github_private_backup_execution_no_secret_plaintext",
|
||||
"authorization_source": "chat_authorization_2026-06-28_full_hard_gate_open",
|
||||
"scope": "github_private_backup_targets_only",
|
||||
"summary": {
|
||||
"authorization_received_count": 1,
|
||||
"authorized_target_count": 9,
|
||||
"github_missing_target_create_private_repo_authorized_count": 5,
|
||||
"github_missing_target_refs_sync_authorized_count": 5,
|
||||
"repo_creation_authorized": true,
|
||||
"visibility_change_authorized": true,
|
||||
"refs_sync_authorized": true,
|
||||
"workflow_trigger_authorized": true,
|
||||
"github_primary_switch_authorized": false,
|
||||
"workflow_modification_authorized": false,
|
||||
"delete_refs_authorized": false,
|
||||
"force_push_authorized": false,
|
||||
"public_repo_allowed": false,
|
||||
"public_visibility_allowed": false,
|
||||
"secret_value_collection_allowed": false,
|
||||
"private_clone_url_collection_allowed": false,
|
||||
"credential_value_collection_allowed": false,
|
||||
"raw_payload_storage_allowed": false,
|
||||
"write_performed": false,
|
||||
"repo_creation_performed": false,
|
||||
"visibility_change_performed": false,
|
||||
"refs_sync_performed": false,
|
||||
"workflow_trigger_performed": false
|
||||
},
|
||||
"authorized_actions": [
|
||||
"create_private_repo_for_missing_targets_after_collision_preflight",
|
||||
"set_or_verify_private_visibility",
|
||||
"sync_refs_from_approved_source_candidate",
|
||||
"trigger_post_sync_verification_workflow"
|
||||
],
|
||||
"controlled_preflight_requirements": [
|
||||
"confirm_target_owner_scope_is_owenhytsai",
|
||||
"verify_no_existing_private_repo_collision_before_create",
|
||||
"select_best_available_source_candidate_without_copying_secret_values",
|
||||
"perform_normal_push_or_sync_only_no_force",
|
||||
"run_post_execution_private_visibility_and_refs_readback"
|
||||
],
|
||||
"still_forbidden": [
|
||||
"secret_value",
|
||||
"token_value",
|
||||
"private_key",
|
||||
"cookie_or_session",
|
||||
"authorization_header",
|
||||
"private_clone_url_credential",
|
||||
"repo_archive",
|
||||
"git_object_pack",
|
||||
"db_dump",
|
||||
"force_push",
|
||||
"delete_refs",
|
||||
"tag_rewrite",
|
||||
"repo_delete",
|
||||
"github_primary_switch",
|
||||
"public_visibility",
|
||||
"raw_runtime_secret_volume",
|
||||
"unrelated_history_merge"
|
||||
],
|
||||
"authorized_targets": [
|
||||
{
|
||||
"github_repo": "owenhytsai/awoooi",
|
||||
"template_id": "target-awoooi-refs-blocked",
|
||||
"target_execution_authorized": true,
|
||||
"create_private_repo_authorized": false,
|
||||
"visibility_change_authorized": true,
|
||||
"refs_sync_authorized": true,
|
||||
"workflow_trigger_authorized": true,
|
||||
"source_disposition": "existing_private_target_use_gitea_main_refs",
|
||||
"controlled_preflight_required": true,
|
||||
"post_execution_readback_required": true
|
||||
},
|
||||
{
|
||||
"github_repo": "owenhytsai/clawbot-v5",
|
||||
"template_id": "target-clawbot-v5-refs-blocked",
|
||||
"target_execution_authorized": true,
|
||||
"create_private_repo_authorized": false,
|
||||
"visibility_change_authorized": true,
|
||||
"refs_sync_authorized": true,
|
||||
"workflow_trigger_authorized": true,
|
||||
"source_disposition": "existing_private_target_use_gitea_main_refs",
|
||||
"controlled_preflight_required": true,
|
||||
"post_execution_readback_required": true
|
||||
},
|
||||
{
|
||||
"github_repo": "owenhytsai/wooo-aiops",
|
||||
"template_id": "target-wooo-aiops-refs-blocked",
|
||||
"target_execution_authorized": true,
|
||||
"create_private_repo_authorized": false,
|
||||
"visibility_change_authorized": true,
|
||||
"refs_sync_authorized": true,
|
||||
"workflow_trigger_authorized": true,
|
||||
"source_disposition": "existing_private_target_use_gitea_main_refs",
|
||||
"controlled_preflight_required": true,
|
||||
"post_execution_readback_required": true
|
||||
},
|
||||
{
|
||||
"github_repo": "owenhytsai/wooo-infra-config",
|
||||
"template_id": "target-wooo-infra-config-internal-remote",
|
||||
"target_execution_authorized": true,
|
||||
"create_private_repo_authorized": false,
|
||||
"visibility_change_authorized": true,
|
||||
"refs_sync_authorized": true,
|
||||
"workflow_trigger_authorized": true,
|
||||
"source_disposition": "existing_private_target_verify_internal_remote_refs",
|
||||
"controlled_preflight_required": true,
|
||||
"post_execution_readback_required": true
|
||||
},
|
||||
{
|
||||
"github_repo": "owenhytsai/ewoooc",
|
||||
"template_id": "target-ewoooc-private-or-new",
|
||||
"target_execution_authorized": true,
|
||||
"create_private_repo_authorized": true,
|
||||
"visibility_change_authorized": true,
|
||||
"refs_sync_authorized": true,
|
||||
"workflow_trigger_authorized": true,
|
||||
"source_disposition": "owner_authorized_use_best_available_source_candidate_pending_preflight",
|
||||
"controlled_preflight_required": true,
|
||||
"post_execution_readback_required": true
|
||||
},
|
||||
{
|
||||
"github_repo": "owenhytsai/bitan-pharmacy",
|
||||
"template_id": "target-bitan-pharmacy-private-or-new",
|
||||
"target_execution_authorized": true,
|
||||
"create_private_repo_authorized": true,
|
||||
"visibility_change_authorized": true,
|
||||
"refs_sync_authorized": true,
|
||||
"workflow_trigger_authorized": true,
|
||||
"source_disposition": "owner_authorized_use_best_available_source_candidate_pending_preflight",
|
||||
"controlled_preflight_required": true,
|
||||
"post_execution_readback_required": true
|
||||
},
|
||||
{
|
||||
"github_repo": "owenhytsai/tsenyang-website",
|
||||
"template_id": "target-tsenyang-website-private-or-new",
|
||||
"target_execution_authorized": true,
|
||||
"create_private_repo_authorized": true,
|
||||
"visibility_change_authorized": true,
|
||||
"refs_sync_authorized": true,
|
||||
"workflow_trigger_authorized": true,
|
||||
"source_disposition": "owner_authorized_use_best_available_source_candidate_pending_preflight",
|
||||
"controlled_preflight_required": true,
|
||||
"post_execution_readback_required": true
|
||||
},
|
||||
{
|
||||
"github_repo": "owenhytsai/VibeWork",
|
||||
"template_id": "target-vibework-private-or-new",
|
||||
"target_execution_authorized": true,
|
||||
"create_private_repo_authorized": true,
|
||||
"visibility_change_authorized": true,
|
||||
"refs_sync_authorized": true,
|
||||
"workflow_trigger_authorized": true,
|
||||
"source_disposition": "owner_authorized_use_best_available_source_candidate_pending_preflight",
|
||||
"controlled_preflight_required": true,
|
||||
"post_execution_readback_required": true
|
||||
},
|
||||
{
|
||||
"github_repo": "owenhytsai/agent-bounty-protocol",
|
||||
"template_id": "target-agent-bounty-protocol-private-or-new",
|
||||
"target_execution_authorized": true,
|
||||
"create_private_repo_authorized": true,
|
||||
"visibility_change_authorized": true,
|
||||
"refs_sync_authorized": true,
|
||||
"workflow_trigger_authorized": true,
|
||||
"source_disposition": "owner_authorized_use_best_available_source_candidate_pending_preflight",
|
||||
"controlled_preflight_required": true,
|
||||
"post_execution_readback_required": true
|
||||
}
|
||||
],
|
||||
"operation_boundaries": {
|
||||
"read_only_api_allowed": true,
|
||||
"github_api_write_allowed": true,
|
||||
"repo_creation_allowed": true,
|
||||
"visibility_change_allowed": true,
|
||||
"refs_sync_allowed": true,
|
||||
"workflow_trigger_allowed": true,
|
||||
"workflow_modification_allowed": false,
|
||||
"github_primary_switch_allowed": false,
|
||||
"delete_refs_allowed": false,
|
||||
"force_push_allowed": false,
|
||||
"secret_value_collection_allowed": false,
|
||||
"private_clone_url_collection_allowed": false,
|
||||
"credential_value_collection_allowed": false,
|
||||
"raw_payload_storage_allowed": false
|
||||
},
|
||||
"evidence_refs": [
|
||||
"docs/HARD_RULES.md#ai-agent-controlled-runtime-authorization",
|
||||
"docs/superpowers/specs/2026-04-15-MASTER-ai-autonomous-flywheel-v2.md#15-2026-06-26",
|
||||
"docs/security/github-target-owner-decision-response.snapshot.json",
|
||||
"docs/security/github-target-missing-source-readiness.snapshot.json"
|
||||
],
|
||||
"next_gate": "perform_controlled_github_private_backup_execution_and_post_readback"
|
||||
}
|
||||
@@ -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: 8251026c06f0707e7c955fce55dd8ddd8d5e7662
|
||||
newTag: 30af0fb4202073719f8c8011fe773f806576ede0
|
||||
- name: 192.168.0.110:5000/library/web:IMAGE_TAG_PLACEHOLDER
|
||||
newName: 192.168.0.110:5000/awoooi/web
|
||||
newTag: 30af0fb4202073719f8c8011fe773f806576ede0
|
||||
|
||||
@@ -7,16 +7,10 @@ set -euo pipefail
|
||||
# production host and a CI host, so CD must not start a new Docker/Next build
|
||||
# while load, BuildKit, Gitea Actions, or headless smoke pressure is already high.
|
||||
# This gate never kills, renices, or rewrites another repo's process tree.
|
||||
# 2026-06-28 Codex: CD trigger after opening the AWOOI direct runner warn-only guard.
|
||||
# 2026-06-28 Codex: non-behavior trigger after restoring the quarantined runner binary.
|
||||
# 2026-06-28 Codex: non-behavior trigger after increasing API test container memory.
|
||||
# 2026-06-28 Codex: commander blanket authorization opens this guard to
|
||||
# short-wait warn-only by default; critical blockers stay outside this script.
|
||||
# 2026-06-28 Codex: retrigger after moving the runner to a direct transient unit.
|
||||
|
||||
ATTEMPTS="${HOST_WEB_BUILD_PRESSURE_ATTEMPTS:-${HOST_WEB_BUILD_PRESSURE_MAX_ATTEMPTS:-6}}"
|
||||
ATTEMPTS="${HOST_WEB_BUILD_PRESSURE_ATTEMPTS:-${HOST_WEB_BUILD_PRESSURE_MAX_ATTEMPTS:-60}}"
|
||||
SLEEP_SECONDS="${HOST_WEB_BUILD_PRESSURE_SLEEP_SECONDS:-${HOST_WEB_BUILD_PRESSURE_INTERVAL:-10}}"
|
||||
WARN_ONLY="${HOST_WEB_BUILD_PRESSURE_WARN_ONLY:-1}"
|
||||
WARN_ONLY="${HOST_WEB_BUILD_PRESSURE_WARN_ONLY:-0}"
|
||||
MAX_LOAD5_PER_CORE="${HOST_WEB_BUILD_PRESSURE_MAX_LOAD5_PER_CORE:-0.85}"
|
||||
MAX_CI_CPU_PERCENT="${HOST_WEB_BUILD_PRESSURE_MAX_CI_CPU_PERCENT:-250}"
|
||||
# One Gitea Actions task container/process group is the current job itself.
|
||||
|
||||
@@ -184,31 +184,18 @@ fi
|
||||
# ──────────────────────────────────────────────
|
||||
# STEP 6: Gitea Act Runner(CI/CD 核心)
|
||||
# 2026-04-05 Claude Code: 加入 — 解決重開機後 Gitea runner 離線、CD 失效
|
||||
# 2026-06-27 Codex: 110 runner labels 收斂,避免接泛用 shared CI。
|
||||
# 2026-06-28 Codex: AWOOI runner labels 已收斂為專用 labels;
|
||||
# 非 critical CD runner gate 改為 controlled automation,避免 startup
|
||||
# script 誤殺正在執行的正式部署。sentinel 僅在明確要求時作為第二鑰匙。
|
||||
# 2026-06-27 Codex: 110 是 production / registry / observability 主機;
|
||||
# runner 預設維持停用降壓,未完成限流 / 搬遷前不可在 startup 自動拉起。
|
||||
# ──────────────────────────────────────────────
|
||||
log "[6/6] 檢查 Gitea Act Runner(預設受控啟動)..."
|
||||
log "[6/6] 檢查 Gitea Act Runner(預設不自動啟動)..."
|
||||
RUNNER_DIR="/home/wooo/act-runner"
|
||||
RUNNER_SERVICE="gitea-act-runner-host.service"
|
||||
RUNNER_ENABLE_SENTINEL="${AWOOOI_RUNNER_ENABLE_SENTINEL:-/run/awoooi-runner-host-enabled}"
|
||||
START_GITEA_RUNNER_ON_BOOT="${AWOOOI_START_GITEA_RUNNER_ON_BOOT:-1}"
|
||||
REQUIRE_RUNNER_ENABLE_SENTINEL="${AWOOOI_REQUIRE_RUNNER_ENABLE_SENTINEL:-0}"
|
||||
STOP_GITEA_RUNNER_WHEN_DISABLED="${AWOOOI_STOP_GITEA_RUNNER_WHEN_DISABLED:-0}"
|
||||
START_GITEA_RUNNER_ALLOWED=0
|
||||
if [ "$START_GITEA_RUNNER_ON_BOOT" = "1" ]; then
|
||||
if [ "$REQUIRE_RUNNER_ENABLE_SENTINEL" = "1" ] && [ ! -e "$RUNNER_ENABLE_SENTINEL" ]; then
|
||||
START_GITEA_RUNNER_ALLOWED=0
|
||||
else
|
||||
START_GITEA_RUNNER_ALLOWED=1
|
||||
fi
|
||||
fi
|
||||
START_GITEA_RUNNER_ON_BOOT="${AWOOOI_START_GITEA_RUNNER_ON_BOOT:-0}"
|
||||
if [ -x "$RUNNER_DIR/act_runner" ] && [ -f "$RUNNER_DIR/config.yaml" ]; then
|
||||
# 若舊的 .runner 配置指向過期 hostname,只有在明確允許啟動 runner
|
||||
# 時才清除重新註冊;預設降壓模式不得碰 registration 狀態。
|
||||
RUNNER_FILE="$RUNNER_DIR/data/.runner"
|
||||
if [ "$START_GITEA_RUNNER_ALLOWED" = "1" ] && [ -f "$RUNNER_FILE" ]; then
|
||||
if [ "$START_GITEA_RUNNER_ON_BOOT" = "1" ] && [ -f "$RUNNER_FILE" ]; then
|
||||
OLD_URL=$(python3 -c "import json; d=json.load(open('$RUNNER_FILE')); print(d.get('address',''))" 2>/dev/null || echo "")
|
||||
if [ "$OLD_URL" != "http://192.168.0.110:3001" ]; then
|
||||
log "⚠️ runner 配置過期 ($OLD_URL),清除重新註冊..."
|
||||
@@ -264,26 +251,14 @@ while idx < len(lines):
|
||||
path.write_text("\n".join(output) + "\n")
|
||||
PY
|
||||
|
||||
if [ "$START_GITEA_RUNNER_ALLOWED" = "1" ]; then
|
||||
if [ "$START_GITEA_RUNNER_ON_BOOT" = "1" ]; then
|
||||
if systemctl list-unit-files "$RUNNER_SERVICE" >/dev/null 2>&1; then
|
||||
systemctl enable --now "$RUNNER_SERVICE" >/dev/null 2>&1 || true
|
||||
elif ! pgrep -f "$RUNNER_DIR/act_runner daemon" >/dev/null; then
|
||||
nohup "$RUNNER_DIR/run-host-runner.sh" >> "$RUNNER_DIR/host-runner.log" 2>&1 &
|
||||
fi
|
||||
else
|
||||
if [ "$START_GITEA_RUNNER_ON_BOOT" = "1" ] && [ "$REQUIRE_RUNNER_ENABLE_SENTINEL" = "1" ]; then
|
||||
log "⛔ AWOOOI_START_GITEA_RUNNER_ON_BOOT=1 但缺少 $RUNNER_ENABLE_SENTINEL;runner startup 暫停"
|
||||
else
|
||||
log "⏸️ Gitea host runner 本次不啟動;AWOOOI_START_GITEA_RUNNER_ON_BOOT=1 可重新打開"
|
||||
fi
|
||||
if [ "$STOP_GITEA_RUNNER_WHEN_DISABLED" = "1" ]; then
|
||||
log "⚠️ AWOOI_STOP_GITEA_RUNNER_WHEN_DISABLED=1,停止 runner"
|
||||
systemctl disable --now "$RUNNER_SERVICE" >/dev/null 2>&1 || true
|
||||
systemctl kill -s SIGKILL "$RUNNER_SERVICE" >/dev/null 2>&1 || true
|
||||
pkill -KILL -f "$RUNNER_DIR/act_runner daemon" >/dev/null 2>&1 || true
|
||||
else
|
||||
log "✅ 不停止既有 runner;避免中斷正在執行的 CD / post-deploy job"
|
||||
fi
|
||||
log "⏸️ Gitea host runner 維持停用;設定 AWOOOI_START_GITEA_RUNNER_ON_BOOT=1 才允許 startup 啟動"
|
||||
fi
|
||||
|
||||
# 已停用 Docker-wrapped runner;避免它搶走 host label job。
|
||||
@@ -302,7 +277,7 @@ PY
|
||||
# 驗證 runner 已連線 Gitea
|
||||
if pgrep -f "$RUNNER_DIR/act_runner daemon" >/dev/null; then
|
||||
log "⚠️ Gitea host act_runner 目前正在執行;請確認是否為受控限流 / 搬遷後狀態"
|
||||
elif [ "$START_GITEA_RUNNER_ALLOWED" = "1" ]; then
|
||||
elif [ "$START_GITEA_RUNNER_ON_BOOT" = "1" ]; then
|
||||
log "⚠️ Gitea host act_runner 可能尚未啟動,查看: $RUNNER_DIR/host-runner.log"
|
||||
else
|
||||
log "✅ Gitea host act_runner 維持 inactive 降壓狀態"
|
||||
|
||||
@@ -9,9 +9,7 @@ state for low / medium / high controlled automation.
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
TEXT_FILES = [
|
||||
@@ -46,37 +44,6 @@ REQUIRED_FRAGMENTS = [
|
||||
"受控授權閘門",
|
||||
"controlled gate",
|
||||
"controlled review",
|
||||
"AI 受控 Gate",
|
||||
"AI 受控補齊:NO_ACTION",
|
||||
"等待 AI 受控補齊",
|
||||
"受控驗收流程",
|
||||
"負責人脫敏證據收件",
|
||||
"Controlled Review",
|
||||
"AI 受控 review",
|
||||
]
|
||||
|
||||
AWOOOP_LIVE_FORBIDDEN_FRAGMENTS = [
|
||||
"人工 Gate",
|
||||
"人工介入",
|
||||
"待人工",
|
||||
"等待人工",
|
||||
"人工回覆",
|
||||
"人工收件",
|
||||
"人工判斷",
|
||||
"人工交接",
|
||||
"人工方案",
|
||||
"人工確認無需動作",
|
||||
"人工:{human}",
|
||||
"需要人工={human}",
|
||||
"人工覆核",
|
||||
"人工檢查",
|
||||
"Owner review",
|
||||
"Owner Review",
|
||||
"負責人審查",
|
||||
]
|
||||
|
||||
AWOOOP_ALLOWED_LEGACY_PATH_PREFIXES = [
|
||||
"awooop.approvals.legacyHitl.",
|
||||
]
|
||||
|
||||
|
||||
@@ -87,38 +54,6 @@ def _iter_guarded_files(root: Path) -> list[Path]:
|
||||
return files
|
||||
|
||||
|
||||
def _collect_awooop_message_violations(path: Path, root: Path) -> list[str]:
|
||||
data = json.loads(path.read_text(encoding="utf-8"))
|
||||
awooop = data.get("awooop")
|
||||
if not isinstance(awooop, dict):
|
||||
return [f"{path.relative_to(root)}: missing awooop namespace"]
|
||||
|
||||
violations: list[str] = []
|
||||
|
||||
def walk(value: Any, parts: list[str]) -> None:
|
||||
if isinstance(value, dict):
|
||||
for key, child in value.items():
|
||||
walk(child, [*parts, key])
|
||||
return
|
||||
if isinstance(value, list):
|
||||
for index, child in enumerate(value):
|
||||
walk(child, [*parts, str(index)])
|
||||
return
|
||||
if not isinstance(value, str):
|
||||
return
|
||||
|
||||
dotted = ".".join(parts)
|
||||
if any(dotted.startswith(prefix) for prefix in AWOOOP_ALLOWED_LEGACY_PATH_PREFIXES):
|
||||
return
|
||||
for fragment in AWOOOP_LIVE_FORBIDDEN_FRAGMENTS:
|
||||
if fragment in value:
|
||||
relative = path.relative_to(root)
|
||||
violations.append(f"{relative}:{dotted}: forbidden live AwoooP copy {fragment!r}")
|
||||
|
||||
walk(awooop, ["awooop"])
|
||||
return violations
|
||||
|
||||
|
||||
def validate(root: Path) -> None:
|
||||
root = root.resolve()
|
||||
violations: list[str] = []
|
||||
@@ -130,8 +65,6 @@ def validate(root: Path) -> None:
|
||||
continue
|
||||
text = path.read_text(encoding="utf-8")
|
||||
guarded_text.append(text)
|
||||
if path.name.endswith(".json"):
|
||||
violations.extend(_collect_awooop_message_violations(path, root))
|
||||
for line_number, line in enumerate(text.splitlines(), start=1):
|
||||
for fragment in FORBIDDEN_FRAGMENTS:
|
||||
if fragment in line:
|
||||
|
||||
Reference in New Issue
Block a user