fix(governance): enforce P2-105 redaction guard
This commit is contained in:
@@ -45,6 +45,7 @@ def load_latest_ai_agent_critic_reviewer_result_capture(
|
|||||||
_require_promotion_gates(payload, str(latest))
|
_require_promotion_gates(payload, str(latest))
|
||||||
_require_candidate_routes(payload, str(latest))
|
_require_candidate_routes(payload, str(latest))
|
||||||
_require_redaction_contract(payload, str(latest))
|
_require_redaction_contract(payload, str(latest))
|
||||||
|
_require_no_forbidden_display_terms(payload, str(latest))
|
||||||
_require_rollup_consistency(payload, str(latest))
|
_require_rollup_consistency(payload, str(latest))
|
||||||
return payload
|
return payload
|
||||||
|
|
||||||
@@ -262,6 +263,38 @@ def _require_redaction_contract(payload: dict[str, Any], label: str) -> None:
|
|||||||
raise ValueError(f"{label}: display redaction fields must remain false: {unsafe}")
|
raise ValueError(f"{label}: display redaction fields must remain false: {unsafe}")
|
||||||
|
|
||||||
|
|
||||||
|
def _require_no_forbidden_display_terms(payload: dict[str, Any], label: str) -> None:
|
||||||
|
forbidden_terms = {
|
||||||
|
"工作視窗",
|
||||||
|
"對話內容",
|
||||||
|
"批准!繼續",
|
||||||
|
"In app browser",
|
||||||
|
"My request for Codex",
|
||||||
|
"work window transcript",
|
||||||
|
"internal collaboration transcript",
|
||||||
|
}
|
||||||
|
|
||||||
|
hits: list[str] = []
|
||||||
|
|
||||||
|
def walk(value: Any, path: str) -> None:
|
||||||
|
if isinstance(value, dict):
|
||||||
|
for key, nested in value.items():
|
||||||
|
walk(nested, f"{path}.{key}" if path else str(key))
|
||||||
|
return
|
||||||
|
if isinstance(value, list):
|
||||||
|
for index, nested in enumerate(value):
|
||||||
|
walk(nested, f"{path}[{index}]")
|
||||||
|
return
|
||||||
|
if isinstance(value, str):
|
||||||
|
matched = sorted(term for term in forbidden_terms if term in value)
|
||||||
|
if matched:
|
||||||
|
hits.append(f"{path}: {', '.join(matched)}")
|
||||||
|
|
||||||
|
walk(payload, "")
|
||||||
|
if hits:
|
||||||
|
raise ValueError(f"{label}: forbidden display terms found: {hits}")
|
||||||
|
|
||||||
|
|
||||||
def _require_rollup_consistency(payload: dict[str, Any], label: str) -> None:
|
def _require_rollup_consistency(payload: dict[str, Any], label: str) -> None:
|
||||||
rollups = payload.get("rollups") or {}
|
rollups = payload.get("rollups") or {}
|
||||||
truth = payload.get("score_truth") or {}
|
truth = payload.get("score_truth") or {}
|
||||||
|
|||||||
@@ -112,6 +112,16 @@ def test_rejects_candidate_route_write(tmp_path):
|
|||||||
load_latest_ai_agent_critic_reviewer_result_capture(tmp_path)
|
load_latest_ai_agent_critic_reviewer_result_capture(tmp_path)
|
||||||
|
|
||||||
|
|
||||||
|
def test_rejects_forbidden_display_terms(tmp_path):
|
||||||
|
data = load_latest_ai_agent_critic_reviewer_result_capture()
|
||||||
|
bad = copy.deepcopy(data)
|
||||||
|
bad["agent_scorecards"][0]["failure_if_missing"] = "不得顯示工作視窗對話內容"
|
||||||
|
_write_snapshot(tmp_path, bad)
|
||||||
|
|
||||||
|
with pytest.raises(ValueError, match="forbidden display terms"):
|
||||||
|
load_latest_ai_agent_critic_reviewer_result_capture(tmp_path)
|
||||||
|
|
||||||
|
|
||||||
def test_rejects_rollup_mismatch(tmp_path):
|
def test_rejects_rollup_mismatch(tmp_path):
|
||||||
data = load_latest_ai_agent_critic_reviewer_result_capture()
|
data = load_latest_ai_agent_critic_reviewer_result_capture()
|
||||||
bad = copy.deepcopy(data)
|
bad = copy.deepcopy(data)
|
||||||
|
|||||||
@@ -49,6 +49,7 @@
|
|||||||
**完成(本地)**:
|
**完成(本地)**:
|
||||||
|
|
||||||
- 新增 `ai_agent_critic_reviewer_result_capture_v1` schema、committed snapshot 與 backend loader,固定 5 張 Agent scorecard、5 個 result capture contract、6 個 promotion gate 與 4 條 candidate route。
|
- 新增 `ai_agent_critic_reviewer_result_capture_v1` schema、committed snapshot 與 backend loader,固定 5 張 Agent scorecard、5 個 result capture contract、6 個 promotion gate 與 4 條 candidate route。
|
||||||
|
- Loader 新增可見文案紅線防退化檢查,snapshot 若再出現 `工作視窗`、`對話內容`、`批准!繼續`、`In app browser`、`My request for Codex` 或舊英文內部逐字稿詞,API 會 fail closed。
|
||||||
- 新增 `GET /api/v1/agents/agent-critic-reviewer-result-capture` 只讀 API 與測試;API 只回傳 scorecard、result capture contract、promotion gate、candidate route 與 redaction boundary。
|
- 新增 `GET /api/v1/agents/agent-critic-reviewer-result-capture` 只讀 API 與測試;API 只回傳 scorecard、result capture contract、promotion gate、candidate route 與 redaction boundary。
|
||||||
- 治理頁 `/zh-TW/governance?tab=automation-inventory` 新增 P2-105 區塊,顯示 OpenClaw Critic / Reviewer、Hermes redaction / operator report、NemoTron failure verifier 如何互判、接手與阻擋 unsafe promotion。
|
- 治理頁 `/zh-TW/governance?tab=automation-inventory` 新增 P2-105 區塊,顯示 OpenClaw Critic / Reviewer、Hermes redaction / operator report、NemoTron failure verifier 如何互判、接手與阻擋 unsafe promotion。
|
||||||
- 更新 `AI_AGENT_AUTOMATION_WORKLIST_2026-06-04.md`、`AI_AGENT_INTERACTION_LEARNING_PROOF_2026-06-11.md` 與 MASTER §3.2 / §5,將 P2-105 標記為完成,下一步改為 `P2-106` owner-approved result capture dry-run。
|
- 更新 `AI_AGENT_AUTOMATION_WORKLIST_2026-06-04.md`、`AI_AGENT_INTERACTION_LEARNING_PROOF_2026-06-11.md` 與 MASTER §3.2 / §5,將 P2-105 標記為完成,下一步改為 `P2-106` owner-approved result capture dry-run。
|
||||||
@@ -58,7 +59,7 @@
|
|||||||
- `python -m json.tool` 等效解析 P2-105 snapshot / schema / `zh-TW.json` / `en.json` 通過。
|
- `python -m json.tool` 等效解析 P2-105 snapshot / schema / `zh-TW.json` / `en.json` 通過。
|
||||||
- `cmp -s apps/web/messages/zh-TW.json apps/web/messages/en.json` 通過,兩份訊息檔維持繁體中文鏡像。
|
- `cmp -s apps/web/messages/zh-TW.json apps/web/messages/en.json` 通過,兩份訊息檔維持繁體中文鏡像。
|
||||||
- `DATABASE_URL='postgresql+asyncpg://test:test@localhost/test' PYTHONPATH=apps/api python -m py_compile apps/api/src/services/ai_agent_critic_reviewer_result_capture.py apps/api/src/api/v1/agents.py` 通過。
|
- `DATABASE_URL='postgresql+asyncpg://test:test@localhost/test' PYTHONPATH=apps/api python -m py_compile apps/api/src/services/ai_agent_critic_reviewer_result_capture.py apps/api/src/api/v1/agents.py` 通過。
|
||||||
- `DATABASE_URL='postgresql+asyncpg://test:test@localhost/test' PYTHONPATH=apps/api python -m pytest -q apps/api/tests/test_ai_agent_critic_reviewer_result_capture.py apps/api/tests/test_ai_agent_critic_reviewer_result_capture_api.py`:`10 passed`。
|
- `DATABASE_URL='postgresql+asyncpg://test:test@localhost/test' PYTHONPATH=apps/api python -m pytest -q apps/api/tests/test_ai_agent_critic_reviewer_result_capture.py apps/api/tests/test_ai_agent_critic_reviewer_result_capture_api.py`:`11 passed`。
|
||||||
- `pnpm --filter @awoooi/web typecheck` 通過。
|
- `pnpm --filter @awoooi/web typecheck` 通過。
|
||||||
- `pnpm --filter @awoooi/web exec next lint --file 'src/app/[locale]/governance/tabs/automation-inventory-tab.tsx' --file src/lib/api-client.ts`:`✔ No ESLint warnings or errors`。
|
- `pnpm --filter @awoooi/web exec next lint --file 'src/app/[locale]/governance/tabs/automation-inventory-tab.tsx' --file src/lib/api-client.ts`:`✔ No ESLint warnings or errors`。
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user