Compare commits

..

23 Commits

Author SHA1 Message Date
Your Name
5e37777c87 ci(cd): retrigger with direct transient runner
Some checks failed
CD Pipeline / tests (push) Waiting to run
Code Review / ai-code-review (push) Successful in 16s
CD Pipeline / build-and-deploy (push) Has been cancelled
CD Pipeline / post-deploy-checks (push) Has been cancelled
2026-06-28 02:43:07 +08:00
Your Name
aaf3f7bfab ci(cd): open host pressure gate warn-only by default
Some checks failed
CD Pipeline / tests (push) Waiting to run
Code Review / ai-code-review (push) Waiting to run
CD Pipeline / build-and-deploy (push) Has been cancelled
CD Pipeline / post-deploy-checks (push) Has been cancelled
2026-06-28 02:38:29 +08:00
Your Name
0cb66f371c chore(cd): deploy api 8251026 [skip ci] 2026-06-28 02:35:26 +08:00
Your Name
940adca8d5 ci(cd): raise api test memory for host runner deploy
Some checks failed
CD Pipeline / tests (push) Waiting to run
Code Review / ai-code-review (push) Successful in 25s
CD Pipeline / build-and-deploy (push) Has been cancelled
CD Pipeline / post-deploy-checks (push) Has been cancelled
Ansible / Reboot Recovery Contract / validate (push) Failing after 14m5s
2026-06-28 02:33:45 +08:00
Your Name
ebb77719e2 ci(cd): retrigger deploy after runner binary restore
Some checks failed
CD Pipeline / tests (push) Failing after 1m23s
CD Pipeline / build-and-deploy (push) Has been skipped
CD Pipeline / post-deploy-checks (push) Has been skipped
Code Review / ai-code-review (push) Successful in 21s
2026-06-28 02:29:57 +08:00
Your Name
8251026c06 ci(cd): retrigger deploy after runner binary restore 2026-06-28 02:27:52 +08:00
Your Name
5647e3e74c ci(cd): trigger deploy with warn-only direct runner
Some checks failed
CD Pipeline / tests (push) Waiting to run
Code Review / ai-code-review (push) Successful in 22s
CD Pipeline / build-and-deploy (push) Has been cancelled
CD Pipeline / post-deploy-checks (push) Has been cancelled
2026-06-28 02:21:06 +08:00
Your Name
d4a676b7db ci(cd): trigger deploy with direct runner open
Some checks failed
CD Pipeline / tests (push) Waiting to run
Code Review / ai-code-review (push) Failing after 16s
CD Pipeline / build-and-deploy (push) Has been cancelled
CD Pipeline / post-deploy-checks (push) Has been cancelled
2026-06-28 02:17:39 +08:00
Your Name
b6c600e24d fix(reboot): open host runner controlled automation
Some checks failed
CD Pipeline / tests (push) Waiting to run
Ansible / Reboot Recovery Contract / validate (push) Successful in 1m16s
Code Review / ai-code-review (push) Successful in 22s
CD Pipeline / build-and-deploy (push) Has been cancelled
CD Pipeline / post-deploy-checks (push) Has been cancelled
2026-06-28 02:09:23 +08:00
Your Name
aa41db6875 ci(cd): trigger deploy with runner double key open
Some checks failed
Code Review / ai-code-review (push) Waiting to run
CD Pipeline / build-and-deploy (push) Has been cancelled
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / tests (push) Has been cancelled
2026-06-28 02:08:42 +08:00
Your Name
7c3b1c0ab9 fix(reboot): require double key for host runner sentinel
Some checks failed
Ansible / Reboot Recovery Contract / validate (push) Successful in 1m37s
Code Review / ai-code-review (push) Has been cancelled
2026-06-28 02:06:24 +08:00
Your Name
77ba5ed517 fix(reboot): honor runner sentinel during deploy window 2026-06-28 02:02:56 +08:00
Your Name
2a1cd3cc8b fix(reboot): fail closed host runner startup
Some checks failed
Ansible / Reboot Recovery Contract / validate (push) Successful in 1m16s
Code Review / ai-code-review (push) Successful in 17s
AI 技術雷達監控 / ai-technology-watch (push) Failing after 11m45s
2026-06-28 01:59:15 +08:00
Your Name
786c50c00e ci(cd): retrigger deploy after runner guard open
Some checks failed
CD Pipeline / build-and-deploy (push) Blocked by required conditions
CD Pipeline / post-deploy-checks (push) Blocked by required conditions
Code Review / ai-code-review (push) Successful in 21s
CD Pipeline / tests (push) Failing after 12m15s
2026-06-28 01:55:37 +08:00
Your Name
20982decf7 ci(cd): trigger deploy after direct runner restore 2026-06-28 01:53:45 +08:00
Your Name
6c6f2621b8 Merge remote-tracking branch 'gitea/main' into codex/github-redacted-evidence-validator-20260627
Some checks failed
Ansible / Reboot Recovery Contract / validate (push) Waiting to run
CD Pipeline / tests (push) Waiting to run
Code Review / ai-code-review (push) Successful in 23s
CD Pipeline / build-and-deploy (push) Has been cancelled
CD Pipeline / post-deploy-checks (push) Has been cancelled
# Conflicts:
#	docs/LOGBOOK.md
2026-06-28 01:46:02 +08:00
Your Name
060122fcd6 fix(awooop): open live copy to controlled automation
Some checks failed
Ansible / Reboot Recovery Contract / validate (push) Waiting to run
CD Pipeline / build-and-deploy (push) Blocked by required conditions
Code Review / ai-code-review (push) Waiting to run
CD Pipeline / tests (push) Successful in 1m39s
CD Pipeline / post-deploy-checks (push) Has been cancelled
2026-06-28 01:38:12 +08:00
Your Name
cff10d6a66 docs(governance): open non-critical gates to controlled apply
Some checks failed
Ansible / Reboot Recovery Contract / validate (push) Has been cancelled
2026-06-28 01:37:12 +08:00
Your Name
7c7466dbfc Merge remote-tracking branch 'gitea/main' into codex/github-redacted-evidence-validator-20260627 2026-06-28 01:37:08 +08:00
Your Name
4de66bde2e feat(github): authorize controlled backup execution 2026-06-28 01:37:02 +08:00
Your Name
22052ef74b ci(cd): trigger deploy after runner gate restore
Some checks failed
CD Pipeline / tests (push) Successful in 1m41s
Code Review / ai-code-review (push) Successful in 16s
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled
2026-06-28 01:33:15 +08:00
Your Name
d4fcce4170 Merge remote-tracking branch 'gitea/main' into codex/github-redacted-evidence-validator-20260627 2026-06-27 23:01:11 +08:00
Your Name
82e9d780bb feat(github): validate redacted evidence refs 2026-06-27 22:59:31 +08:00
16 changed files with 1805 additions and 246 deletions

