Files
awoooi/scripts/security/awooop-controlled-automation-copy-guard.py
Your Name 7fcfc0b24b
Some checks failed
Ansible / Reboot Recovery Contract / validate (push) Successful in 1m15s
CD Pipeline / tests (push) Failing after 1m8s
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 27s
fix(ci): open controlled guard gates
2026-06-28 03:10:48 +08:00

180 lines
5.3 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Guard AwoooP public copy against legacy manual-gate defaults.
This guard is static and read-only. It prevents AwoooP pages and serialized
message payloads from reintroducing old manual-gate wording as the default
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 = [
Path("apps/web/messages/zh-TW.json"),
Path("apps/web/messages/en.json"),
]
AWOOOP_SOURCE_ROOT = Path("apps/web/src/app/[locale]/awooop")
ALERTS_ROUTE = AWOOOP_SOURCE_ROOT / "alerts" / "page.tsx"
FORBIDDEN_FRAGMENTS = [
"待人工決策",
"等待人工決策",
"阻塞與人工閘門",
"人工接手",
"人工決策佇列",
"人工關卡",
"人工 Gate",
"人工 gate",
"人工閘門",
"人工升級",
"待 owner 複核",
"未批准不會執行",
"等 負責人審查",
"owner review",
"owner packet",
"manual gate",
"manual handoff",
]
REQUIRED_FRAGMENTS = [
"待 AI 受控決策",
"阻塞與 AI 受控隊列",
"AI 處置包與工作項",
"受控執行邊界",
"受控授權閘門",
"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.",
]
def _iter_guarded_files(root: Path) -> list[Path]:
files = [root / path for path in TEXT_FILES]
source_root = root / AWOOOP_SOURCE_ROOT
files.extend(sorted(source_root.rglob("*.tsx")))
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 _collect_forbidden_line_violations(path: Path, root: Path, text: str) -> list[str]:
violations: list[str] = []
for line_number, line in enumerate(text.splitlines(), start=1):
for fragment in FORBIDDEN_FRAGMENTS:
if fragment in line:
relative = path.relative_to(root)
violations.append(f"{relative}:{line_number}: forbidden {fragment!r}")
return violations
def validate(root: Path) -> None:
root = root.resolve()
violations: list[str] = []
guarded_text = []
for path in _iter_guarded_files(root):
if not path.exists():
violations.append(f"{path.relative_to(root)}: missing guarded file")
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))
violations.extend(_collect_forbidden_line_violations(path, root, text))
alerts_route = root / ALERTS_ROUTE
if not alerts_route.exists():
violations.append(f"{ALERTS_ROUTE}: missing AwoooP Alerts route")
else:
route_text = alerts_route.read_text(encoding="utf-8")
if "#ai-alert-card-delivery-readback" not in route_text:
violations.append(f"{ALERTS_ROUTE}: missing ai-alert-card-delivery-readback redirect target")
combined_text = "\n".join(guarded_text)
for fragment in REQUIRED_FRAGMENTS:
if fragment not in combined_text:
violations.append(f"messages/source: missing required controlled automation text {fragment!r}")
if violations:
formatted = "\n".join(violations[:40])
raise SystemExit(f"BLOCKED awooop_controlled_automation_copy_guard:\n{formatted}")
def main() -> int:
parser = argparse.ArgumentParser(description="Validate AwoooP controlled automation public copy.")
parser.add_argument("--root", default=".")
args = parser.parse_args()
validate(Path(args.root))
print("AWOOOP_CONTROLLED_AUTOMATION_COPY_GUARD_OK")
return 0
if __name__ == "__main__":
raise SystemExit(main())