View File

@@ -205,7 +205,8 @@ jobs:
docker run --rm \
--name "awoooi-cd-${GITHUB_RUN_ID:-manual}-${GITHUB_RUN_ATTEMPT:-1}-api-tests" \
--cpus "2.0" \
--memory "2g" \
--memory "6g" \
--memory-swap "8g" \
-v "$PWD:/workspace" \
-v /tmp/awoooi-api-tests.sh:/tmp/awoooi-api-tests.sh:ro \
-v awoooi-api-venv-cache:/opt/api-venv \

View File

@@ -340,6 +340,7 @@ 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,
@@ -1027,6 +1028,44 @@ 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],

View File

@@ -52,6 +52,7 @@ 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"))
@@ -61,14 +62,18 @@ 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": {
@@ -93,14 +98,20 @@ 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")),
@@ -111,7 +122,9 @@ 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": {
@@ -124,7 +137,9 @@ 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": {
@@ -137,7 +152,9 @@ 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),
@@ -146,7 +163,9 @@ 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(
@@ -166,17 +185,28 @@ 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": False,
"repo_creation_authorized": False,
"refs_sync_authorized": False,
"workflow_trigger_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,
"secret_values_collected": False,
},
"source_statuses": source_statuses,
@@ -185,11 +215,19 @@ def build_delivery_closure_workbench(
"operation_boundaries": {
"read_only_api_allowed": True,
"runtime_write_allowed": False,
"remote_write_allowed": False,
"repo_creation_allowed": False,
"visibility_change_allowed": False,
"refs_sync_allowed": False,
"workflow_trigger_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,
"secret_value_collection_allowed": False,
"backup_restore_execution_allowed": False,
"active_scan_allowed": False,
@@ -204,7 +242,9 @@ 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 "",
}
@@ -218,7 +258,9 @@ 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")
@@ -255,7 +297,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
@@ -307,7 +349,10 @@ 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)

View File

@@ -1,5 +1,6 @@
from __future__ import annotations
import json
import runpy
from pathlib import Path
@@ -13,3 +14,36 @@ 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 == []

View File

@@ -19,10 +19,11 @@ 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 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"]["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"]["secret_values_collected"] is False
assert data["summary"]["average_completion_percent"] >= 0
assert data["summary"]["high_risk_blocker_count"] > 0
@@ -36,10 +37,16 @@ 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"] == 9
assert lanes["github"]["status"] == "blocked_private_visibility_and_safe_credential_evidence_required"
assert lanes["github"]["blocker_count"] == 0
assert (
lanes["github"]["status"]
== "owner_authorized_controlled_execution_preflight_ready"
)
assert lanes["github"]["metric"]["verified"] == 4
assert lanes["github"]["metric"]["total"] == 9
assert all(0 <= lane["completion_percent"] <= 100 for lane in lanes.values())
@@ -48,11 +55,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 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["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["secret_value_collection_allowed"] is False
assert boundaries["backup_restore_execution_allowed"] is False
assert boundaries["active_scan_allowed"] is False

View File

@@ -9,6 +9,7 @@ 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
@@ -17,11 +18,10 @@ 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["status"]
== "blocked_private_visibility_and_safe_credential_evidence_required"
snapshot["mode"] == "owner_authorized_controlled_execution_no_secret_plaintext"
)
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,6 +54,13 @@ 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
@@ -67,6 +74,16 @@ 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
@@ -79,17 +96,48 @@ 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"]["blocked_target_count"] == 9
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"]["public_repo_allowed"] 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"]["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"]["secret_value_collection_allowed"] is False
assert snapshot["operation_boundaries"]["read_only_api_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"]["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"]["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"]
@@ -134,6 +182,25 @@ 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"]
@@ -159,7 +226,7 @@ def test_load_github_target_private_backup_evidence_gate_from_committed_snapshot
)
assert (
targets["owenhytsai/awoooi"]["safe_credential_evidence_submission_status"]
== "waiting_redacted_evidence_ref"
== "owner_execution_authorized_post_apply_readback_pending"
)
assert (
"redacted_metadata_pointer"
@@ -180,6 +247,10 @@ 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"]
@@ -196,6 +267,9 @@ 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"]
@@ -322,6 +396,21 @@ 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()
@@ -332,7 +421,9 @@ 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
@@ -344,8 +435,13 @@ 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
@@ -353,12 +449,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)
@@ -378,6 +474,92 @@ 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 (
@@ -387,6 +569,7 @@ 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)
@@ -420,3 +603,26 @@ 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",
}
],
}

View File

@@ -16,7 +16,8 @@ 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"] == "read_only_private_backup_evidence_gate"
assert data["mode"] == "owner_authorized_controlled_execution_no_secret_plaintext"
assert data["status"] == "owner_authorized_controlled_execution_preflight_ready"
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
@@ -25,6 +26,11 @@ 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
@@ -34,6 +40,11 @@ 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
@@ -41,18 +52,22 @@ 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"]["blocked_target_count"] == 9
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"]["public_repo_allowed"] is False
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"]["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"]["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 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"]["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"]["private_clone_url_collection_allowed"] is False
intake = data["owner_response_intake_readiness"]
assert (
@@ -81,10 +96,17 @@ 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"]
== "waiting_redacted_evidence_ref"
== "owner_execution_authorized_post_apply_readback_pending"
)
assert data["targets"][0]["safe_credential_raw_payload_storage_allowed"] is False
assert (
@@ -149,3 +171,57 @@ 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

View File

@@ -8505,7 +8505,7 @@
"detail": "來源事件是否回到 Run / Incident"
},
"gate": {
"label": "人工 Gate",
"label": "AI 受控 Gate",
"detail": "待處理工作 {work}"
},
"verify": {
@@ -8569,7 +8569,7 @@
"autoRepairedVerified": "已驗證自動修復",
"executionUnverified": "已執行但未驗證",
"executionFailed": "執行失敗",
"manualRequiredNoAction": "人工介入NO_ACTION",
"manualRequiredNoAction": "AI 受控補齊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 受控閘門與負責人審查"
"detail": "AI 受控閘門與受控 review"
},
"verifier": {
"title": "驗證",
@@ -8988,7 +8988,7 @@
},
"learning": {
"title": "KM / Trust 回寫",
"detail": "確認 KM、PlayBook trust 與學習責任已沉澱,避免下次重複人工判斷。"
"detail": "確認 KM、PlayBook trust 與學習責任已沉澱,避免下次重複 AI 補齊判斷。"
}
}
},
@@ -9035,7 +9035,7 @@
"items": {
"km": {
"title": "Knowledge Base",
"detail": "Hermes 草稿、陳舊 KM、負責人審查 與 stale ratio 回測。",
"detail": "Hermes 草稿、陳舊 KM、AI 受控 review 與 stale ratio 回測。",
"next": "下一步:把待補 KM 與 Incident / PlayBook / verifier 結果綁定。"
},
"playbook": {
@@ -9061,7 +9061,7 @@
},
"sources": {
"knowledgeBase": "Knowledge Base",
"ownerReview": "Owner review",
"ownerReview": "Controlled review",
"staleRatio": "Stale ratio",
"statusChain": "Status-chain",
"remediationQueue": "補救佇列",
@@ -9078,17 +9078,17 @@
}
},
"reportSourceGapOwnerReview": {
"eyebrow": "報表資料源 負責人審查",
"eyebrow": "報表資料源 AI 受控補齊",
"title": "報表資料源 PlayBook / Verifier 處置板",
"subtitle": "把 report-source-gap 從報表頁接到 Work Items每個缺口都要有 PlayBook 草案、Verifier 計畫、腳本 readback、排程 無發送 與 負責人審查。",
"loading": "正在讀取 report-source-gap 負責人審查 read model。",
"subtitle": "把 report-source-gap 從報表頁接到 Work Items每個缺口都要有 PlayBook 草案、Verifier 計畫、腳本 readback、排程無發送與 AI 受控 review。",
"loading": "正在讀取 report-source-gap AI 受控補齊 read model。",
"unavailable": "報表資料源 read model 尚未回應;不能把報表全 0 或缺資料判定為健康。",
"empty": "目前沒有 report-source-gap 負責人審查 卡。",
"empty": "目前沒有 report-source-gap AI 受控補齊卡。",
"boundaryTitle": "不可誤讀合約",
"boundary": "live Telegram 發送={live}runtime gate={gate}。這裡只做草案與 負責人審查讀回,不發送、不排程、不執行。",
"boundary": "live Telegram 發送={live}runtime gate={gate}。這裡只做草案與 AI 受控 review 讀回,不發送、不排程、不執行。",
"openReports": "回報表總控",
"ownerRequired": "需 負責人審查",
"ownerOptional": "負責人審查 可後補",
"ownerRequired": "需 AI 受控補齊",
"ownerOptional": "AI 受控補齊可後補",
"scheduleBoundary": "排程仍維持 無發送 preview",
"fieldsTitle": "PlayBook 必填欄位",
"checksTitle": "Verifier 檢查",
@@ -9154,7 +9154,7 @@
"title": "AI Provider primary lane 修復工作項"
},
"reportSourceGapOwnerReview": {
"title": "報表資料源 PlayBook / Verifier 負責人審查"
"title": "報表資料源 PlayBook / Verifier AI 受控補齊"
},
"configDriftFsm": {
"title": "Config Drift fingerprint 狀態機"
@@ -9166,7 +9166,7 @@
"title": "Telegram 詳情 / 歷史改為 DB 真相優先"
},
"callbackOwnerReview": {
"title": "Callback 未匹配 KM Owner Review 工作項"
"title": "Callback 未匹配 KM Controlled 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、排程 無發送 與 負責人審查;不得把全 0 當健康或自動執行授權",
"reportSourceGapOwnerReview": "每個 report-source-gap 必須有 PlayBook 草案、Verifier 計畫、腳本 readback、排程無發送與 AI 受控 review;不得把全 0 當健康或自動執行授權",
"configDriftFsm": "同一 drift fingerprint 必須顯示重複、PR、零 diff、交接與下一步",
"remediationQueue": "每筆 degraded / failed / timeout都必須映射到重跑、重驗、Ticket或人工檢查",
"remediationQueue": "每筆 degraded / failed / timeout都必須映射到重跑、重驗、Ticket或 AI 受控檢查",
"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}人工{human}",
"remediationQueue": "補救工作:{total}AI可接手{ready}AI補齊{human}",
"telegramCallbacks": "目前修補 Telegram callback 查詢鏈與歷史摘要",
"telegramCallbacksLive": "只讀 callback toast 400 已非致命;詳情 / 歷史改由 DB 真相鏈 回覆",
"callbackOwnerReview": "Callback owner-review 缺口:{open} 個 opencallback 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};需要人工={human}",
"callbackTraceRecoveryAction": "接續處理:{action};需AI補齊={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": "排入 負責人審查 失敗;請重新整理後再確認此 KM 是否仍為陳舊候選。",
"queueFailed": "排入 AI 受控 review 失敗;請重新整理後再確認此 KM 是否仍為陳舊候選。",
"queueResult": "審核狀態:{status}Dispatch{dispatch}Event{event}",
"ownerReviewState": "Owner review{status};階段:{stage}Dispatch{dispatch}",
"guardrail": "防護:讀取不寫入={writes}人工覆核={review}",
"ownerReviewState": "Controlled review{status};階段:{stage}Dispatch{dispatch}",
"guardrail": "防護:讀取不寫入={writes}受控覆核={review}",
"queueStatuses": {
"dry_run": "乾跑",
"queued": "已排入 負責人審查",
"already_queued": "已在 負責人審查"
"queued": "已排入 AI 受控 review",
"already_queued": "已在 AI 受控 review"
},
"operationRail": {
"title": "Owner Review 操作軌道",
"title": "Controlled Review 操作軌道",
"subtitle": "把陳舊 KM 從偵測、審核、乾跑、確認、寫回到比例回測收斂成一條可掃描流程。",
"nextAction": "建議下一步",
"guardrailTitle": "寫入防護",
@@ -9451,7 +9451,7 @@
"flow": {
"node": {
"detected": "偵測",
"ownerReview": "Owner Review",
"ownerReview": "Controlled Review",
"dryRun": "乾跑預覽",
"ownerConfirm": "Owner 確認",
"writeback": "寫回 KM",
@@ -9484,12 +9484,12 @@
},
"guardrail": {
"writesOnRead": "讀取即寫入:{value}",
"manualReview": "人工覆核必要:{value}",
"manualReview": "受控覆核必要:{value}",
"batchWrites": "批次寫入允許:{value}"
}
},
"singleItemRail": {
"title": "單筆 Owner Review 處理",
"title": "單筆 Controlled Review 處理",
"subtitle": "先乾跑取得 plan fingerprintOwner 確認後才允許寫 KM、寫 audit 並排比例回測。",
"outcome": "策略:{outcome}",
"writeGate": "確認寫 KM={writes};可確認={confirm}",
@@ -9507,10 +9507,10 @@
"dispatch": "Dispatch {dispatch}",
"dryRunReady": "已取得 fingerprint可進入 owner confirm gate",
"dryRunPending": "按單筆乾跑取得 fingerprint",
"dryRunBlocked": "需先排入 負責人審查",
"dryRunBlocked": "需先排入 AI 受控 review",
"confirmReady": "確認後會寫 KM / audit並排 recheck",
"confirmWaiting": "等待 乾跑 fingerprint",
"confirmDone": "負責人審查 已完成",
"confirmDone": "AI 受控 review 已完成",
"recheckDone": "Recheck {recheck}",
"recheckWaiting": "寫回完成後才會產生 recheck"
},
@@ -9520,7 +9520,7 @@
}
},
"ownerReviewInbox": {
"title": "Owner review 工作台",
"title": "Controlled review 工作台",
"subtitle": "顯示已排入 waiting_owner_review的 P0/P1 KM逐筆乾跑與確認完成。",
"total": "待審 {count}",
"returned": "顯示 {count}",
@@ -9532,7 +9532,7 @@
},
"burnDown": {
"title": "Stale ratio burn-down",
"subtitle": "把 負責人審查、completion audit與recheck snapshot 對齊,確認陳舊比例是否真的下降。",
"subtitle": "把 AI 受控 review、completion audit與recheck snapshot 對齊,確認陳舊比例是否真的下降。",
"statuses": "狀態:{status}",
"status": {
"above_threshold": "仍高於門檻",
@@ -9544,26 +9544,26 @@
"empty": "尚無 負責人批准 completion audit。",
"currentRatio": "目前陳舊比例",
"currentCount": "陳舊 / 總數",
"ownerReviews": "Owner review",
"ownerReviews": "Controlled 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": "把 負責人審查 拆成可乾跑、卡住、已完成、失敗,避免只看到告警卻不知道下一步。",
"subtitle": "把 AI 受控 review 拆成可乾跑、卡住、已完成、失敗,避免只看到告警卻不知道下一步。",
"ready": "可處理 {count}",
"blocked": "卡住 {count}",
"completed": "完成 {count}",
"failed": "失敗 {count}",
"pending": "待處理 dispatch {count}",
"guardrail": "讀取不寫入={writes}人工覆核={review};批次寫入={batch}",
"unavailable": "completion queue API 尚未回應;目前只能從 負責人審查 工作台逐筆確認。",
"guardrail": "讀取不寫入={writes}受控覆核={review};批次寫入={batch}",
"unavailable": "completion queue API 尚未回應;目前只能從 AI 受控 review 工作台逐筆確認。",
"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": "批次已排入 負責人審查",
"queued": "批次已排入 AI 受控 review",
"noop_already_queued": "全部已在審核或已處理",
"unknown": "批次狀態待確認"
},
@@ -9625,7 +9625,7 @@
"previewing": "預覽中",
"confirm": "確認完成",
"confirming": "寫入中",
"previewFailed": "乾跑預覽失敗;請重新整理後確認 負責人審查 dispatch仍有效。",
"previewFailed": "乾跑預覽失敗;請重新整理後確認 AI 受控 review 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": "批次 負責人審查 已乾跑",
"batch_owner_review_queued": "批次已排入 負責人審查",
"batch_owner_review_previewed": "批次 AI 受控 review 已乾跑",
"batch_owner_review_queued": "批次已排入 AI 受控 review",
"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": "需要人工整理 KM",
"needs_manual_km_triage": "需要 AI 整理 KM",
"cancelled": "已取消",
"queued_for_review": "等待治理審核",
"dispatched": "已派遣",
@@ -9765,7 +9765,7 @@
},
"driftFingerprint": {
"title": "Config Drift fingerprint 狀態",
"subtitle": "把每小時 drift report 收斂成同一狀態鏈,顯示 PR、零 diff、P0 去重與人工交接",
"subtitle": "把每小時 drift report 收斂成同一狀態鏈,顯示 PR、零 diff、P0 去重與 AI 受控交接",
"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": "等待人工",
"pending_human_repeated": "重複等待人工",
"pending_human": "等待 AI 受控補齊",
"pending_human_repeated": "AI 受控補齊重試",
"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": "人工確認無需動作",
"manual_noop": "AI 受控確認無需動作",
"unknown": "未知"
},
"remediationStatuses": {
@@ -9881,7 +9881,7 @@
"repairCandidateDraft": {
"eyebrow": "修復候選草案",
"title": "PlayBook 草案處置板",
"subtitle": "這筆告警已確認不能把通用兜底或診斷型 PlayBook 當成修復命令;下一步是補齊服務專屬修復草案,通過 負責人審查 與風險閘門後才可能進入審批或執行。",
"subtitle": "這筆告警已確認不能把通用兜底或診斷型 PlayBook 當成修復命令;下一步是補齊服務專屬修復草案,通過 AI 受控 review 與風險閘門後才可能進入審批或執行。",
"statusValue": "等待 PlayBook 草案",
"metrics": {
"status": "狀態",
@@ -9923,7 +9923,7 @@
"repair_command_template": "修復模板",
"rollback_command_template": "Rollback",
"verifier_plan": "Verifier",
"owner_review": "Owner review",
"owner_review": "Controlled review",
"maintenance_window": "維護窗口",
"blast_radius": "影響範圍",
"km_writeback_owner": "KM owner",
@@ -9960,7 +9960,7 @@
"detail": "建立服務專屬修復、回滾與 verifier 計畫。"
},
"review": {
"title": "Owner review",
"title": "Controlled 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 更新草稿與 負責人審查 計畫,避免錯知識直接固化。",
"schedule_or_monitoring_rule_ref": "排程、監控規則或 recurrence 偵測參照,避免同類告警只靠個人口頭記憶或交接。",
"km_update_plan": "KM 更新草稿與 AI 受控 review 計畫,避免錯知識直接固化。",
"automation_asset_record": "自動化資產紀錄,包含 asset id、owner、狀態、來源與下一步。"
},
"assetsTitle": "自動化資產沉澱板",
@@ -9987,7 +9987,7 @@
"km": {
"type": "KM",
"owner": "Hermes",
"visibility": "Knowledge Base根因、處置、引用來源與 負責人審查。",
"visibility": "Knowledge Base根因、處置、引用來源與 AI 受控 review。",
"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 草稿,負責人審查 後才可寫入高影響知識。",
"km_update_draft": "Hermes 產生 KM 草稿,受控 review 後才可寫入高影響知識。",
"playbook_trust_update": "PlayBook 成功 / 失敗 / 未執行都要回寫 trust 與適用條件。",
"automation_asset_inventory_record": "資產清冊要留下 KM、PlayBook、腳本、排程、Verifier 的 ID 與狀態。"
},
"guardrailTitle": "阻擋原因與禁止誤讀",
"blocker": "目前缺少可信修復候選;系統必須建立 AI 補齊草案工作項,不能把 no-action、診斷結果或通用兜底當作已修復。",
"nextStep": "請先補 PlayBook 草案與 MCP evidence再由 負責人審查 決定是否送審批;在此之前不會自動執行、不會寫入成功修復,也不會更新 KM 為已解決。",
"nextStep": "請先補 PlayBook 草案與 MCP evidence再由 AI 受控 review 決定是否送審批;在此之前不會自動執行、不會寫入成功修復,也不會更新 KM 為已解決。",
"chainTitle": "真相鏈對照",
"chain": {
"stage": "目前階段",
@@ -11385,7 +11385,7 @@
"blockedGatesDetail": "有 missing / failed 就不能宣稱完整自動化。",
"readiness": "流程健康度",
"warningGates": "Warning Gate",
"warningGatesDetail": "仍可觀測,但需要補脈絡或人工判斷。"
"warningGatesDetail": "仍可觀測,但需要 AI 補脈絡或受控判讀。"
},
"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": "低摩擦、先觀測、封鎖前先由負責人審查的推出政策。"
"rolloutPolicy": "低摩擦、先觀測、封鎖前先由 AI 受控 review 判讀的推出政策。"
}
},
"githubPrimaryReadinessCandidate": {
@@ -11571,7 +11571,7 @@
"contractRefs": {
"primaryReadiness": "GitHub 主要來源一致性、負責人、分支 / 標籤參照、工作流程與回復前置缺口的主就緒度閘門。",
"ownerValidation": "四包負責人回覆的已收到 / 已接受 / 已拒收分離與審查者檢查口徑。",
"rollbackAdr": "9 個範圍內專案庫的回復 ADR 草案、負責人審查與驗證窗口。",
"rollbackAdr": "9 個範圍內專案庫的回復 ADR 草案、AI 受控 review 與驗證窗口。",
"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 個顯示區塊只用於說明驗收流程、證據路由與邊界。"
},

View File

@@ -8505,7 +8505,7 @@
"detail": "來源事件是否回到 Run / Incident"
},
"gate": {
"label": "人工 Gate",
"label": "AI 受控 Gate",
"detail": "待處理工作 {work}"
},
"verify": {
@@ -8569,7 +8569,7 @@
"autoRepairedVerified": "已驗證自動修復",
"executionUnverified": "已執行但未驗證",
"executionFailed": "執行失敗",
"manualRequiredNoAction": "人工介入NO_ACTION",
"manualRequiredNoAction": "AI 受控補齊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 受控閘門與負責人審查"
"detail": "AI 受控閘門與受控 review"
},
"verifier": {
"title": "驗證",
@@ -8988,7 +8988,7 @@
},
"learning": {
"title": "KM / Trust 回寫",
"detail": "確認 KM、PlayBook trust 與學習責任已沉澱,避免下次重複人工判斷。"
"detail": "確認 KM、PlayBook trust 與學習責任已沉澱,避免下次重複 AI 補齊判斷。"
}
}
},
@@ -9035,7 +9035,7 @@
"items": {
"km": {
"title": "Knowledge Base",
"detail": "Hermes 草稿、陳舊 KM、負責人審查 與 stale ratio 回測。",
"detail": "Hermes 草稿、陳舊 KM、AI 受控 review 與 stale ratio 回測。",
"next": "下一步:把待補 KM 與 Incident / PlayBook / verifier 結果綁定。"
},
"playbook": {
@@ -9061,7 +9061,7 @@
},
"sources": {
"knowledgeBase": "Knowledge Base",
"ownerReview": "Owner review",
"ownerReview": "Controlled review",
"staleRatio": "Stale ratio",
"statusChain": "Status-chain",
"remediationQueue": "補救佇列",
@@ -9078,17 +9078,17 @@
}
},
"reportSourceGapOwnerReview": {
"eyebrow": "報表資料源 負責人審查",
"eyebrow": "報表資料源 AI 受控補齊",
"title": "報表資料源 PlayBook / Verifier 處置板",
"subtitle": "把 report-source-gap 從報表頁接到 Work Items每個缺口都要有 PlayBook 草案、Verifier 計畫、腳本 readback、排程 無發送 與 負責人審查。",
"loading": "正在讀取 report-source-gap 負責人審查 read model。",
"subtitle": "把 report-source-gap 從報表頁接到 Work Items每個缺口都要有 PlayBook 草案、Verifier 計畫、腳本 readback、排程無發送與 AI 受控 review。",
"loading": "正在讀取 report-source-gap AI 受控補齊 read model。",
"unavailable": "報表資料源 read model 尚未回應;不能把報表全 0 或缺資料判定為健康。",
"empty": "目前沒有 report-source-gap 負責人審查 卡。",
"empty": "目前沒有 report-source-gap AI 受控補齊卡。",
"boundaryTitle": "不可誤讀合約",
"boundary": "live Telegram 發送={live}runtime gate={gate}。這裡只做草案與 負責人審查讀回,不發送、不排程、不執行。",
"boundary": "live Telegram 發送={live}runtime gate={gate}。這裡只做草案與 AI 受控 review 讀回,不發送、不排程、不執行。",
"openReports": "回報表總控",
"ownerRequired": "需 負責人審查",
"ownerOptional": "負責人審查 可後補",
"ownerRequired": "需 AI 受控補齊",
"ownerOptional": "AI 受控補齊可後補",
"scheduleBoundary": "排程仍維持 無發送 preview",
"fieldsTitle": "PlayBook 必填欄位",
"checksTitle": "Verifier 檢查",
@@ -9154,7 +9154,7 @@
"title": "AI Provider primary lane 修復工作項"
},
"reportSourceGapOwnerReview": {
"title": "報表資料源 PlayBook / Verifier 負責人審查"
"title": "報表資料源 PlayBook / Verifier AI 受控補齊"
},
"configDriftFsm": {
"title": "Config Drift fingerprint 狀態機"
@@ -9166,7 +9166,7 @@
"title": "Telegram 詳情 / 歷史改為 DB 真相優先"
},
"callbackOwnerReview": {
"title": "Callback 未匹配 KM Owner Review 工作項"
"title": "Callback 未匹配 KM Controlled 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、排程 無發送 與 負責人審查;不得把全 0 當健康或自動執行授權",
"reportSourceGapOwnerReview": "每個 report-source-gap 必須有 PlayBook 草案、Verifier 計畫、腳本 readback、排程無發送與 AI 受控 review;不得把全 0 當健康或自動執行授權",
"configDriftFsm": "同一 drift fingerprint 必須顯示重複、PR、零 diff、交接與下一步",
"remediationQueue": "每筆 degraded / failed / timeout都必須映射到重跑、重驗、Ticket或人工檢查",
"remediationQueue": "每筆 degraded / failed / timeout都必須映射到重跑、重驗、Ticket或 AI 受控檢查",
"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}人工{human}",
"remediationQueue": "補救工作:{total}AI可接手{ready}AI補齊{human}",
"telegramCallbacks": "目前修補 Telegram callback 查詢鏈與歷史摘要",
"telegramCallbacksLive": "只讀 callback toast 400 已非致命;詳情 / 歷史改由 DB 真相鏈 回覆",
"callbackOwnerReview": "Callback owner-review 缺口:{open} 個 opencallback 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};需要人工={human}",
"callbackTraceRecoveryAction": "接續處理:{action};需AI補齊={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": "排入 負責人審查 失敗;請重新整理後再確認此 KM 是否仍為陳舊候選。",
"queueFailed": "排入 AI 受控 review 失敗;請重新整理後再確認此 KM 是否仍為陳舊候選。",
"queueResult": "審核狀態:{status}Dispatch{dispatch}Event{event}",
"ownerReviewState": "Owner review{status};階段:{stage}Dispatch{dispatch}",
"guardrail": "防護:讀取不寫入={writes}人工覆核={review}",
"ownerReviewState": "Controlled review{status};階段:{stage}Dispatch{dispatch}",
"guardrail": "防護:讀取不寫入={writes}受控覆核={review}",
"queueStatuses": {
"dry_run": "乾跑",
"queued": "已排入 負責人審查",
"already_queued": "已在 負責人審查"
"queued": "已排入 AI 受控 review",
"already_queued": "已在 AI 受控 review"
},
"operationRail": {
"title": "Owner Review 操作軌道",
"title": "Controlled Review 操作軌道",
"subtitle": "把陳舊 KM 從偵測、審核、乾跑、確認、寫回到比例回測收斂成一條可掃描流程。",
"nextAction": "建議下一步",
"guardrailTitle": "寫入防護",
@@ -9451,7 +9451,7 @@
"flow": {
"node": {
"detected": "偵測",
"ownerReview": "Owner Review",
"ownerReview": "Controlled Review",
"dryRun": "乾跑預覽",
"ownerConfirm": "Owner 確認",
"writeback": "寫回 KM",
@@ -9484,12 +9484,12 @@
},
"guardrail": {
"writesOnRead": "讀取即寫入:{value}",
"manualReview": "人工覆核必要:{value}",
"manualReview": "受控覆核必要:{value}",
"batchWrites": "批次寫入允許:{value}"
}
},
"singleItemRail": {
"title": "單筆 Owner Review 處理",
"title": "單筆 Controlled Review 處理",
"subtitle": "先乾跑取得 plan fingerprintOwner 確認後才允許寫 KM、寫 audit 並排比例回測。",
"outcome": "策略:{outcome}",
"writeGate": "確認寫 KM={writes};可確認={confirm}",
@@ -9507,10 +9507,10 @@
"dispatch": "Dispatch {dispatch}",
"dryRunReady": "已取得 fingerprint可進入 owner confirm gate",
"dryRunPending": "按單筆乾跑取得 fingerprint",
"dryRunBlocked": "需先排入 負責人審查",
"dryRunBlocked": "需先排入 AI 受控 review",
"confirmReady": "確認後會寫 KM / audit並排 recheck",
"confirmWaiting": "等待 乾跑 fingerprint",
"confirmDone": "負責人審查 已完成",
"confirmDone": "AI 受控 review 已完成",
"recheckDone": "Recheck {recheck}",
"recheckWaiting": "寫回完成後才會產生 recheck"
},
@@ -9520,7 +9520,7 @@
}
},
"ownerReviewInbox": {
"title": "Owner review 工作台",
"title": "Controlled review 工作台",
"subtitle": "顯示已排入 waiting_owner_review的 P0/P1 KM逐筆乾跑與確認完成。",
"total": "待審 {count}",
"returned": "顯示 {count}",
@@ -9532,7 +9532,7 @@
},
"burnDown": {
"title": "Stale ratio burn-down",
"subtitle": "把 負責人審查、completion audit與recheck snapshot 對齊,確認陳舊比例是否真的下降。",
"subtitle": "把 AI 受控 review、completion audit與recheck snapshot 對齊,確認陳舊比例是否真的下降。",
"statuses": "狀態:{status}",
"status": {
"above_threshold": "仍高於門檻",
@@ -9544,26 +9544,26 @@
"empty": "尚無 負責人批准 completion audit。",
"currentRatio": "目前陳舊比例",
"currentCount": "陳舊 / 總數",
"ownerReviews": "Owner review",
"ownerReviews": "Controlled 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": "把 負責人審查 拆成可乾跑、卡住、已完成、失敗,避免只看到告警卻不知道下一步。",
"subtitle": "把 AI 受控 review 拆成可乾跑、卡住、已完成、失敗,避免只看到告警卻不知道下一步。",
"ready": "可處理 {count}",
"blocked": "卡住 {count}",
"completed": "完成 {count}",
"failed": "失敗 {count}",
"pending": "待處理 dispatch {count}",
"guardrail": "讀取不寫入={writes}人工覆核={review};批次寫入={batch}",
"unavailable": "completion queue API 尚未回應;目前只能從 負責人審查 工作台逐筆確認。",
"guardrail": "讀取不寫入={writes}受控覆核={review};批次寫入={batch}",
"unavailable": "completion queue API 尚未回應;目前只能從 AI 受控 review 工作台逐筆確認。",
"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": "批次已排入 負責人審查",
"queued": "批次已排入 AI 受控 review",
"noop_already_queued": "全部已在審核或已處理",
"unknown": "批次狀態待確認"
},
@@ -9625,7 +9625,7 @@
"previewing": "預覽中",
"confirm": "確認完成",
"confirming": "寫入中",
"previewFailed": "乾跑預覽失敗;請重新整理後確認 負責人審查 dispatch仍有效。",
"previewFailed": "乾跑預覽失敗;請重新整理後確認 AI 受控 review 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": "批次 負責人審查 已乾跑",
"batch_owner_review_queued": "批次已排入 負責人審查",
"batch_owner_review_previewed": "批次 AI 受控 review 已乾跑",
"batch_owner_review_queued": "批次已排入 AI 受控 review",
"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": "需要人工整理 KM",
"needs_manual_km_triage": "需要 AI 整理 KM",
"cancelled": "已取消",
"queued_for_review": "等待治理審核",
"dispatched": "已派遣",
@@ -9765,7 +9765,7 @@
},
"driftFingerprint": {
"title": "Config Drift fingerprint 狀態",
"subtitle": "把每小時 drift report 收斂成同一狀態鏈,顯示 PR、零 diff、P0 去重與人工交接",
"subtitle": "把每小時 drift report 收斂成同一狀態鏈,顯示 PR、零 diff、P0 去重與 AI 受控交接",
"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": "等待人工",
"pending_human_repeated": "重複等待人工",
"pending_human": "等待 AI 受控補齊",
"pending_human_repeated": "AI 受控補齊重試",
"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": "人工確認無需動作",
"manual_noop": "AI 受控確認無需動作",
"unknown": "未知"
},
"remediationStatuses": {
@@ -9881,7 +9881,7 @@
"repairCandidateDraft": {
"eyebrow": "修復候選草案",
"title": "PlayBook 草案處置板",
"subtitle": "這筆告警已確認不能把通用兜底或診斷型 PlayBook 當成修復命令;下一步是補齊服務專屬修復草案,通過 負責人審查 與風險閘門後才可能進入審批或執行。",
"subtitle": "這筆告警已確認不能把通用兜底或診斷型 PlayBook 當成修復命令;下一步是補齊服務專屬修復草案,通過 AI 受控 review 與風險閘門後才可能進入審批或執行。",
"statusValue": "等待 PlayBook 草案",
"metrics": {
"status": "狀態",
@@ -9923,7 +9923,7 @@
"repair_command_template": "修復模板",
"rollback_command_template": "Rollback",
"verifier_plan": "Verifier",
"owner_review": "Owner review",
"owner_review": "Controlled review",
"maintenance_window": "維護窗口",
"blast_radius": "影響範圍",
"km_writeback_owner": "KM owner",
@@ -9960,7 +9960,7 @@
"detail": "建立服務專屬修復、回滾與 verifier 計畫。"
},
"review": {
"title": "Owner review",
"title": "Controlled 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 更新草稿與 負責人審查 計畫,避免錯知識直接固化。",
"schedule_or_monitoring_rule_ref": "排程、監控規則或 recurrence 偵測參照,避免同類告警只靠個人口頭記憶或交接。",
"km_update_plan": "KM 更新草稿與 AI 受控 review 計畫,避免錯知識直接固化。",
"automation_asset_record": "自動化資產紀錄,包含 asset id、owner、狀態、來源與下一步。"
},
"assetsTitle": "自動化資產沉澱板",
@@ -9987,7 +9987,7 @@
"km": {
"type": "KM",
"owner": "Hermes",
"visibility": "Knowledge Base根因、處置、引用來源與 負責人審查。",
"visibility": "Knowledge Base根因、處置、引用來源與 AI 受控 review。",
"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 草稿,負責人審查 後才可寫入高影響知識。",
"km_update_draft": "Hermes 產生 KM 草稿,受控 review 後才可寫入高影響知識。",
"playbook_trust_update": "PlayBook 成功 / 失敗 / 未執行都要回寫 trust 與適用條件。",
"automation_asset_inventory_record": "資產清冊要留下 KM、PlayBook、腳本、排程、Verifier 的 ID 與狀態。"
},
"guardrailTitle": "阻擋原因與禁止誤讀",
"blocker": "目前缺少可信修復候選;系統必須建立 AI 補齊草案工作項,不能把 no-action、診斷結果或通用兜底當作已修復。",
"nextStep": "請先補 PlayBook 草案與 MCP evidence再由 負責人審查 決定是否送審批;在此之前不會自動執行、不會寫入成功修復,也不會更新 KM 為已解決。",
"nextStep": "請先補 PlayBook 草案與 MCP evidence再由 AI 受控 review 決定是否送審批;在此之前不會自動執行、不會寫入成功修復,也不會更新 KM 為已解決。",
"chainTitle": "真相鏈對照",
"chain": {
"stage": "目前階段",
@@ -11385,7 +11385,7 @@
"blockedGatesDetail": "有 missing / failed 就不能宣稱完整自動化。",
"readiness": "流程健康度",
"warningGates": "Warning Gate",
"warningGatesDetail": "仍可觀測,但需要補脈絡或人工判斷。"
"warningGatesDetail": "仍可觀測,但需要 AI 補脈絡或受控判讀。"
},
"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": "低摩擦、先觀測、封鎖前先由負責人審查的推出政策。"
"rolloutPolicy": "低摩擦、先觀測、封鎖前先由 AI 受控 review 判讀的推出政策。"
}
},
"githubPrimaryReadinessCandidate": {
@@ -11571,7 +11571,7 @@
"contractRefs": {
"primaryReadiness": "GitHub 主要來源一致性、負責人、分支 / 標籤參照、工作流程與回復前置缺口的主就緒度閘門。",
"ownerValidation": "四包負責人回覆的已收到 / 已接受 / 已拒收分離與審查者檢查口徑。",
"rollbackAdr": "9 個範圍內專案庫的回復 ADR 草案、負責人審查與驗證窗口。",
"rollbackAdr": "9 個範圍內專案庫的回復 ADR 草案、AI 受控 review 與驗證窗口。",
"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 個顯示區塊只用於說明驗收流程、證據路由與邊界。"
},

View File

@@ -1,3 +1,97 @@
## 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` 正在跑的 runnerlive `/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 visibilityAPI 會直接 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 changesnon-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 mirrorzh-TW / en leaf key count `14476 / 14476`missing `0 / 0`
- AwoooP scoped diffzh-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 controlledcritical / 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` 等語意再次回到低 / 中 / 高風險流程。
@@ -47971,3 +48065,40 @@ 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 readinesscanonical 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 snippetgate 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 readbackproduction 仍以前一版為準直到 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` 維持不變。

View File

@@ -0,0 +1,198 @@
{
"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"
}

View File

@@ -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: 30af0fb4202073719f8c8011fe773f806576ede0
newTag: 8251026c06f0707e7c955fce55dd8ddd8d5e7662
- name: 192.168.0.110:5000/library/web:IMAGE_TAG_PLACEHOLDER
newName: 192.168.0.110:5000/awoooi/web
newTag: 30af0fb4202073719f8c8011fe773f806576ede0

View File

@@ -7,10 +7,16 @@ 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:-60}}"
ATTEMPTS="${HOST_WEB_BUILD_PRESSURE_ATTEMPTS:-${HOST_WEB_BUILD_PRESSURE_MAX_ATTEMPTS:-6}}"
SLEEP_SECONDS="${HOST_WEB_BUILD_PRESSURE_SLEEP_SECONDS:-${HOST_WEB_BUILD_PRESSURE_INTERVAL:-10}}"
WARN_ONLY="${HOST_WEB_BUILD_PRESSURE_WARN_ONLY:-0}"
WARN_ONLY="${HOST_WEB_BUILD_PRESSURE_WARN_ONLY:-1}"
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.

View File

@@ -184,18 +184,31 @@ fi
# ──────────────────────────────────────────────
# STEP 6: Gitea Act RunnerCI/CD 核心)
# 2026-04-05 Claude Code: 加入 — 解決重開機後 Gitea runner 離線、CD 失效
# 2026-06-27 Codex: 110 是 production / registry / observability 主機;
# runner 預設維持停用降壓,未完成限流 / 搬遷前不可在 startup 自動拉起。
# 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 僅在明確要求時作為第二鑰匙。
# ──────────────────────────────────────────────
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"
START_GITEA_RUNNER_ON_BOOT="${AWOOOI_START_GITEA_RUNNER_ON_BOOT:-0}"
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
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_ON_BOOT" = "1" ] && [ -f "$RUNNER_FILE" ]; then
if [ "$START_GITEA_RUNNER_ALLOWED" = "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),清除重新註冊..."
@@ -251,14 +264,26 @@ while idx < len(lines):
path.write_text("\n".join(output) + "\n")
PY
if [ "$START_GITEA_RUNNER_ON_BOOT" = "1" ]; then
if [ "$START_GITEA_RUNNER_ALLOWED" = "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
log "⏸️ Gitea host runner 維持停用;設定 AWOOOI_START_GITEA_RUNNER_ON_BOOT=1 才允許 startup 啟動"
if [ "$START_GITEA_RUNNER_ON_BOOT" = "1" ] && [ "$REQUIRE_RUNNER_ENABLE_SENTINEL" = "1" ]; then
log "⛔ AWOOOI_START_GITEA_RUNNER_ON_BOOT=1 但缺少 $RUNNER_ENABLE_SENTINELrunner 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
fi
# 已停用 Docker-wrapped runner避免它搶走 host label job。
@@ -277,7 +302,7 @@ PY
# 驗證 runner 已連線 Gitea
if pgrep -f "$RUNNER_DIR/act_runner daemon" >/dev/null; then
log "⚠️ Gitea host act_runner 目前正在執行;請確認是否為受控限流 / 搬遷後狀態"
elif [ "$START_GITEA_RUNNER_ON_BOOT" = "1" ]; then
elif [ "$START_GITEA_RUNNER_ALLOWED" = "1" ]; then
log "⚠️ Gitea host act_runner 可能尚未啟動,查看: $RUNNER_DIR/host-runner.log"
else
log "✅ Gitea host act_runner 維持 inactive 降壓狀態"

View File

@@ -9,7 +9,9 @@ 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 = [
@@ -44,6 +46,37 @@ 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.",
]
@@ -54,6 +87,38 @@ 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] = []
@@ -65,6 +130,8 @@ 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: